Search the Catalog
VBScript in a Nutshell

VBScript in a Nutshell

By Paul Lomax, Matt Childs & Ron Petrusha
1st Edition May 2000
1-56592-720-6, Order Number: 7206
350 pages, $29.95

Chapter 2
Program Structure

In order to write VBScript, you have to know how to structure your code so that your scripts and programs execute properly. Each of the different types of VBScript that you write has different rules regarding its structure. We'll look at each of these in turn. We'll also examine the ways in which your host environment allows you to import VBScript code libraries, thus allowing you to create reusable code. Finally, we'll end the chapter with a discussion of VBScript usage to write class modules. First, though, it's important to cover the basic structures of VBScript that are relevant to all of the different script types: that script-level code calls code in individual functions or procedures.

Functions and Procedures

Functions and procedures (or subroutines) are central to modern programming. Dividing our script into subroutines helps us to maintain and write programs by segregating related code into smaller, manageable sections. It also helps to reduce the number of lines of code we have to write by allowing us to reuse the same subroutine or function many times in different situations and from different parts of the program. In this section, we'll examine the different types of subroutines, how and why they are used, and how using subroutines helps to optimize code.

Defining Subroutines: The Sub . . . End Sub Construct

The Sub...End Sub construct is used to define a subroutine; that is, a procedure that performs some operation but does not return a value to its calling program. Blocks of code defined as subroutines with the Sub...End Sub construct can be called in two ways:

Automatically
Some subroutines provide the means by which an object interfaces with the script. For instance, when a class defined with the Class...End Class construct is initialized, its Initialize event, if one has been defined, is executed automatically. For subroutines of this type, the routine's name can be constructed in only one way, as follows:

Sub objectname_event

For example, Sub Class_Initialize is a valid name of a subroutine. This type of subroutine is known as an event handler or an event procedure.

Referring to it by name
A subroutine can be executed at any time by referring to it by name in another part of the script. (For additional details, including the syntax required to call subroutines, see "Calling a Subroutine" later in this chapter.) While it is possible to execute event procedures in this way, this method is most commonly used to execute custom subroutines. Custom subroutines are constructed to perform particular tasks within a program, and can be assigned virtually any name that you like. They allow you to place code that's commonly used or that is shared by more than one part of a program in a single place, so that you don't have to duplicate the same code throughout your application.

Subroutine Names

There are several very straightforward rules to remember when giving names to your subroutines:

  • The name can contain any alphabetical or numeric characters and the underscore character.
  • The name cannot start with a numeric character.
  • The name cannot contain any spaces. Use the underscore character to separate words to make them easier to read.

For example:

Sub 123MySub( ) ' Illegal
Sub My Sub Routine( ) ' Illegal

both contain illegal subroutine names. However:

Sub MySub123( ) ' Legal
Sub MySubRoutine( ) ' Legal

are legal subroutine names.

Example 2-1 illustrates the use of a custom subroutine in a client-side script to contain code that is common to more than one part of an application. It provides a simple example of some common code that is placed in a custom subroutine. The web page in Example 2-1 contains three intrinsic HTML command buttons. But rather than handling the user's click of a particular button separately, each button's OnClick event procedure simply calls the ShowAlertBox routine. Had we not included the ShowAlertBox subroutine, which contains code common to all three event handlers in our web page, we would have had to create a script several times longer than the one shown in Example 2-1.

Along with showing how to use a custom subroutine to share code, Example 2-1 also demonstrates how to pass variables from one procedure to another, a topic discussed in greater depth in the section "Passing Variables Into a Subroutine" later in this chapter. In particular, the ShowAlertBox routine is passed the caption of the button on which the user has clicked so that it can display it in an alert box.

Example 2-1: Using a Custom Subroutine to Share Code
   Sub cmdButton1_OnClick
    Call ShowAlertBox(cmdButton1.Value)
   End Sub
 
   Sub cmdButton2_OnClick
    ShowAlertBox cmdButton2.Value
   End Sub
 
   Sub cmdButton3_OnClick
    ShowAlertBox cmdButton3.Value 
   End Sub
 
   Sub ShowAlertBox(strButtonValue)
    dim strMessage
    strMessage = "This is to let you know" & vbCrLf
    strMessage = strMessage & "you just pressed the button" & vbCrLf
    strMessage = strMessage & "marked " & strButtonValue
    Alert strMessage
   End Sub

Calling a Subroutine

In Example 2-1, you may have noticed that the cmdButton1_OnClick event procedure uses a different syntax to invoke the ShowAlertBox routine than the cmdButton2_OnClick and cmdButton3_OnClick procedures. The second form of the call to the ShowAlertBox function:

showAlertBox cmdButton2.Value

is currently the preferred method. Note that it is unclear that this is actually a call to a subroutine named ShowAlertBox. Presumably, ShowAlertBox could be a variable. In fact, in order to identify ShowAlertBox as a subroutine, we have to rely on a visual clue: it is followed by another variable on the same line of code. This assumes, of course, that the code is correct, and that we haven't inadvertently omitted an equal sign between two variables.

In contrast, invoking a procedure by using a Call statement like the following:

Call showAlertBox(Top.cmdButton1.Value)

makes the code much more readable. You may prefer using it for this reason.

