Single Form Version II

Universal Related Popup Menus

by Robert Gravelle (blackjacques@musician.org)

In my last article about the single form URPMs, I spoke about a couple of possible enhancements that could be done to improve them, such as having the ability to store the list values and to populate the menus directly from a database. Since that time, I have received several emails from people wanting me to elaborate on how one could go about integrating these improvements into their pages. Rather than try to answer everyone individually, I decided to write another article to deal with all these issues.

This article is divided into two parts.  The first one covers some changes that I made to the JavaScript code, including how the menu's value properties are stored.  The second part deals with an ASP script I wrote to extract the list items from a database. It covers how to add the script to your page with minimal effort, followed by a line by line explanation of the code.  For most people, this last section can be avoided, unless you who want to customize the code, or just like to know how things work.

Part 1: The JavaScript Code

The first incarnation of the single form URPM did not keep track of menu values because my client only stored the text property. Since then, many people I heard from expressed their need to have the code sent to the server, rather than the description. To add the codes to the arrays, I stored literal arrays within each array element - in effect creating a 2 dimensional array. Most people familiar with JavaScript are used to defining arrays with a constructor - e.g.: var a = new Array() or Array( "dog", "cat", 4, 56.43 ). What is less commonly known, is that we can also create an array literal by enclosing the elements with square brackets ( [] ). Literal values are the ones you type directly into mathematical or string expressions, as opposed to data which is stored in a variable. For example 23 (an integer), 12.32E23 (a floating point), or 'flopsy the Hamster' (a string) are all examples of literals.  More complicated data types such as Regular Expressions and Arrays also support literals.

With respect to Objects, literals are a great time saver by allowing the programmer to create the Object and assign a value to it simultaneously. Whether you're defining a string or an Array of strings, unless you're waiting for some kind of special occasion, you should assign its data at the same time if you already know the value(s). To illustrate, let's say that I want to assign a name to a string for safe keeping. I would write the following line:

var name = "Fred";

I could do the same using the String constructor:

var name = new String("Fred");

Both are identical in terms of function, but the first is shorter. Similarly, if you wanted to assign the values of an array right away, there would be no real reason to go through the constructor ( that is, unless you like typing ). You would just assign them like so:

var a = [ "Fred", "Joe", "Mary" ]; //same as new Array("Fred", "Joe", "Mary");

WARNING: Array literals are not supported by really old browsers such as Netscape and IE 3 or below.  If you want to make everyone happy, use the constructor.

There are many times that you will want to assign the array elements one at a time because you don't how many or what kind of data types will be coming your way. Since I knew in advance exactly what I needed to store, I opted to use the more succinct array literal over the longer Array constuctor. I stored the text in the first element, and the value in the second one. Here is a comparison of the old and the new arrays:

Legend:
blue text: JavaScript
green text: modified code

OLD ARRAY:

//an empty array that will hold the final Objects.
var m            = new Array();
var manufacturer = new Array(); //list 1
var model        = new Array(); //list 2
var level        = new Array(); //list 3


 manufacturer[0]="ACURA";
  model[0]="CL";
   level[0]="2.2 PREMIUM";
   level[1]="2.3";
   level[2]="3.0";
   level[3]="";
  model[1]="EL";
   level[4]="PREMIUM";
   level[5]="SE";
   level[6]="SPORT";
   level[7]="";
  model[2]="INTEGRA";
   level[8]="GS";
   level[9]="GS-R";
   level[10]="LS";
   level[11]="RS";
   level[12]="SE";
   level[13]="TYPE-R";
   level[14]="";
  model[3]="";
 manufacturer[1]="ALFA ROMEO";
  model[4]="SEDAN LS";
   level[31]="LEVEL N/A";
   level[32]="";
  model[5]="SPIDER";
   level[33]="LEVEL N/A";
   level[34]="VELOCE";
   level[35]="";
  model[6]="";
  ...

NEW ARRAY:

