CoverYourASP --> "/SurveyDescr.html" --> Source

Free membership

Join in the fun! Sign in
Member Services

Site navigation
Download the entire site!
Search my articles
Free Magazines
Browse the directory

Send me feedback
Buy my boxer shorts

Recommend this page
Printer-friendly page

Resources I recommend
Link to my site
Advertising slashed!
About your privacy
Legal stuff
Site statistics
24 active users
1402 visitors today
677 pages today
(only part of today)
Tools I use

CoverYourASP
Copyright © 1999-2016 James Shaw.
All rights reserved.

ASP.NET Blog
RSS submissions
E-commerce

Now open source with SourceForge!

This page shows the actual source code used on this site. You can read the article that discusses this code here.

If this is the first CYA source code you've seen you should read this overview first.

Did you know you can download all the source code (and the database) of this site? Then get my newsletter to be emailed when I update the source code!

Please spread the word by recommending my site to your friends and colleagues!

This is JScript (server-side JavaScript), not the more common VBScript. More...

SurveyDescr.asp

<!--#include file = "/include/Startup.html"-->
<!--#include file = "/utils/Survey.html"-->

<%
// ============================================
// NOTE: all source code downloaded from CoverYourASP was written by
// James Shaw (unless stated otherwise), and is copyright (c) 2000-2002
// by James Shaw. You can use the code for any purpose, but do not
// publish or distribute the content in any way.
//
// See http://CoverYourASP.com/Legal.asp for up-to-date details.
// ============================================

// output relevant meta tags
Init( "Survey your readers", "Survey, Quiz, Questionnaire" );

// output common top of page
Header( 'Survey your readers' );

// output page content
Content ( );

// output common bottom of page
Footer( );

// ============================================
// the content of this page - every page has a function 'Content' that
// is called above.
// ============================================
function Content ( )
{
   Out ( '<td valign="top" class="content">' );

      // if the survey hasnt been submitted yet...
      if ( !Request.Form.Count )
      {
         //...display some blah, blah
         Out ( 'Finally, surveys come to CoverYourASP! I\'ve been wanting to ask you guys and gals questions for a long time, and now I can. It\'s up to you if you want to answer of course!' );

         Out ( '<p>Of course, the real benefit to you is that if you tell me what you like I\'ll probably provide it. If you send in your <a href="/Donate.html">donations</a> the probability increases rather dramatically!' );

         Out ( '<p>Take the example survey below if you have the time and inclination. I plan to post more in a special survey category, and start offering incentives to take them.' );

         Out ( '<p>Afterwards, look at the code. I think you\'ll be surprised how simple it is to create surveys with this code. This page has one function call in it, with just one parameter - the name of the survey! All questions, answers and results are stored in the database.' );
      }

      // show the survey, or process it's input
      ProcessSurvey ( 'Who are you and what do you think?' );

      if ( !Request.Form.Count )
         Out ( '<p><a href="/ShowSource_page_SurveyDescr.html"><img src="/images/source.gif" align="right" border=0></a>Please submit the survey first before looking at the source code - this link is on the result page too!' );
      else
         Out ( '<p><center><a href="/ShowSource_page_SurveyDescr.html"><img src="/images/source.gif" border=0></a></center>' );

      ShowBottomBanner()

   Out ( '</td>' );
   Out ( '<td background="/images/gx/navgap.gif" valign="top">' );

      // show rotating banners
      ShowBanners ( 8 );

   Out ( '</td>' );
}

// ============================================
// ProcessSurvey calls this function to give us a chance to add extra
// inputs to the form, before the survey questions
// ============================================
function PreSurvey ( sSurvey, nStage )
{
   switch ( nStage )
   {
   case nDisplayInputs:      // show <input>
      Out ( '<p>There aren\'t any important instructions when answering these questions - except you don\'t have to answer any. All are optional - if you don\'t like a question, or none of the answers are relevant, just move onto the next one!' );
      break;

   case nValidateSurvey:      // validate <input>
      break;

   case nProcessSurvey:      // process <input>
      break;

   case nDisplayAnswers:   // show answers
      Out ( '<p>Thanks for taking part in the "' + sSurvey + '" survey! The answers that everyone has given so far are shown below:' );
      break;
   }

   return true;
}