The rules for calling procedures are quite simple. If you use the Call statement, you must enclose the argument list in parentheses. If you do not use Call, you cannot use parentheses unless you're passing a single variable. In this case, though, parentheses also cause the variable to be passed by value rather than by reference to the subroutine (for the meaning of by value and by reference, see the section "Passing Variables into a Subroutine" later in this chapter), a behavior that may have undesirable consequences.

Defining Functions: The Function . . . End Function Construct

As we've seen, subroutines created by the Sub...End Sub construct are used to manipulate data that is passed to them (assuming that the subroutine accepts parameters) or to perform some useful operation. However, subroutines have one major shortcoming: they don't return data, like the results of their manipulations or information on whether they were able to execute successfully.

It is possible for a subroutine to "return" a value by passing it an argument by reference (a topic discussed in the later section "Passing Variables into a Subroutine"). However, that has one major disadvantage: it requires that you declare a variable to pass to the subroutine, even if you're not concerned with that variable's value or with the value "returned" by the subroutine.

There's also an additional way that a subroutine can return a value: you can pass it the value of a script-level variable that is visible throughout your routine. For instance, we could use the following code fragment to create a subroutine that cubes any value that is passed to it as a parameter:

<SCRIPT LANGUAGE="vbscript" RUNAT="Server">
  dim cube       ' script-level variable
           
   Sub CubeIt(x)
      cube = x^3
   end sub
</SCRIPT>    

Another routine can then access the result with a code fragment like the following:

<%
Dim intVar
intVar = 3
CubeIt intVar
Response.Write cube 
%>

This approach, though, suffers from two limitations. First, it means that the script-level variable must remain in memory for the entire life of our script, even though the variable itself may be used only briefly, if at all. In most cases, this is a very minor concern, unless that variable is a large string or it's used on a particularly busy web server. Second, and much more important, it creates a variable that can be accessed and modified from anywhere within our script. This makes it very easy for a routine to accidentally modify the value of a variable that is used elsewhere in the script. The availability or unavailability of a variable within a particular procedure is called its scope. And in general, the variables in a well-designed application should have the least restrictive scope possible.

Through its support for functions, VBScript supports a much safer way of retrieving some value from a routine. Functions share many of the same characteristics as subroutines defined with the Sub...End Sub construct:

However, unlike subroutines, functions return some value to the calling procedure. This can be either the result of some manipulation that the function performs on the arguments supplied to it (as, for example, when a function is passed a number and returns its cube) or it can be some indicator (like True or False) that the function has completed its operation successfully. This makes functions ideal for such uses as storing the code for frequently used calculations and conversions.

Functions are defined by using the Function...End Function construct, and by placing the function's code between these two statements. The full form of the Function...End Function statements is:

Function functionname(argumentlist)
End Function

Defining a Function's Return Value

If you've used VB or VBA to create functions, you probably have used the As keyword to define the data type of the value returned by a function, as in the following statement:

Function CubeIt(ByVal x As Long) As Long

Since VBScript supports only the variant data type, though, the As keyword is not supported, and you don't have to worry about the data type returned by your custom function. All functions defined by the Function statement return data of type variant.

A function's argument list is defined in exactly the same way as a subroutine's: the list of arguments is separated by commas and is enclosed in parentheses.

So how do we have our function return a value to the calling procedure? Within the body of our function, we assign the value that we want our function to return to a variable whose name is the same as the name of the function, as illustrated by the following code fragment:

Function functionname(argumentlist)
   . . . some calculation or manipulation
   functionname = result of calculation or manipulation
End Function

This variable is automatically initialized through the use of the Function statement. This means that if you're accustomed to defining your variables before using them, and especially if you've included the Option Explicit statement in your script, you should not use the Dim statement to explicitly initialize the variable for the function's return value.

To implement our earlier CubeIt procedure as a function rather than a subroutine, we dispense with the need to define a global variable to hold the cube of the argument passed to the function and enormously simplify our code, as the following code fragment shows:

<SCRIPT LANGUAGE="vbscript" RUNAT="Server">
   Function CubeIt(x)
      CubeIt = x^3
   End Function
</SCRIPT>   
 
<%
Dim intVar
intVar = 3
Response.Write CubeIt(intVar)
%>

Once a custom function is correctly defined using the Function...End Function statement, it can be called just as if it were an intrinsic function that is built into the VBScript language. The function call itself can take either of two forms. The most common form involves using the function name and its argument list on the right side of an expression, and assigning its return value to a variable on the left side of the expression. For example, the most common way to call the CubeIt function is:

y = CubeIt(x)