//an empty array that will hold the final Objects.
var m            = new Array();
var manufacturer = new Array(); //list 1
var model        = new Array(); //list 2
var level        = new Array(); //list 3


 manufacturer[0]=["ACURA", "ACU"];
  model[0]="CL";   //no value
   level[0]=["2.2 PREMIUM", "2.2"];
   level[1]="2.3";
   level[2]="3.0";
   level[3]=[];    //"" still works too
  model[1]="EL";
   level[4]=["PREMIUM", "PRE"];
   level[5]="SE";
   level[6]=["SPORT", "SP"];
   level[7]=[];
  model[2]=["INTEGRA", "INT"];
   level[8]="GS";
   level[9]=["GS-R", "GRS"];
   level[10]="LS";
   level[11]="RS";
   level[12]="SE";
   level[13]=["TYPE-R", "TYR"];
   level[14]=[];
  model[3]=[];
 manufacturer[1]=["ALFA ROMEO", "AR"];
  model[4]=["SEDAN LS", "SLS"];
   level[31]=["LEVEL N/A", "NA"];
   level[32]=[];
  model[5]=["SPIDER", "SPI"];
   level[33]=["LEVEL N/A", "NA"];
   level[34]=["VELOCE", "VEL"];
   level[35]=[];
  model[6]=[];
  ...

My goal was to make adding the value property as easy as possible, so I only added values where the text is not the same. If it is, the script will not assign a value to the listbox, which will ultimately lead to the Web page sending the text property to the server instead. Since they are the same, it will make no difference from the database's point of view. Whether or not you need to always have a value will depend on what kind of text you are displaying in your lists, and what you plan on doing with the listboxes. Either way, both these arrays will work with the new code.