// ============================================
// ProcessSurvey calls this function to give us a chance to add extra
// inputs to the form, after the survey questions
// ============================================
function PostSurvey ( sSurvey, nStage )
{
   switch ( nStage )
   {
   case nDisplayInputs:      // show <input>
      break;

   case nValidateSurvey:   // validate <input>
      break;

   case nProcessSurvey:      // process <input>
      break;

   case nDisplayAnswers:   // show answers
      break;
   }

   return true;
}
%>

utils/Survey.asp

<%
// ============================================
// NOTE: all source code downloaded from CoverYourASP was written by
// James Shaw (unless stated otherwise), and is copyright (c) 2000-2002
// by James Shaw. You can use the code for any purpose, but do not
// publish or distribute the content in any way.
//
// See http://CoverYourASP.com/Legal.asp for up-to-date details.
// ============================================

// definitions passed into PreSurvey and PostSurvey - two functions
// that you must supply yourself (leave empty if you want)
var nDisplayInputs = 327;      // show <input>
var nValidateSurvey = 877;   // validate <input>
var nProcessSurvey = 198;      // process <input>
var nDisplayAnswers = 432;   // show answers

// ============================================
// display or process the named survey
// ============================================
function ProcessSurvey ( sSurvey )
{
   // has the survey form been submitted?
   var bSubmitted = Request.Form.Count;

   // connect to the database
   DBInitConnection ( );

   // lets validate any inputs first
   if ( bSubmitted )
   {
      if ( !PreSurvey ( sSurvey, nValidateSurvey ) || !PostSurvey ( sSurvey, nValidateSurvey ) )
         bSubmitted = false;
   }

   // am I ready to process the form results?
   if ( bSubmitted )
   {
      // =======================
      // process survey answers
      // =======================

      // process some user inputs first
      PreSurvey ( sSurvey, nProcessSurvey );

      // get the data from the form and update the database
      var e = new Enumerator ( Request.Form );

      while ( !e.atEnd ( ) )
      {
         var nAnswerID = Request.Form ( e.item () ) - 0;

         if ( !isNaN ( nAnswerID ) )
            // increment the current number of times this answer has been chosen
            oConnection.Execute ( 'UPDATE SurveyAnswers SET Hits=Hits+1 WHERE AnswerID=' + nAnswerID );

         e.moveNext ( );
      }

      // note that setting cookie here assumes I am buffering
      // the Reponse.Writes - cookies must be set before any
      // HTML is sent to the client
      Response.Cookies ( sSurvey ) = "1";

      // I'm not setting the 'expires' on the cookie, so it'll go
      // away when the browser is closed. I just wanted to stop
      // the survey incrementing if the page refreshed.

      // process some user inputs last
      PostSurvey ( sSurvey, nProcessSurvey );

      DisplaySurveyResults ( sSurvey );
   }
   else
   {
      // =======================
      // display survey form
      // =======================

      // the form container
      Out ( '<form action="' + Request.ServerVariables ( 'SCRIPT_NAME' ) + '" method="post">' );

      // add a hidden input so that form.count isn\'t 0 if none selected
      Out ( '<input type="hidden" name="_hidden_" value=1>' );

      // display some user inputs first
      PreSurvey ( sSurvey, nDisplayInputs );

      // get the questions from the database
      DBGetRecords ( 'SELECT Question FROM SurveyQuestions WHERE Survey=\'' + sSurvey + '\' ORDER BY Question;' );

      if ( oRecordSet.EOF )
      {
         Out ( 'No questions were found for survey "' + sSurvey + '"<p>' );
         return;
      }

      // store the questions in an array
      var sIntQuestions = new Array;
      var nQuestions = 0;

      while ( !oRecordSet.EOF )
      {
         sIntQuestions [ nQuestions++ ] = "" + oRecordSet ( 0 );
         oRecordSet.MoveNext ( );
      }

      // now loop through the questions
      for ( var nQuestion=0; nQuestion<nQuestions; nQuestion++ )
      {
         var sIntQuestion = sIntQuestions [ nQuestion ];

         // slice off chars used for sorting
         var sQuestion = sIntQuestion.slice ( 2 );

         // get the answers from the database
         DBGetRecords ( 'SELECT AnswerID,Answer,AnswerType FROM SurveyAnswers WHERE Question=\'' + sIntQuestion + '\' ORDER BY Answer;' );

         Out ( '<h5>' + sQuestion + '</h5>' );

         while ( !oRecordSet.EOF )
         {
            // get the answer ID
            var nAnswerID = oRecordSet ( 0 ) - 0;

            // and the answer text
            var sIntAnswer = "" + oRecordSet ( 1 );

            // slice off chars used for sorting
            var sAnswer = sIntAnswer.slice ( 2 );

            var sAnswerType= "" + oRecordSet ( 2 );

            switch ( sAnswerType )
            {
            case 'radio':
               Out ( '<input type="radio" name="' + sIntQuestion + '" value="' + nAnswerID + '"> ' + sAnswer );
               break;

            default:
               break;
            }

            Out ( '<br>' );

            // get next answer
            oRecordSet.MoveNext ( );
         }
      }

      // display some user inputs last
      PostSurvey ( sSurvey, nDisplayInputs );

      Out ( '<p><input type="submit" value="Submit answers">' );
      Out ( '</form>' );
   }

   // release the connection ASAP
   DBReleaseConnection ( );
}