This assigns the value returned by the CubeIt function to the variable x. Unlike a call to a subroutine, though, this means that the argument list, if one is present, must always be surrounded by parentheses. (If the function accepts no parameters, though, the opening and closing parentheses are typically still used, although they're not required.)

In some cases, you may not actually be concerned with a function's return value. This doesn't happen very often: usually, you call a function precisely in order to have it return some value, so ignoring its return value renders the function useless. Nevertheless, if you do want to discard a function's return value, you can call a function just like you would call a subroutine. For example:

Call CubeIt(x)

or:

CubeIt x

Example 2-2 provides a real-world example--a client-side script that converts inches to either millimeters or meters--that shows how functions are defined and called. Along with two event procedures, it contains a function, sngMetric, that has a single argument, strInches, which is a string containing the number of inches that the user has input into the form's text box. The function converts this value to a single precision number, multiplies by 27.3, and, by storing it to the variable sngMetric, returns the result. The cmdButton1_OnClick and cmdButton2_OnClick event handlers call the function as necessary, and pass the appropriate values to it. As you can see, the result returned by the sngMetric function is immediately displayed in a message box.

Example 2-2: Calling a Function and Returning a Result
<HTML>
 <HEAD>
  <SCRIPT LANGUAGE="vbscript">
  <!--
    Sub cmdButton1_OnClick
     Dim strImperial
     strImperial = txtText1.Value
     Alert CStr(sngMetric(strImperial)) & " mm" 
    End Sub
 
    Sub cmdButton2_OnClick
     Dim strImperial
     strImperial = txtText1.Value
     Alert CStr(sngMetric(strImperial)/1000) & " m" 
    End Sub
 
    Function sngMetric(strInches) 
     Dim sngInches
     sngInches = CSng(StrInches)
     sngMetric = sngInches * 27.3
    End Function
  -->
  </SCRIPT>
 </HEAD>
 
 <BODY BGCOLOR="white">
    Input Inches: <INPUT TYPE="text" NAME="txtText1">
    <INPUT TYPE="button" NAME="cmdButton1" VALUE="Show Millimeters">
    <INPUT TYPE="button" NAME="cmdButton2" VALUE="Show Meters">
 </BODY>
</HTML>

Passing Variables into a Subroutine

The ability to pass variables from one procedure to another is an important part of using custom procedures. It allows us to write custom "black box" routines that can behave differently depending on where the routine has been called from and also on the particular data values that the routine receives from the calling program.

The data is passed from a calling routine to a subroutine by an argument list. The argument list is delimited with commas and can contain any data subtypes, including objects and arrays. For instance, the following mySubRoutine procedure expects three arguments: intDataIn1, strDataIn2, and lngDataIn3:

Sub AnotherSubRoutine(  )
   some code. . . .
   mySubRoutine intvar1, strvar2, lngvar3
   more code that executes after mySubRoutine
End Sub
 
Sub mySubRoutine(intDataIn1, strDataIn2, lngDataIn3)
   code which uses incoming data
End Sub

When mySubRoutine is called from AnotherSubRoutine, it is passed three variables as arguments: intvar1, strvar2, and lngvar3. So as you can see, the names of variables passed in the calling routine's argument list do not need to match the names in the custom procedure's argument list. However, the number of variables in the two argument lists does need to match or a runtime error results.

Passing Parameters by Reference

If you're accustomed to programming in VB or VBA, you'll recognize the way that you pass arguments in VBScript. However, in Versions 1 and 2 of VBScript, this wasn't the case. Parameters could be passed only by value, and there was no support for passing parameters by reference.

In addition, because VBScript is so flexible in its use of data types, you must take care when building subroutines that use data passed into them. The variables designated in the custom subroutine's argument list are automatically assigned the data types of the calling program's argument list. If a custom subroutine attempts to perform some inappropriate operation on the data passed to it, an error results, as the following code fragment illustrates:

Sub AnotherSubRoutine(  )
   some code. . . 
   intVar1 = "Hello World"
   Call mySubRoutine (intvar1, strvar2, lngvar3)
   more code that executes after mySubRoutine
End Sub
 
Sub mySubRoutine(intDataIn1, strDataIn2, lngDataIn3)
   code that uses incoming data
   intResult = intDataIn1 * 10 'this will generate an error
End Sub

The custom subroutine mySubRoutine assumed that intDataIn1 would be an integer, but instead the calling program passed it a string variable, intVar1. Therefore VBScript automatically cast intData1 as a string. The subroutine then produces a runtime error when it attempts to perform multiplication on a non-numeric variable. As you can see, while loosely typed languages like VBScript have many advantages, one of their major drawbacks is the fact that you must be on your guard for rogue data at all times.

Note that you can pass an argument to a procedure either by reference or by value. By default, arguments are passed by reference. By reference means that the calling routine passes the called function or subroutine a pointer to the argument (that is, its actual address in memory). As a result, any modifications made to the variable are reflected once control returns to the calling routine. The ASP code in Example 2-3 illustrates passing a variable by reference. The variable x is initially assigned a value of 10 in the DoSubroutine procedure. This value is then changed to 100 in the CallAnotherSub procedure. When control returns to the DoSubroutine procedure, the value of x remains 100 because the variable was passed by reference to CallAnotherSub.

Example 2-3: Passing a Variable by Reference
<SCRIPT LANGUAGE="VBScript" RUNAT="Server">
Sub DoSubroutine(  )
   Dim x
   x = 10
   Response.Write "In DoSubroutine, x is " & x & "<P>"
   CallAnotherSub x
   Response.Write "Back in DoSubroutine, x is " & x & "<P>"
End Sub
 
Sub CallAnotherSub(ByRef var1)
   var1 = var1^2
   Response.Write "In CallAnotherSub, var1 is " & var1 & "<P>"  
End Sub
</SCRIPT>
 
About to call DoSubroutine <P>
<%
   DoSubroutine
%>

Note that the Sub statement for CallAnotherSub explicitly indicates that its single parameter, var1, is to be passed by reference because of the ByRef keyword. Since this is the default method of passing parameters, however, the keyword could be been omitted. The statement:

Sub CallAnotherSub(ByRef var1)

is identical to:

Sub CallAnotherSub(var1)

On the other hand, by value means that the calling routine passes the called function or subroutine a copy of the variable. This means that any changes to the variable's value are lost when control returns to the calling program. The ASP code in Example 2-4 illustrates passing a variable by value. As was also true in Example 2-3, the variable x is initially assigned a value of 10 in the DoSubroutine procedure. This value is then changed to 100 in the CallAnotherSub procedure. When control returns to the DoSubroutine procedure, the value of x reverts back to 10 because the variable x was passed by value to CallAnotherSub.

Example 2-4: Passing a variable by value
<SCRIPT LANGUAGE="VBScript" RUNAT="Server">
Sub DoSubroutine(  )
   Dim x
   x = 10
   Response.Write "In DoSubroutine, x is " & x & "<P>"
   CallAnotherSub x
   Response.Write "Back in DoSubroutine, x is " & x & "<P>"
End Sub
 
Sub CallAnotherSub(ByVal var1)
   var1 = var1^2
   Response.Write "In CallAnotherSub, var1 is " & var1 & "<P>"  
End Sub
</SCRIPT>
 
About to call DoSubroutine <P>
<%
   DoSubroutine
%>

Note that the Sub statement for CallAnotherSub explicitly indicates that its single parameter, var1, is to be passed by value because of the ByVal keyword. This is necessary, since otherwise the variable would have been passed by reference.

There is one additional way to pass an argument by value to a procedure that involves overriding the explicit or default ByRef keyword. For example, if we had called the CallAnotherSub procedure in Example 2-3 with the following syntax:

   CallAnotherSub(x)

x would have been passed by value to the routine rather than by reference. The parentheses, in other words, cause an argument to be passed by value to a routine that is expecting to receive a parameter by reference. (Note that the converse does not work: parentheses do not cause an argument to be passed to reference to a routine that is expecting to receive a parameter by value.) This is a subtle difference that you should be aware of when passing parameters to procedures, since it can have unintended consequences.

Exiting a Routine with the Exit Statement

Ordinarily, when you call a function or a subroutine, all code between the initial Function or Sub statement and the concluding End Function or End Sub statement is executed. In some cases, though, you may not want all of a routine's code to be executed.

For example, imagine a situation in which you only want to execute a subroutine if a particular condition is met. One way of implementing this in your code is to test for the condition before calling the subroutine, as follows:

. . . some code
If condition Then
  Call MySubRoutine(  )
End if
. . . more code

However, if you call the routine from multiple locations in your code, and you want to apply this test to each call, you'll have to include this control structure at every place in the script in which you call the subroutine. To avoid this redundant code, it's better to call the subroutine regardless of the condition, and to place the test within the subroutine. One way of doing this is as follows:

Sub MySubRoutine(  )
   If condition then
      . . . all our subroutine code
   End if
End Sub

This is all well and good, and quite legal. However, in a large and complex subroutine, the End If statement becomes visually lost, especially if there are several conditions to be met. The preferred alternative is the Exit Sub and the Exit Function statements, which are used with the Sub . . . End Sub and Function . . . End Function constructs, respectively. Our conditional test at the beginning of a subroutine then appears as follows if we use the Exit Sub statement:

Sub MySubRoutine(  )
 If Not condition Then
   Exit Sub
 End if
 . . . all our subroutine code 
End Sub

Exit Sub and Exit Function immediately pass execution of the program back to the calling procedure; the code after the Exit statement is never executed. As you can see from the previous code fragment, the code is clean and clearly understandable. If the particular condition is not met, the remainder of the subroutine is not executed. Like the Exit Do and Exit For statements, any number of Exit Sub or Exit Function statements can be placed anywhere within a procedure, as the following code fragment demonstrates:

Function functionname(argumentlist)
 
 . . . some calculation or manipulation
 
   If condition1 Then
     functionname = result of calculation or manipulation
     Exit Function
   End If
 
 . . . perhaps some more code
 
   If condition2 Then
     functionname = result of calculation or manipulation
     Exit Function
   End If
 
End Function

Class Modules

Since VBScript 5.0, developers have been able to create classes to use in their scripts--a definite step along the road of object-oriented programming in VBScript. Writing classes with VBScript is very similar to writing COM objects with VB. Before we look at writing an actual class, let's go over some of the terminology so we are clear on what we are doing and what we are referring to.

A class is simply the template for an object. When you instantiate an object (that is, create an instance of a class) in code, VBScript makes a copy of the class for your use. All objects come from a class. Writing the class is simply a matter of creating a design for the objects that you want to use.

So naturally, it follows that an object is simply a copy of the class that you are making available to your program. You can make as many copies as you like for your use. The copies are temporary structures for holding information or creating interactions. When you are done with the objects, you can release them. If you need another one, you can instantiate another copy.

In VBScript, classes must be created in the scripts where you want to use them or they must be included in the scripts that use them. Since VBScript isn't compiled, you don't have the advantage of being able to write a set of VBScript COM classes that are usable outside of the scripts in which they're defined, or that can be easily accessed by programs and scripts written in other languages.

The Class Construct

You declare a class using the Class...End Class construct. The syntax of the Class statement is:

Class classname

where classname is the name you want to assign to the class. It must follow standard VBScript variable naming conventions.

Classes can contain variables, properties, methods, and events. How many of these and of what types is completely up to you. It is possible to have an object that has no properties or methods and supports only the two default events, but it won't be a very useful class.

To instantiate an object--that is, to create an instance of your class that you can use in your code--use the following syntax:

Set oObj = New classname

where oObj is the name you want to assign to your object variable (it again must follow standard VBScript variable naming conventions), and classname is the name of the class. The statement creates an object reference--that is, the variable oObj contains the address of your object in memory, rather than the object itself.

Class Variables

In addition to properties, methods (which are either functions or subroutines), and events (which are subroutines), the code inside a Class structure can include variable definitions (but not variable assignments). The variable definition can take any of the following forms:

Dim varName1 [, varName2...]
Private varName1 [, varName2...]
Public varName1 [, varName2...]

The variable name must once again follow standard VBScript variable naming conventions.

The Dim, Private, and Public keywords indicate whether the variable is accessible outside of the class. By default, variables are public--that is, they are visible outside of the Class...End Class structure. This means that the Dim and Public keywords both declare public variables, while the Private keyword declares a variable that's not visible outside of the class.

In general, it is poor programming practice to make a class variable visible outside of the class. There are numerous reasons for this, the most important of which is that you have no control over the value assigned to the variable (which is especially a problem when dealing with a weakly typed language like VBScript) and no ability to detect when the value of the variable has been changed. As a rule, then, all variables declared within your classes should be private.

Class Properties

Typically, class properties are used to "wrap" the private variables of a class. That is, to change the value of a private variable, the user of your class changes the value of a property; the property assignment procedure (called a Property Let procedure) handles the process of data validation and assigning the new value to the private variable. If the private variable is an object, use an object property assignment procedure (called a Property Set procedure) to assign the new property value to the private object variable. Similarly, to retrieve the value of a private variable, the user of your class retrieves the value of a property; the property retrieval procedure (called a Property Get procedure) handles the process of returning the value of the private variable.

Read-only properties (which wrap read-only private variables) have only a Property Get procedure, while write-only properties (which are rare) have only a Property Let or a Property Set procedure. Otherwise, properties have a Property Get procedure and either a Property Let or a Property Set procedure and are read-write.

The use of public properties that are available outside of the class to wrap private variables is illustrated in Example 2-5, which shows a simple class that defines a private variable, modStrType, and a two read-write properties, ComputerType and OperatingSystem, the latter of which is an object property. Normally, you would validate the incoming data in the Property Let and Property Set procedures before assigning it to private variables, although that hasn't been done here to keep the example as simple as possible.

Example 2-5: Using Properties to Wrap Private Variables
Class Computer
   
   Private modStrType
   Private oOS
 
   Public Property Let ComputerType(strType)
      modStrType = strType
   End Property
 
   Public Property Get ComputerType(  )
      ComputerType = modStrType
   End Property
 
   Public Property Set OperatingSystem(oObj)
      Set oOS = oObj
   End Property
 
   Public Property Get OperatingSystem(  )
      Set OperatingSystem = oOS
   End Property
 
End Class

Class Methods

Methods allow the class to do something. There is no magic to methods: they are simply subroutines or functions that do whatever it is you wish for the object to do. For example, if we created an object to represent a laptop computer in a company's inventory, then we would like to have a method that reports the laptop's owner. Example 2-6 shows a class with such a method.

Example 2-6: Creating a Class Method
Class LaptopComputer
private modOwner
 
Public Property Let CompOwner(strOwner)
   modOwner = strOwner
End Property
 
Public Property Get CompOwner(  )
   CompOwner = modOwner
End Property
 
Public Function GetOwner(  )
   GetOwner = modOwner
End Function
 
End Class

As with properties, you can use the Public and Private keywords to make methods available inside or outside of the class. In the previous example, the method and both properties are available outside of the class because they are declared as Public.

Note that in Example 2-6, the Property Get procedure performs the same functionality as the GetOwner method. This is quite common: you often can choose whether you want to implement a feature as a property or as a method. In this case, you could define both property procedures to be private; then the only way for anyone to get the owner information from the object would be to invoke the GetOwner method.

The GetOwner method is declared as a function because it returns a value to the calling code. You can write methods as subroutines as well. You would do this when the method that you are calling does not need to pass back a return value to the caller.

Class Events

Two events are automatically associated with every class you create: Class_Initialize and Class_Terminate. Class_Initialize is fired whenever you instantiate an object based on this class. The executing the statement:

Set objectname = New classname

causes the event to fire. You can use this event to set class variables, to create database connections, or to check to see if conditions necessary for the creation of the object exist. You can make this event handler either public or private, but usually event handlers are private--this keeps the interface from being fired from outside code. The general format of the Class_Initialize event is:

Private Sub Class_Initialize(  )
Initalization code goes here
End Sub

The Class_Terminate event is fired when the object goes out of scope, or when the object is set to Nothing. You can use this handler to clean up any other objects that might be opened or to shut down resources that are no longer necessary. Consider it a housekeeping event. This would be a good place to make sure that you have returned all memory and cleaned up any objects no longer needed. The general format of the Class_Terminate event is:

Private Sub Class_Terminate(  )
Termination code goes here
End Sub

Once again, the event handler can either be public or private, though ordinarily it's defined as private to prevent termination code from being executed from outside of the class.

Note that the Class_Terminate event is fired only when every instance of an object is destroyed. So, for instance, in the code fragment:

Dim oDog
Set oDog = New CDog
Set oDog2 = oDog
Set oDog3 = oDog2
'Do something here
Set oDog = Nothing
Set oDog2 = Nothing
Set oDog3 = Nothing

the event is fired only in response to the last statement, when oDog3 is set equal to Nothing. In this case, all three object variables hold references to the same object. It is only when the last reference is released that the event is fired.

The Script Level

We've seen that code can be organized into functions, subroutines, and classes, and that some subroutines (and an occasional function) can be executed automatically if they are event handlers and the event they handle fires. However, that seems to offer a relatively limited "hook" for script to run, nor does it seem to make it possible for a script to perform whatever initialization might be required in order for its event handlers to function successfully.

The script level is the answer to this dilemma. Script-level code--that is, code outside functions and subroutines--is executed automatically when the script loads or as the HTML on the page is parsed. The precise meaning of script-level code and the exact way in which code at script level is executed depends on the host environment for which the script is written. We'll examine these in turn.

Active Server Pages

In ASP, script-level code is synonymous with code in direct ASP commands--it is script that is preceded by the <% or <%= tags and terminated by the %> tag. (For details on how script is embedded within in ASP page, see Chapter 5, VBScript with Active Server Pages.) This code is executed automatically as the page's HTML is parsed.

It is also possible to include script-level code in <SCRIPT>...</SCRIPT> tags in an ASP. However, this is not genuine script-level code: aside from variable declarations, the order in which this code is executed is undefined.

Figure 2-1 shows the web page produced by Example 2-7, which illustrates script-level code in an Active Server Page. Note that although the variable x is defined and assigned a value at script level within the <SCRIPT> tag, the variable declaration is recognized but the variable assignment isn't. We can determine this because we've used the Option Explicit statement to require variable declaration, but the VBScript language engine did not raise an error when it first encountered the use of x on the second line after the <BODY> tag. But our assignment of 10 to x is not recognized, since the second line of our web page strongly suggests that x is uninitialized.

Example 2-7: Script-Level Code in an Active Server Page
 <% Option Explicit %>
<HEAD>
<TITLE>Script-level code in ASP</TITLE>
<SCRIPT LANGUAGE="VBScript" RUNAT="Server">
 
Private x
x = 10
 
Private Function Increment(lVar)
   lVar = lVar + 1
   Increment = lVar
End Function
 
Private Function Decrement(lVar)
   lVar = lVar - 1
   Decrement = lVar
End Function
 
</SCRIPT>
</HEAD>
<BODY>
<H2><CENTER>An Active Server Page</CENTER></H2>
The current value of x is <%= x %> <BR>
<%
   Dim y
   y = 20
   If x = 0 Then x = 10
%>
Value returned by Increment function: <%= Increment(x) %> <BR>
Value returned by Increment function: <%= Increment(x) %> <BR>
Value returned by Decrement function: <%= Decrement(x) %> <BR>
The value of <I>x</I> is now <%= x %>.
The value of <I>y</I> is <%= y %>.
</BODY>
</HTML>

Figure 2-1. The web page produced by Example 2-7

 

The conclusions to be drawn from Example 2-7 are unusually clear:

Windows Script Host

In a standard VBScript file for WSH, script-level code is any code that's not located in function, subroutine, or class definitions. This code is executed sequentially regardless of where it is located in the file. This produces some interesting possibilities for spaghetti code, as illustrated in Example 2-8, which provides a WSH equivalent of the ASP script in Example 2-7. This script produces the dialog shown in Figure 2-2. Although its program structure should not be duplicated in your own code, Example 2-8 illustrates that all script-level code is executed from the beginning of a VBScript file to the end.

Example 2-8: Script-Level Code in WSH
Option Explicit
 
Dim x
x = 10
 
Private Function Increment(lVar)
   lVar = lVar + 1
   Increment = lVar
End Function
 
Private Function Decrement(lVar)
   lVar = lVar - 1
   Decrement = lVar
End Function
 
Dim sMsg
sMsg = "The current value of x is " & x & vbCrLf
 
Dim y
y = 20
If x = 0 Then x = 10
 
sMsg = sMsg & "Value returned by Increment: " & Increment(x) & vbCrLf
sMsg = sMsg & "Value returned by Increment: " & Increment(x) & vbCrLf
sMsg = sMsg & "Value returned by Decrement: " & Decrement(x) & vbCrLf
sMsg = sMsg & "The value of x is now " & x & vbCrLf
sMsg = sMsg & "The value of y is " & y & vbCrLf
 
MsgBox sMsg

Figure 2-2. The dialog produced by the script in Example 2-8

 

If you're using a .wsf file with XML elements rather than a simple VBScript file, the same principles apply to code within the XML <job> and </job> tags. All code must be assigned to a particular job, and code assigned to a job is independent of and unrelated to code assigned to any other job. Within the <job> tag, all script-level code is executed sequentially, regardless of how many <script> tags are used to contain it.

Client-Side Scripts for MSIE

Script-level code in client-side scripts is found inside <SCRIPT> . . . </SCRIPT> tags but not inside of functions, subroutines, and classes. All script-level code is executed by MSIE, as Example 2-9 and Figure 2-3 show. In fact, script-level code can be used as a replacement for the Window_OnLoad event.

Example 2-9: Script-Level Code for MSIE
<SCRIPT LANGUAGE="VBScript">
Option Explicit
 
Dim x
x = 10
 
Private Function Increment(lVar)
   lVar = lVar + 1
   Increment = lVar
End Function
 
Private Function Decrement(lVar)
   lVar = lVar - 1
   Decrement = lVar
End Function
 
</SCRIPT>
<CENTER><H2>Welcome to our web page!</H2></CENTER>
<SCRIPT LANGUAGE="VBScript">
 
Document.Write "The current value of x is " & x & "<BR>"
 
Dim y
y = 20
If x = 0 Then 
   Document.Write "Initializing <I>x</I> in the second script block" & "<BR>"
   x = 10
End If
 
Document.Write "Value returned by Increment: " & Increment(x) & "<BR>"
Document.Write "Value returned by Increment: " & Increment(x) & "<BR>"
Document.Write "Value returned by Decrement: " & Decrement(x) & "<BR>"
Document.Write "The value of x is now " & x & "<BR>"
Document.Write "The value of y is " & y & "<BR>"
 
</SCRIPT>

Figure 2-3. The document produced by Example 2-9

 

Outlook Forms

Like Windows Script Host, Outlook executes all script-level code--not just variable declarations--when a form is loaded. In this case, script-level code corresponds closely to the Outlook form's Item_Open event procedure, which is fired when the form is opened.

Although you can use the script level for executable statements, in most cases it is preferable that you do not. Most Outlook form programming is event-driven programming; you should use events, including the Item_Open event, to handle variable initialization, and confine yourself to using script-level code to declare public and private variables.

Reusable Code Libraries

We've now discussed all of the basic principles of structuring VBScript programs, of constructing subroutines that can be used by various parts of your program, of building functions that perform calculations and other manipulations and pass the result back to the calling part of the program, and of creating classes that allow you to encapsulate real-world processes and objects. The emphasis on subroutines, functions, and classes, though, raises another issue--that of code reuse. Typically, classes are defined so that they can be used in a variety of applications. Similarly, many subroutines and functions are intended not only to reduce code in a single application, but also to be "black boxes" that can provide some service to multiple applications.

Although it generally hasn't been emphasized, and is dependent on the host platform, VBScript code can be reused on three of the four host platforms discussed here. The only platform that doesn't support code reuse is Outlook forms. That means that if you're scripting for WSH, ASP, or MSIE, you can develop code libraries that you import into your script.

Active Server Pages

You can import HTML, client-side script, or server-side script into an ASP file by using the #include server-side directive. Its syntax is:

<!-- #include PathType = sFileName -->

where PathType is one of the following keywords:

File
Indicates that sFileName is relative path from the current directory

Virtual
Indicates that sFileName is a full virtual path from the web server's root folder to the file to be included

and sFileName is the name of the file whose contents are to be included. Note that the #include directive must be surrounded by an HTML comment. The included file can consist of any combination of client-side script, server-side script, and HTML, as long as it is syntactically correct and is consistent with the script or HTML source at the point in the ASP page at which it is inserted.

Examples 2-10 and 2-11 illustrate one possible use of the #include directive. Example 2-10 shows the contents of classes.inc, an include file that contains a class definition to be used by the ASP application. Example 2-11 shows the ASP page that includes the file. Note that the include file consists entirely of script and is delimited with the HTML <SCRIPT> and </SCRIPT> tags (or the ASP <% and %> symbols). The ASP page in Example 2-11 inserts the contents of the include file in the HTML header, immediately after the </TITLE> tag.

Example 2-10: classes.inc, an Include File
<SCRIPT RUNAT="Server" LANGUAGE="VBScript">
   
Class CServer
 
   Private sName, sProtocol, sSoftware, sURL, lPort
   
   Public Property Get Name(  )
      Name = sName
   End Property      
 
   Public Property Get Port(  )
      Port = lPort
   End Property   
 
   Public Property Get Protocol(  )
      Protocol = sProtocol
   End Property
 
   Public Property Get URL(  )
      URL = sURL
   End Property
  
   Public Property Get Software(  )
      Software = sSoftware
   End Property
   
   Private Sub Class_Initialize(  )
      sName = Request.ServerVariables("SERVER_NAME")      
      lPort = Request.ServerVariables("SERVER_PORT")
      sProtocol = Request.ServerVariables("SERVER_PROTOCOL")
      sSoftware = Request.ServerVariables("SERVER_SOFTWARE")      
      sURL = Request.ServerVariables("URL")
      sSoftware = Request.ServerVariables("SERVER_SOFTWARE")
   End Sub   
   
End Class
 
</SCRIPT>

Example 2-11: An ASP Page That Uses an Include File

<% Option Explicit %>
<HTML>
<HEAD>
<TITLE>Including a Library File</TITLE>
<!-- #include File="Classes.inc" -->
</HEAD>
<BODY>
<H2>Welcome to our web site!</H2>
Here is information about our server: <P>
<%
   Dim oServer
   Set oServer = New CServer
%>
Name: <%= oServer.Name %> <BR>
Software: <%= oServer.Software %> <BR>
Port: <%= oServer.Port %> <BR>
Protocol <%= oServer.Protocol %> <BR>
Resource: <%= oServer.URL %> <BR>
</BODY>
</HTML>

The advantage of this approach is obvious: you can store common code in a separate file, making it available to all the ASP pages and all the ASP applications that require it. When that code requires modification, you need modify it only once since there is only a single copy in a single location, rather than having to search through all of your web pages to discover which ones incorporate the code.

Windows Script Host

Although standard Windows Script host files (i.e., .vbs files) do not allow you to import other files, WSH files with XML elements (i.e., .wsf files) do. Include another file by using the <SCRIPT SRC> tag, the syntax of which is:

<SCRIPT LANGUAGE="sLanguage" SRC="sFilename" />

where sLanguage is "VBScript" (or any other valid scripting language) and sFileName is either an absolute or a relative path to the file to be excluded. Note that using the <SCRIPT> tag requires that the .wsf file be structurally correct--that is, that the <PACKAGE> and <JOB> tags should be present.

The included file must be a standard WSH script file. It can contain only script, without any XML elements or tags. The include file is simply inserted into the .wsf file as if it were an intrinsic part of it.

Examples 2-12 and 2-13 illustrate the use of an include file. In this case, the code in Example 2-13 imports Lib.vbs, the include file shown in Example 2-12. Example 2-12 simply displays a message box displaying drives and their free space. To retrieve this information, it calls the GetFreeSpace function, which is located in the include file. This function returns a Dictionary object whose keys are drive names and whose values are the amount of free space available on the respective drive.

Example 2-12: Lib.vbs, an Include File
<%
Public Function GetFreeSpace(  )
 
Dim oDict, oFS, oDrives, oDrive
 
Set oDict = WScript.CreateObject("Scripting.Dictionary")
Set oFS = WScript.CreateObject("Scripting.FileSystemObject")
Set oDrives = oFS.Drives
For Each oDrive in oDrives
   If oDrive.IsReady Then
      oDict.Add oDrive.DriveLetter, oDrive.FreeSpace
   End If
Next
 
Set GetFreeSpace = oDict
 
End Function
%>

Example 2-13: A WSH Script That Uses an Include File

<PACKAGE>
<JOB ID=GetFreeSpace>
<SCRIPT LANGUAGE="VBScript" SRC="Lib.vbs" />
<SCRIPT>
Option Explicit
 
Dim oSpace, aDrives
Dim sMsg, sDrive
Dim iCtr
 
Set oSpace = GetFreeSpace(  )
aDrives = oSpace.Keys 
for iCtr = 0 to UBound(aDrives)
   sDrive = aDrives(iCtr)
   sMsg = sMsg & sDrive & ": " & oSpace(sDrive) & vbCrLf
next
 
MsgBox sMsg
</SCRIPT>
</JOB>
</PACKAGE>>

Note that files must be included on a per-job basis. In other words, if a .wsf file contains multiple jobs, you must have a separate <SCRIPT SRC> tag for each job in which you want to include a particular file. An include file applies only to the job in which it's been included.

Client-Side Scripts for MSIE

Like Windows Script Host, MSIE supports the <SCRIPT SRC> tag, which allows you to include script files. The syntax of the tag is:

<SCRIPT SRC="sURL " LANGUAGE="sLanguage"> </SCRIPT>

where sURL is the URL of the include file and sLanguage is the language in which the file designated by sURL is written. sLanguage can be "VBScript" or any other valid scripting language.

The include file is simply inserted into the text stream on the client at the point that the <SCRIPT SRC> tag is encountered, and both it and the original document are viewed by the VBScript language engine as a single document. The inserted file can contain only script, without any HTML tags.

Example 2-14 contains an include file and Example 2-15 an HTML document that includes a client-side script to validate data. Note that the IsBlank routine is visible to the web page, since the included script is considered part of the original document. Note also that Validate.inc contains only script, without any HTML tags, and that the source document contains a <SCRIPT SRC> tag immediately followed by a </SCRIPT> tag.

Example 2-14: Validate.inc, an Include File
Private Function IsBlank(sValue)
   If Trim(sValue) = "" Then
      IsBlank = True
   Else
      IsBlank = False
   End If
End Function

Example 2-15: A Web Page That Uses an Include File

<HTML>
<HEAD>
<TITLE>The SRC Attribute</TITLE>
<SCRIPT SRC="Validate.inc" LANGUAGE="VBScript" > </SCRIPT>
</HEAD>
<BODY>
<SCRIPT LANGUAGE="VBScript">
Private Function frmInfo_OnSubmit(  )
   Dim oFrm
   Set oFrm = Document.frmInfo
   If IsBlank(oFrm.txtName.Value) Or _
      IsBlank(oFrm.txtAddress.Value) Or _
      IsBlank(oFrm.txtCity.Value) Or _
      IsBlank(oFrm.txtState.Value) Then
         frmInfo_OnSubmit = False
         Alert "Please make sure the Name, Address, City " & _
               "State fields are not blank."
   End If
End Function
</SCRIPT>
<H3>Please enter the following data</H3>
<FORM METHOD=POST ACTION="Submission.asp" NAME="frmInfo">
Name: <INPUT TYPE="Text" NAME="txtName"> <P>
Address: <INPUT TYPE="Text" NAME="txtAddress"> <P>
City: <INPUT TYPE="Text" NAME="txtCity"> 
State <INPUT TYPE="Text" NAME="txtState">
Zip Code <INPUT TYPE="Text" NAME="txtZip"><P>
<INPUT TYPE="submit">
</BODY>
</HTML>

Back to: VBScript in a Nutshell


O'Reilly Home | O'Reilly Bookstores | How to Order | O'Reilly Contacts
International | About O'Reilly | Affiliated Companies

© 2001, O'Reilly & Associates, Inc.
webmaster@oreilly.com