It didn't take much work to fit the script to the new arrays. All that was required was to check for the presence of an array in the initLists() function by testing to see if the data type is an object.  If it is, I call the O (Object) constructor with both a text and a value argument, otherwise, I substitute a null for the value, as in the old version: 

    for(var i=0; i<a.length;i++) {
      if (a[i].length) {
        //create a new Option with the appropriate submenu
        subMenu = arg == argLen ? null : m[oldSubI++];
        temp[temp.length] = typeof(a[i])== "object"  ?
                            new O(a[i][0], a[i][1], subMenu ) :
           	            new O(a[i] ,   null ,   subMenu ); 
      }
      else
      { ...
       

I made up the system of storing the list contents in individual arrays because I had a lot of difficulty dealing with a giant array of "O" Objects. While my system worked well for me, it did not make it any easier on others, like I thought it would. A few people who wrote asked me if I could go over how this works, so I will start over and see if I can't clarify things a bit here.

There are a total of 4 arrays declared at the top of the old vs. new array example above. The m array is used to store the final list linking information, and the other three are for each listbox. You don't have to intertwine the arrays for the script to work, but I laid them out that way so that I can easily see which items in one list correspond to which subcategories in the next list. The easiest way to go about setting up the arrays is to start with the base list. Start with an array index of 0 and set each menu item from start to finish ( see Step 1 below ). Now that you've got your base list, insert all the relevant subcategories between each entry, without indexes ( see Step 2 ). In my example, it's the model. Once you've done this, add an empty array element after the last subcategory ( see Step 3a ) and add the indexes, again starting from 0 ( see Step 3b ). The empty element is what tells the script to start a new subcategory. Then repeat the same process for all subcategories ( see Step 4 ). When you're done, you should have the same kind of layout as above.

Step 1: construct the base list

manufacturer[0]=["ACURA", "ACU"];
manufacturer[1]=["ALFA ROMEO", "AR"];
...

Step 2: Add the subcategories

manufacturer[0]=["ACURA", "ACU"];
  model[]="CL";   //no value
  model[]="EL";
  model[]=["INTEGRA", "INT"];
manufacturer[1]=["ALFA ROMEO", "AR"];
  model[]=["SEDAN LS", "SLS"];
  model[]=["SPIDER", "SPI"];
...

Step 3a: Insert an empty array after each subcategory group

manufacturer[0]=["ACURA", "ACU"];
  model[]="CL";   //no value
  model[]="EL";
  model[]=["INTEGRA", "INT"];
  model[]=[];
manufacturer[1]=["ALFA ROMEO", "AR"];
  model[]=["SEDAN LS", "SLS"];
  model[]=["SPIDER", "SPI"];
  model[]=[];
...

Step 3b: Insert indexes, starting form 0

manufacturer[0]=["ACURA", "ACU"];
  model[0]="CL";   //no value
  model[1]="EL";
  model[2]=["INTEGRA", "INT"];
  model[3]=[];
manufacturer[1]=["ALFA ROMEO", "AR"];
  model[4]=["SEDAN LS", "SLS"];
  model[5]=["SPIDER", "SPI"];
  model[6]=[];
...

Step 4: Repeat steps 2 and 3 for each subcategory

manufacturer[0]=["ACURA", "ACU"];
  model[0]="CL";   //no value
   level[0]=["2.2 PREMIUM", "2.2"];
   level[1]="2.3";
   level[2]="3.0";
   level[3]=[];    //"" still works too
  model[1]="EL";
   level[4]=["PREMIUM", "PRE"];
   level[5]="SE";
   level[6]=["SPORT", "SP"];
   level[7]=[];
  model[2]=["INTEGRA", "INT"];
   etc...
  model[3]=[];
manufacturer[1]=["ALFA ROMEO", "AR"];
  model[4]=["SEDAN LS", "SLS"];
   etc...
  model[5]=["SPIDER", "SPI"];
   etc...
  model[6]=[];
...

Part 2: Retrieving the list contents from a database

One of the weaknesses of using arrays to represent the list data is that they can be very time-consuming to maintain. The problem lies in the fact that the element indexes are always incremented by 1. As a result, in order to insert new items in the middle of the list, you have to adjust all the subsequent indexes accordingly. To get around this, I wrote a small application to update the lists from an Access database using Visual Basic. More recently, I adapted the original program to VBScript, to be used within an ASP page. I used these tools because people asked me specifically about them, and they are among some of the easiest to use. If your data needs to be updated more than once a month, you may want to consider this approach. Performance will take a slight hit because the script has to go to the database every time it sends the page, but this is almost always the case with any dynamically generated page.


USING THE SCRIPT

Using this script with your own Web page is really quite simple. In fact, it's a lot easier than the static version because now you have a lot less JavaScript to write. All you need to do is place the scripts in your page, create a query to extract the data in a specific format, and change a few line of code. If you are not happy with any aspects of the script, you'll have to delve into it to tweak it. That won't be as straight forward, but I'll tell you what everything does here, so you'll know where to look.

Once you've downloaded the zipped file, and extracted the contents to a folder, you should have a total of 10 files:

  1. A copy of this article and accompanying 3 image files.
  2. A sample ASP page.
  3. A sample Access database. 
  4. 2 JavaScript files. 
  5. 2 ".inc" files containing VBScript code.

In order to run the example, you'll need a server that can execute ASP scripts.  For testing purposes, I use Microsoft's Personal Web Server.  It was designed for testing small applications such as this and is very easy to use.  You can download it here for free.  The personal Web server is part of a suite of related Web development products. After you select your operating system, you have to download a small program which will take you through the steps to install it.  Be forewarned, the entire download file is about 23 megs, so make sure you have sufficient space on your hard drive. Maybe it's time to burn some of those mp3's to a CD?

To try it out, place the ASP page, database, and 2 .inc files in the root folder of the server.  In the PWS it's called wwwroot.  Then, bring up the ASP page in a browser via the server by typing the following in the address box:

http://localhost/URPM_sample.asp

Note that your server name may be different.  If your server is listening on a different port, you must append it to the server name, using a colon:

http://servername:8080/URPM_sample.asp

Once you're ready to integrate the script into your own page, you'll need the 4 script files ( 2 JavaScript and 2 VBScript ).  The first script is the getListData.inc file. It is ready to include in your page with an include server directive such as:

<!--#INCLUDE FILE="getListData.inc"-->

This line goes at the very top of your page ( even before the opening <HTML> tag ). If your server does not allow server side includes, just copy and paste the contents of the getListData.inc file directly into your Web page.  There are 2 pieces of information that you will have to add to the file for it to work properly: The name of your database and the name of the query to retrieve the data:

 '*SET THE FOLLOWING LINE TO YOUR DATABASE:----------------------------
 db = "vehicle lists '97.mdb"

 '*SET THE FOLLOWING LINE TO YOUR QUERY:-------------------------------
 query = "getLists"
 '---------------------------------------------------------------------

Then, insert the JavaScript files by using the src attribute

<SCRIPT Language="JavaScript" src="initLists.js">
<SCRIPT Language="JavaScript1.2" src="initListsII.js">

The reason that there are two JavaScript files is because one of them contains 1.2 JavaScript code.  Should a browser not recognize this version of JavaScript, it will simply ignore the script.

Finally, insert the showErrors.inc file somewhere in the document body. It really depends on where you want errors to be displayed.  You could even skip this part, if you don't care what happens if an error occurs, but be prepared to see some pretty obscure error messages, instead of your Web page!

Just like the old version, you still need to call the JavaScript functions when the page loads and from the onChange event of each menu. If you don't do this, the page will load fine, but the menus will be empty!   To put data in the lists, you need to call the initLists() function in the onLoad event, from the body tag:

<body onLoad="initLists(BaseArray, BaseMenu, 1stSubcategoryArray, 1stSubcategoryMenu, 
                        2ndSubcategoryArray, 2ndSubcategoryMenu)">

To refresh the list contents when the user selects an item, you need to call the relate function from the onChange event of each menu, except the last one, since it does not affect any other menus:

onChange="relate(this,m,1)"

The first argument is the listbox itself and never varies.  The second one is the array which contains the linked list items. It should never vary either, since this variable is hardcoded in the ASP script. The only argument that you have to worry about is the last one.  It denotes the depth of the list items in the m array, starting at 1 for the base list.  In the example page, 1 is the Manufacturer list, 2 is the Model list, and 3 is the Model Level list.

 

The Query:

The script extracts the data from the database by running the query you specified earlier, along with the database name, in the getListData.inc file. Most ASP scripts I have seen run the SQL code directly from the script, but I like the idea of having it run from within the database, much like a stored procedure. This approach offers several advantages, including making the script shorter and giving you the ability to write and test your query right in Access using the Query design screen.

Here is how my query looks when I run it in Access:

The query is laid out from the base category on the left to the innermost subcategory on the right. There are 2 columns per category. The first is the text that will appear in the listbox. It is also the name that will be used by the ASP script to assign the categories to JavaScript arrays. Since both the text and values are stored in the same variable, only one column name per category is needed. In order to successfully create the JavaScript variable, you have to follow the JS naming conventions. That means that it must start with a letter or underscore ("_"); subsequent characters can also be digits (0-9). Because JavaScript is case sensitive, letters include the characters "A" through "Z" (uppercase) and the characters "a" through "z" (lowercase). You also have to watch out for the following reserved words:

abstract
boolean
break
byte
case
catch
char
class
const
continue
debugger
default
delete
do
double
else
enum
export
extends
false
final
finally
float
for
function
goto
if
implements
import
in
instanceof
int
interface
long
native
new
null
package
private
protected
public
return
short
static
super
switch
synchronized
this
throw
throws
transient
true
try
typeof
var
void
volatile
while
with

If your column name follows the above rules and is not in the reserved word list, then you're in the clear. Otherwise, you will have to change the name in the query. There are two ways to do this. The first would be to precede the column name with the new name and a colon in the query design window. In the following example, the original tables' description column names are innapropriate because of the spaces:

The other way would be to insert the code directly into the select part of the SQL statement:

SELECT Manufacturer.[Manu Name] AS Manufacturer, Manufacturer.[Manufacturer ID], Model.[Mod Name] AS Model, Model.[Model ID], Level.[Lev Name] AS Level, Level.[Level ID]

The second column is the associated value property. Its name doesn't matter because it isn't used in the JavaScript

That's all there is to it.  With your query and scripts in place, you should be able to link up as many menus as you need.  Remember to view the page through the Web server, as the script will only run when it's interpreted by the server.

 

THE VBSCRIPT CODE

For those of you who want to customize the script further, here is a line by line explanation of what the ASP code does:

The first line: <%@ LANGUAGE="VBSCRIPT" %>, found at the top of the getListData.inc file, tells the server that we are using VBScript. ASP also supports JavaScript and PerlScript, but when dealing with Microsoft products, it's usually easiest to stick with their programming languages too.

A couple of lines down, you'll notice the "Option Explicit" phrase before any variables are declared. By inserting this code, we are telling the ASP parsing engine that we want to explicitly declare all our variables via the Dim statement. Using Option Explicit allows us to easily catch misspelled variable names - a problem which could spell trouble if we're not careful.

After I declare some variables, it says: "On Error Resume Next". Without this important line of code, the script would bomb if anything went wrong - even the most minute error would cause the server to display an ugly error message instead of the page. Something like:

Microsoft OLE dB Provider for ODBC Drivers error '80004005'


[Microsoft][ODBC Microsoft Access Driver] Could not find file '(unknown)'.


/request_form.asp, line 11

Since the script is only responsible for setting up the list boxes, we should still get the page to load, even if the menus don't initialize properly. This line tells the script interpreter to continue running the script until it's done. While there can be no way to know what the heck will come out, at least it won't crash and burn. Unlike the full Visual Basic language, VBScript does not offer any other options for error handling. You either forge on, or quit right there. I'll be getting back to how I handled errors a little later on.

Connecting to the Database:

There are two types of database connections in ASP: DSN and DSNLess. To establish a DSN connection, you have to create a dsn file, using the "ODBC Data Source Administrator" utility that comes with Microsoft's operating systems. The DSN file is just a regular text file containing information about the database. Once you have a file, you would connect to the database using the following code:

Set MyConn = Server.CreateObject("ADODB.Connection")
MyConn.Open "FILEDSN=c:\dsn\MyDB.dsn"

To connect to the database using a DSNLess connection, we would do the following, as I did in my script:

 Set MyConn  = Server.CreateObject("ADODB.Connection")
 MdbFilePath = Server.MapPath("vehicle lists '97.mdb")
 MyConn.Open "Driver={Microsoft Access Driver (*.mdb)}; DBQ=" & MdbFilePath & ";"

If all went well thus far, we now have a database from which to extract the list items. This is done by executing an SQL command, using the following code:

 Set cmd = Server.CreateObject("ADODB.Command")
 Set cmd.ActiveConnection = MyConn
 cmd.CommandText = "getLists"	'this is the name of the query in Access

 set RS = cmd.Execute

The next several lines define some variables which we will be using:

      'used to store the field values
      Dim lastID()
      ReDim lastID( RS.Fields.count / 2 )

      'used for keeping track of the JS array indexes
      Dim JSindex()
      ReDim JSindex( RS.Fields.count / 2 )

We only need enough elements for each menu level ( 3 in this case ), but since there are two columns for each of them, I had to divide the number of fields by 2. I could have used one variable for each field, but that wouldn't be very generic. VB does not allow us to set an array's size to a variable so we had to create an empty array and then redim it to the proper size - thus creating a variable length array. The lastID array stores the most current value of the Value columns in the recordset.  We know that we've reached a new item every time these no longer match the current record because they are sorted by the value columns, in ascending order.  The JSindex array holds the JavaScript array indexes.  There will be one for each JS array.  

The next bit of code creates the JavaScript array which will hold the list data:

      Response.write("<"+"SCRIPT Language='JavaScript'>" & vbCrLf)
      Response.write( vbTab & "var m     = new Array();" & vbCrLf)
'Define the JS arrays to the Table Field Names and initialize the indexes to 0 For i=0 To RS.Fields.count - 2 Step 2 lastID( i / 2 ) = RS(i+1) 'initialize to the first row ID's JSindex( i / 2 ) = 0 Response.write( vbTab & "var " & RS(i).name & " = new Array();" & vbCrLf ) Next

This loop iterates through every second field, stores the IDs and sets the JavaScript arrays to 0. We have to initialize the array elements because VBScript wouldn't know what to set them to. At the same time, the script writes out the JavaScript arrays, using the description column name as the name of array ( bold text ). That's why it is so important to use JavaScript safe names!

Another loop is then used to write out the first items in each of the JavaScript arrays.  Using loops allows the script to work with any number of menus:

	'write out the first row
	For i = 0 To RS.Fields.count - 2 Step 2
          writeJSArray RS, i, JSindex, false
 	Next

It calls a subroutine to do the actual writing, called writeJSArray:

      Sub writeJSArray(ByRef RS, ByVal i, ByRef JSindex, ByVal blankVal)
        Dim val

        If (blankVal) Then
          val = ""
        Else
          val = Q & RS(i) & Q & ", " & Q & RS(i+1) & Q
        End IF

        Response.write( vbTab & space(i) & RS(i)Name & "[" & JSindex(i/2) & "] = new Array(" & val & ");" & vbCrLf )

        JSindex(i/2) = JSindex(i/2) + 1	'increment the JS index
      End Sub

This is a sub, and not a function, because it does not return anything.  It takes 4 parameters: the recordset containing the data, the current column index, the current JavaScript array index, and a Boolean indicating whether or not we want an empty array.  To eliminate any confusion, I added the ByRef and ByVal attributes to each parameter.  ByRef tells the interpreter that we want to use a pointer to the actual value in memory.  ByVal denotes that we want to use a copy of the variable.  This distinction is an important one because variables passed ByRef can be changed by the sub or function, while those passed ByVal cannot.  We don't need to change the recordset, but objects are always passed by reference in VB and VBScript.  We do need to pass the JSindex by reference, because it is incremented by the sub routine. The first part of the sub sets the array's contents. If blankVal is false, it sets it to a blank string. Otherwise, it is set to the description and value fields respectively, with each of them surrounded by quotes. I used a variable to hold the double quote character to differentiate between the JavaScript output and the VBScript code. The "Response.write" line is what writes the data on the page. Again, it uses the description field to set the name of the JavaScript array. The last line increments the JavaScript array index. Here is what is produced by the loop, in the example:

manufacturer[0]=["ACURA", "ACU"];
  model[0]=["CL", "CL"];
   level[0]=["2.2 PREMIUM", "2.2"];

After we move the cursor to the next record we enter a large Do While loop, to process the remaining records.  The check for the end of file ( RS.EOF ) is not essential first time through the loop, so I could have used a post-check, but the standard pre-test loop tends to be safer. 

One of the most challenging aspects in writing this code was the fact that I used multiple MoveNext statements inside the same loop.  This can be dangerous because there is code which expects a valid record before the loop checks for the end of file marker.  After several attempts at restructuring the loop to avoid this, I felt that it was easier to follow the code the way it was.  To avoid a run time error, I checked for the end of file immediately after the MoveNext statement.  Ironically, it is this line of code that exits the loop, and not the main loop check!

        'get the last sub-category
        i = RS.Fields.count - 2	'set i to the last subcategory description
        While lastID(ubound(lastID) - 2) = RS(i-1)
	  writeJSArray RS, i, JSindex, false

          RS.MoveNext
          If RS.EOF Then Exit Do
        Wend

The loop checks the current value field of the second last menu against the stored one - that's why I subtract 1 from the i, and 2 from the ubound(lastID).  As long as they match, the script writes out the innermost menu array. In our example, it would be the following lines:

   level[1]=["2.3", "2.3"]; // the script always writes out both the text and the values
   level[2]=["3.0", "3.0"];

Once the fields no longer match, the script writes out empty JavaScript array elements ( [] ) until the previous field's value is equal to the stored one.  The new values are also stored for next time through the loop.

	'write out blank JS array elements
        For i = RS.Fields.count - 2 To 2 Step -2
          If RS(i-1) = lastID(i/2 - 1) Then
            Exit For
          Else
            'Write a blank value
            writeJSArray RS, i, JSindex, true

            lastID(i/2 - 1) = RS(i-1)  'store the id for comparison
          End if
        Next

The next bit of code, which is used to write out the next row, is identical to the code which precedes the main loop, except that this time i is set to itself because it already holds the correct value:

        'write the next row
        For i = i To RS.Fields.count - 2 Step 2
          writeJSArray RS, i, JSindex, false
 	Next

        RS.MoveNext

At the point that the script exits the loop, all of the JavaScript arrays have been populated, except for the last empty ones. This is because the loop exits immediately after writing out the innermost array elements.  This is then done with the following code:

      'finish the JS arrays
      For i = RS.Fields.count - 2 To 2 Step -2
        'Write a blank value
	writeJSArray RS, i, JSindex, true
      Next

Finally, we write out the closing JavaScript tag and clean up: 

      Response.write(vbCrLf & "</" & "SCRIPT>")

      RS.Close
      set RS = Nothing

      MyConn.Close
      set MyConn = Nothing

 

Error Handling

The following code, located in the showErrors.inc file, should be placed somewhere in the body of the Web page, to display a generic message to the user if any errors are encountered along the way. Used in conjunction with the "On Error Resume Next" statement, it allows the page to be displayed along with a more meaningful error message:

<%
      If Err.number Then	'display error message
        Response.write("<font color=red><p><b>The following error occurred while loading the listboxes:</p>" & vbCrLf)
        Response.write("<p>" & Err.number & ": " & Err.description & "</b></p></font>" & vbCrLf)
      End If
%>

I simply displayed the error number and description of the last error, but you could check for specific errors and display different messages depending on what happened. Also keep in mind that more than one error could occur along the way. Every time a new one is encountered, the Err Object is updated with the latest error details, and wipes out the old one in the process. To get around this, you might want to store all the errors in some sort of array and display the whole list. The Err object contains other useful properties you can access to gain more information about errors. I won't go into all of them here, as they can be found in any ASP documentation.

Future Enhancements:

Some of my JavaScript modifications went towards converting several simple arrays into one multi-dimensional Object array used to link the list items.  My main reason for doing this was because I found the "O" Objects' syntax difficult to master.  Having transferred the responsibility of populating the menu data to an ASP script, I now realize that I could bypass the extra step of creating individual arrays completely. You could argue that the code might not be as easy to read, but is this really important if the script is organizing the data?   Without looking at the source code, a little testing of your page would quickly tell you if the items are coming up properly.  Writing the code to accomplish this would be guaranteed to be a hair-pulling experience, but the improved performance would quite likely be worth it.

Download the full script (zipped file)

# # #

About the author: Rob Gravelle is both a successful Internet Systems Developer as well as the guitarist for the melodic metal band Ivory Knight. He received a B.A. Mus from Carleton University in 1995, followed by a diploma in Computer Programming from Algonquin College in 1998. He now designs large-scale systems for National organizations at Citizenship and Immigration Canada. You can reach him at blackjacques@musician.org.

Comments are welcome