// ============================================
// display the results of the named survey
// ============================================
function DisplaySurveyResults ( sSurvey )
{
   // now display all the answers to the survey.
   if ( PreSurvey ( sSurvey, nDisplayAnswers ) )
   {
      // the last question we displayed
      var sLast = "";

      // get all the selected answers, sorted by question and hits
      DBGetRecords ( 'SELECT SurveyAnswers.Question,Answer,Hits FROM SurveyAnswers INNER JOIN SurveyQuestions ON SurveyQuestions.Question=SurveyAnswers.Question WHERE Survey=\'' + sSurvey + '\' AND Hits>0 ORDER BY SurveyAnswers.Question,Hits DESC;' );

      var fScale;

      while ( !oRecordSet.EOF )
      {
         // display question when it changes
         var sIntQuestion = "" + oRecordSet ( 0 );

         // slice off chars used for sorting
         var sQuestion = sIntQuestion.slice ( 2 );

         // get answer
         var sIntAnswer = "" + oRecordSet ( 1 );

         // slice off chars used for sorting
         var sAnswer = sIntAnswer.slice ( 2 );

         var nReaders = oRecordSet ( 2 ) - 0;

         if ( sQuestion != sLast )
         {
            Out ( '<h5>' + sQuestion + '</h5>' );
            sLast = sQuestion;

            Out ( '<font color="red">"' + sAnswer + '" was the top answer (' + nReaders + ' readers)</font><br>' );

            fScale = 300.0 / nReaders;
         }
         else
         {
            Out ( '"' + sAnswer + '" was chosen by ' + nReaders + ' readers<br>' );
         }

         Out ( '<img src="images/Dot.gif" height="8" width="' + (nReaders * fScale) + '"><br>' );

         oRecordSet.MoveNext ( );
      }
   }

   PostSurvey ( sSurvey, nDisplayAnswers );
}
%>

utils/Database.asp

<%
// ============================================
// NOTE: all source code downloaded from CoverYourASP was written by
// James Shaw (unless stated otherwise), and is copyright (c) 2000-2002
// by James Shaw. You can use the code for any purpose, but do not
// publish or distribute the content in any way.
//
// See http://CoverYourASP.com/Legal.asp for up-to-date details.
// ============================================

// globals
var oConnection;
var oRecordSet;

// enums

// Connection.State and Recordset.State property
var adStateClosed = 0;         // the object is closed.
var adStateOpen = 1;             // the object is open.
var adStateConnecting = 2;   // the object is connecting.
var adStateExecuting = 4;      // the object is executing a command.
var adStateFetching = 8;         // the rows of the object are being fetched.

// Recordset.Cursor property
var adOpenUnspecified = -1;   // does not specify the type of cursor.
var adOpenForwardOnly = 0;   // (default) a forward-only cursor, i.e. you get only one pass thru the data!
var adOpenKeyset = 1;         // can go in any direction, and as a bonus you'll see changes other users make.  EXPENSIVE!
var adOpenDynamic = 2;      // as Keyset, but also you can see additions/deletions other users make.  EXPENSIVE!
var adOpenStatic = 3;         // can go in any direction, but read-only.

// Recordset.LockType property
var adLockUnspecified = -1;   // does not specify a type of lock.
var adLockReadOnly = 1;      // (default) guess!
var adLockPessimistic = 2;      // guaranteed to work
var adLockOptimistic = 3;      // records locked only when you call Update. fingers crossed
var adLockBatchOptimistic = 4;// required for batch update mode

var adCmdUnspecified = -1;   // Does not specify the command type argument.
var adCmdUnknown = 8;      // Default. Indicates that the type of command in the CommandText property is not known.
var adCmdText = 1;            // a textual definition of a command or stored procedure call.
var adCmdTable = 2;            // a table name whose columns are all returned by an internally generated SQL query.
var adCmdStoredProc = 4;      // a stored procedure name.
var adCmdFile = 256;            // a persisted Recordset.
var adCmdTableDirect = 512;   // a table name whose columns are all returned.

// SchemaEnum - specifies the type of schema Recordset to be retrieved by the OpenSchema method
var adSchemaTables = 20;      // returns the tables
var adSchemaForeignKeys = 27   // returns the foreign keys (relationships)
// ============================================
// example usage:
//      DBInitConnection ( );
//
//      DBGetRecords ( "SELECT * FROM Somewhere" );
//
//      ...use oRecordSet
//
//      DBReleaseRecords ( );      // optional step
//
//      DBGetRecords ( "SELECT * FROM SomewhereElse" );
//
//      ...use oRecordSet
//
//      DBReleaseRecords ( );      // optional step
//
//      DBReleaseConnection ( );
// ============================================

// ============================================
// initializes database variables for first use on page - leave it to the
// last possible second before calling this function
// ============================================
function DBInitConnection ( )
{
   // don't open it again if already opened!
   if ( oConnection != undefined )
      return;

   // don't bother trying to open if path is below SSI folders
   if ( -1 != sDBPath.indexOf ( '\\utils\\' ) || -1 != sDBPath.indexOf ( '\\include\\' ) )
      return;

   // you can open Recordset objects without a Connection object, but
   // it's far less efficient if you are opening multiple Recordsets.
   //
   // if you don't create a Connection object ADO creates a new one for
   // each new Recordset.Open, even if you use the same connection string.
   oConnection = Server.CreateObject( 'ADODB.Connection' );

   try
   {
      // open the database, catching any errors that occur
      oConnection.Open( sConnectionString );
   }
   catch ( e )
   {
      // display error message, and send email
      DatabaseException ( e );

      // quit running the script completely
      Response.End ( );
   }

   // create a Recordset
   oRecordSet = Server.CreateObject( 'ADODB.Recordset' );
}

// ============================================
// tidies up after DBInitConnection
// ============================================
function DBReleaseConnection ( )
{
   // don't release the connection if not connected!
   if ( oConnection == undefined )
      return;

   // close and delete the Recordset object
   DBReleaseRecords ( );

   oRecordSet = undefined;

   // Don't call Close if the Recordset failed to Open properly, i.e. its
   // State is still adStateClosed (0)
   if ( oConnection.State != adStateClosed )
      oConnection.Close();

   oConnection = undefined;
}

// ============================================
// executes the passed in SQL statement and returns a read-only
// forward-only oRecordSet object
// ============================================
function DBGetRecords ( sSQL )
{
   // if the Recordset is already open, close it
   DBReleaseRecords ( );

   // I could use oRecordSet = oConnection.Execute( sSQL ) here
   // but then I will always get back a read-only, forward-only cursor.
   // (admittedly this is the most used type, but still)

   // use oRecordSet.Open and I have far more control. For details
   // read the definitions of the enums at the top of this file.

   //Out ( sSQL );Response.Flush();

   try
   {
      // remember that this can fail if passed garbage, and hence the
      // Recordset will remain closed, State == adStateClosed
      if ( oConnection )
         oRecordSet.Open ( sSQL, oConnection, adOpenForwardOnly, adLockReadOnly );
   }
   catch ( e )
   {
      // display error message, and send email
      DatabaseException ( e );

      // quit running the script completely
      Response.End ( );
   }
}

// ============================================
// tidies up after DBGetRecords
// ============================================
function DBReleaseRecords ( )
{
   // when you have finished with an open Recordset object, call the
   // Close method to release its resources. You can call Open again.

   // Don't call Close if the Recordset failed to Open properly, i.e. its
   // State is still adStateClosed
   if ( oRecordSet != undefined && oRecordSet.State != adStateClosed )
      oRecordSet.Close();
}

// ============================================
// display exception message, but strip out database path if necessary
// ============================================
function DatabaseException ( e )
{
   Out ( '<table bgcolor="#ff0000" cellpadding="20"><tr><td>' );

      Out ( '<h4><font color="white">An error has occured while connecting to the database:</font></h4>' );

      var sMessage = e.description;

      // strip out the database path if present
      var nStart = sMessage.indexOf ( sDBPath )

      if ( -1 != nStart )
         sMessage = sMessage.slice ( 0, nStart ) + '[database path]' + sMessage.slice ( nStart + sDBPath.length );

      Out ( '<h4>&nbsp;&nbsp;&nbsp;"' + sMessage + '"</h4>' );

      Out ( '<h4><font color="white">Don\'t despair - this problem is probably well-documented in my <a href="http://CoverYourASP.com/Trouble.asp"><font color="white">trouble-shooting</font></a> section.</font></h4>' );

   Out ( '</td></tr></table>' );

   // make up the message body
   var sBody = 'The file "' + Request.ServerVariables ( "URL" ) + '?' + Request.QueryString ( ) + '" generated a database error\n\n';

   sBody += 'Referrer: "' + Request.ServerVariables ( "HTTP_REFERER" ) + '".\n';
   sBody += 'Browser: "' + Request.ServerVariables ( "HTTP_USER_AGENT" ) + '".\n';
   sBody += 'IP address: "' + Request.ServerVariables ( "REMOTE_ADDR" ) + '".\n';

   var dateToday = new Date();

   sBody += 'Time: "' + dateToday.getHours() + ':' + dateToday.getMinutes() + '".\n';

   sBody += sMessage;

   // send the email
   SendEmail ( 'Database.Exception', '[email protected]' + sHostDomain, '', 'Reporting exception', sBody );
}

// ============================================
// are we using Jet engine db, or SQL server?
// ============================================
var bUsingJet;

function DBIsJet ( )
{
   // for efficiency, only work out if which I'm using
   // the first time I'm used on a page.
   if ( bUsingJet == undefined )
      bUsingJet = ( -1 != sDBDriver.indexOf ( '.Jet.' ) );

   return bUsingJet;
}

// ============================================
// wrap date in relevant delimeters depending on db engine
// ============================================
function DBWrapDate ( sDate )
{
   return ( DBIsJet ( ) ? '#' + sDate + '#' : '\'' + sDate + '\'' );
}

// ============================================
//
// ============================================
function DBIsNull ( )
{
   return ( DBIsJet ( ) ? 'Is Null' : '= null' );
}

// ============================================
// stores dropdown lists in Application variables for use with foreign keys
// ============================================
function DBGatherForeignKeys ( )
{
   if ( !Application ( 'GatheredForeignKeys' ) )
   {
      DBInitConnection ( );

      bDebug = true;

      oRecordSet = oConnection.OpenSchema ( adSchemaForeignKeys );

      var nFields = oRecordSet.Fields.Count;
      var bHeaders = false;

      var sRefTables = new Array;
      var sRefColumns = new Array;
      var sForeignTables = new Array;
      var sForeignColumns = new Array;
      var nForeign = 0;

      while ( !oRecordSet.EOF )
      {
         if ( IsDebug ( ) )
         {
            if ( !bHeaders )
            {
               Out ( '<table border="1"><tr>' );

               for ( i=0; i<nFields; i++ )
                  Out ( '<td>' + oRecordSet.Fields ( i ).Name + '</td>' );

               Out ( '</tr>' );

               bHeaders= true;
            }

            Out ( '<tr>' );

            for ( i=0; i<nFields; i++ )
               Out ( '<td>' + oRecordSet ( i ) + '</td>' );

            Out ( '</tr>' );
         }
      
         sRefTables [ nForeign ] = '' + oRecordSet ( 'FK_TABLE_NAME' );
         sRefColumns [ nForeign ] = '' + oRecordSet ( 'FK_COLUMN_NAME' );
         sForeignTables [ nForeign ] = '' + oRecordSet ( 'PK_TABLE_NAME' );
         sForeignColumns [ nForeign++ ] = '' + oRecordSet ( 'PK_COLUMN_NAME' );

         oRecordSet.MoveNext  ( );
      }

      if ( bHeaders )
         DebugOut ( '</table>' );

      for ( i=0; i<nForeign; i++ )
      {
         DBGetRecords ( 'SELECT * FROM ' + sForeignTables [ i ] );

         try
         {
            var sList = '<select name="' + sRefColumns [ i ] + '">';
            var sForeignColumn = sForeignColumns [ i ];

            while ( !oRecordSet.EOF )
            {
               // I assume that the second field is
               // the one to show in dropdown list
               sList += '<option value="' + oRecordSet ( sForeignColumn ) + '">' + oRecordSet ( 1 ) + '</option>';

               oRecordSet.MoveNext  ( );
            }

            sList += '</select>';

            Application ( sRefTables [ i ] + ':' + sRefColumns [ i ] ) = sList;

            DebugOut ( '<p>Created ' + sRefTables [ i ] + ':' + sRefColumns [ i ] );
            DebugOut ( '<p>' + sRefColumns [ i ] + '=' + sForeignTables [ i ] + ':' + sForeignColumn + ' output:'+ Server.HTMLEncode ( sList ) + sList );
         }
         catch ( e )
         {
            DebugOut ( '<p>Failed to create dropdown list for ' + sRefTables [ i ] + ':' + sRefColumns [ i ] );
         }
      }

      DBReleaseConnection ( );

      Application ( 'GatheredForeignKeys' ) = true;
   }
}

// ============================================
// display (not editable) recordset column value
// ============================================
function DBDisplayValue ( oRecordSet, sTableName, nColumn )
{
   var sColumnName = oRecordSet.Fields ( nColumn ).Name;
   var oValue = oRecordSet ( nColumn );

   // get dropdown list if a foreign key
   var sHTML = Application ( sTableName + ':' + sColumnName );

//   DebugOut ( '<p>Application (  ' + sTableName + ':' + sColumnName + '=' + sHTML );

   if ( sHTML )
   {
      // disable control
      var nIndex = sHTML.indexOf ( ' name' );

      if ( nIndex != -1 )
         sHTML = sHTML.slice ( 0, nIndex ) + ' disabled' + sHTML.slice ( nIndex );

      // place 'selected' in the correct spot
      var nIndex = sHTML.indexOf ( ' value="' + oValue );

      if ( nIndex != -1 )
         sHTML = sHTML.slice ( 0, nIndex ) + ' selected' + sHTML.slice ( nIndex );
   }
   else
   {
      // show prettier dates
      if ( oValue.Type == 7/*date*/ )
         sHTML = FormatDateDMY ( oValue );
      else
         sHTML = "" + Server.HTMLEncode ( '' + oValue );

      // for brevity show the first x characters only
      if ( sHTML.length > 35 )
         sHTML = sHTML.slice ( 0, 35 ) + '...';
   }

   return sHTML;
}

// ============================================
// display editable recordset column value
// ============================================
function DBEditValue ( oRecordSet, sTableName, nColumn )
{
   var sColumnName = oRecordSet.Fields ( nColumn ).Name;
   var oValue = oRecordSet ( nColumn );

   // get dropdown list if a foreign key
   var sHTML = Application ( sTableName + ':' + sColumnName );

//   DebugOut ( '<p>Application (  ' + sTableName + ':' + sColumnName + '=' + sHTML );

   if ( sHTML )
   {
      // place 'selected' in the correct spot
      var nIndex = sHTML.indexOf ( ' value="' + oValue );

      if ( nIndex != -1 )
         sHTML = sHTML.slice ( 0, nIndex ) + ' selected' + sHTML.slice ( nIndex );
   }
   else
   {
      // show prettier dates
      if ( oValue.Type == 7/*date*/ )
         sHTML = FormatDateDMY ( oValue );
      else
         sHTML = "" + Server.HTMLEncode ( '' + oValue );

      sHTML = '<input type="text" name="' + sColumnName + '" size="45" value="' + sHTML + '">';
   }

   return sHTML;
}

// ============================================
// return value with ' replaced by SQL-safe ''
// ============================================
function DBEncode ( sValue )
{
   return sValue.replace ( /\'/g, '\'\'' );
}
%>

Hopefully much of this is self-explanatory. If not, or if you see ways that I can improve the code, please drop me a line.

To see the source code for this page, click on the icon below.

Featured sponsor
My favorite resources


Qualify for Free Trade Magazines

Free subscriptions to industry leading publications for those who qualify!


New Proposal Kit Professional 5.1
Brand yourself as a top professional: create quotes and amazing proposals and get many legal documents free!

The latter saved me 3 times the purchase price on the first day I owned it!


See my source code
wherever you see this icon...

You can also download the entire site source code for FREE!


I share my content

Supporting ASPRSS

Do you need a quick and easy way to link to my articles? All the information you need is published with ASPRSS...

CoverYourASP Mugs, T-shirts, caps - even Boxer shorts...
I don't make a penny from these, but they're a lot of fun! Don't you need a new mouse mat?