|
|
|
|
VBScript in a NutshellBy Paul Lomax, Matt Childs & Ron Petrusha1st Edition May 2000 1-56592-720-6, Order Number: 7206 350 pages, $29.95 |
Chapter 2
Program StructureIn 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 Subconstruct 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 theSub...End Subconstruct 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...EndClassconstruct 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_eventFor 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.
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_OnClickCall ShowAlertBox(cmdButton1.Value)End SubSub cmdButton2_OnClickShowAlertBox cmdButton2.ValueEnd SubSub cmdButton3_OnClickShowAlertBox cmdButton3.ValueEnd SubSub ShowAlertBox(strButtonValue)dim strMessagestrMessage = "This is to let you know" & vbCrLfstrMessage = strMessage & "you just pressed the button" & vbCrLfstrMessage = strMessage & "marked " & strButtonValueAlert strMessageEnd SubCalling 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.Valueis 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
Callstatement 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
Callstatement, you must enclose the argument list in parentheses. If you do not useCall, 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 theSub...End Subconstruct 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 variableSub CubeIt(x)cube = x^3end sub</SCRIPT>Another routine can then access the result with a code fragment like the following:
<%Dim intVarintVar = 3CubeIt intVarResponse.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 Subconstruct:
- Through their optional argument list, they can be used to manipulate data that is passed to them.
- Since they can be called from anywhere in a script, they can be used to contain code that is shared by more than one part of the application.
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
TrueorFalse) 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 Functionconstruct, and by placing the function's code between these two statements. The full form of theFunction...End Functionstatements is:Function functionname(argumentlist)End Function
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 manipulationfunctionname = result of calculation or manipulationEnd FunctionThis variable is automatically initialized through the use of the
Functionstatement. This means that if you're accustomed to defining your variables before using them, and especially if you've included theOption Explicitstatement in your script, you should not use theDimstatement 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^3End Function</SCRIPT><%Dim intVarintVar = 3Response.Write CubeIt(intVar)%>Once a custom function is correctly defined using the
Function...End Functionstatement, 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 xExample 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_OnClickDim strImperialstrImperial = txtText1.ValueAlert CStr(sngMetric(strImperial)) & " mm"End SubSub cmdButton2_OnClickDim strImperialstrImperial = txtText1.ValueAlert CStr(sngMetric(strImperial)/1000) & " m"End SubFunction sngMetric(strInches)Dim sngInchessngInches = CSng(StrInches)sngMetric = sngInches * 27.3End 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, lngvar3more code that executes after mySubRoutineEnd SubSub mySubRoutine(intDataIn1, strDataIn2, lngDataIn3)code which uses incoming dataEnd SubWhen 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.
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 mySubRoutineEnd SubSub mySubRoutine(intDataIn1, strDataIn2, lngDataIn3)code that uses incoming dataintResult = intDataIn1 * 10'this will generate an errorEnd SubThe 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 xx = 10Response.Write "In DoSubroutine, x is " & x & "<P>"CallAnotherSub xResponse.Write "Back in DoSubroutine, x is " & x & "<P>"End SubSub CallAnotherSub(ByRef var1)var1 = var1^2Response.Write "In CallAnotherSub, var1 is " & var1 & "<P>"End Sub</SCRIPT>About to call DoSubroutine <P><%DoSubroutine%>Note that the
Substatement for CallAnotherSub explicitly indicates that its single parameter, var1, is to be passed by reference because of theByRefkeyword. 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 xx = 10Response.Write "In DoSubroutine, x is " & x & "<P>"CallAnotherSub xResponse.Write "Back in DoSubroutine, x is " & x & "<P>"End SubSub CallAnotherSub(ByVal var1)var1 = var1^2Response.Write "In CallAnotherSub, var1 is " & var1 & "<P>"End Sub</SCRIPT>About to call DoSubroutine <P><%DoSubroutine%>Note that the
Substatement for CallAnotherSub explicitly indicates that its single parameter, var1, is to be passed by value because of theByValkeyword. 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
ByRefkeyword. 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
FunctionorSubstatement and the concludingEnd FunctionorEnd Substatement 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 codeIf condition ThenCall MySubRoutine( )End if. . . more codeHowever, 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 codeEnd ifEnd SubThis is all well and good, and quite legal. However, in a large and complex subroutine, the
End Ifstatement becomes visually lost, especially if there are several conditions to be met. The preferred alternative is theExit Suband theExitFunctionstatements, which are used with theSub. . .End SubandFunction. . .End Functionconstructs, respectively. Our conditional test at the beginning of a subroutine then appears as follows if we use theExit Substatement:Sub MySubRoutine( )If Not condition ThenExit SubEnd if. . . all our subroutine codeEnd Sub
ExitSubandExitFunctionimmediately pass execution of the program back to the calling procedure; the code after theExitstatement 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 theExit DoandExit Forstatements, any number ofExitSuborExit Functionstatements can be placed anywhere within a procedure, as the following code fragment demonstrates:Function functionname(argumentlist). . . some calculation or manipulationIf condition1 Thenfunctionname = result of calculation or manipulationExit FunctionEnd If. . . perhaps some more codeIf condition2 Thenfunctionname = result of calculation or manipulationExit FunctionEnd IfEnd FunctionClass 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 Classconstruct. The syntax of theClassstatement is:Class classnamewhere 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:
SetoObj= Newclassnamewhere 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
Classstructure 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, andPublickeywords indicate whether the variable is accessible outside of the class. By default, variables are public--that is, they are visible outside of theClass...End Classstructure. This means that theDimandPublickeywords both declare public variables, while thePrivatekeyword 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 ComputerPrivate modStrTypePrivate oOSPublic Property Let ComputerType(strType)modStrType = strTypeEnd PropertyPublic Property Get ComputerType( )ComputerType = modStrTypeEnd PropertyPublic Property Set OperatingSystem(oObj)Set oOS = oObjEnd PropertyPublic Property Get OperatingSystem( )Set OperatingSystem = oOSEnd PropertyEnd ClassClass 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 LaptopComputerprivate modOwnerPublic Property Let CompOwner(strOwner)modOwner = strOwnerEnd PropertyPublic Property Get CompOwner( )CompOwner = modOwnerEnd PropertyPublic Function GetOwner( )GetOwner = modOwnerEnd FunctionEnd ClassAs with properties, you can use the
PublicandPrivatekeywords 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 asPublic.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 classnamecauses 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 hereEnd SubThe 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 hereEnd SubOnce 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 oDogSet oDog = New CDogSet oDog2 = oDogSet oDog3 = oDog2'Do something hereSet oDog = NothingSet oDog2 = NothingSet oDog3 = Nothingthe 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 theOptionExplicitstatement 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 xx = 10Private Function Increment(lVar)lVar = lVar + 1Increment = lVarEnd FunctionPrivate Function Decrement(lVar)lVar = lVar - 1Decrement = lVarEnd Function</SCRIPT></HEAD><BODY><H2><CENTER>An Active Server Page</CENTER></H2>The current value of x is <%= x %> <BR><%Dim yy = 20If 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:
- Variable declarations placed at script level within the
<SCRIPT>tag are recognized by ASP.
- Aside from variable declarations, no script-level code should be placed within the
<SCRIPT>tag. The remaining code located within a<SCRIPT>tag should consist solely of function, subroutine, and class definitions.
- Direct commands can contain any script-level code.
- All direct commands are executed as the web server is parsing the HTML and generating a response to the client. In other words, along with handlers for the events supported by ASP (Application_OnStart, Application_OnEnd, Session_OnStart, Session_OnEnd, OnTransactionAbort, and OnTransactionCommit), direct commands are basic "hooks" that allow your code to run.
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 ExplicitDim xx = 10Private Function Increment(lVar)lVar = lVar + 1Increment = lVarEnd FunctionPrivate Function Decrement(lVar)lVar = lVar - 1Decrement = lVarEnd FunctionDim sMsgsMsg = "The current value of x is " & x & vbCrLfDim yy = 20If x = 0 Then x = 10sMsg = sMsg & "Value returned by Increment: " & Increment(x) & vbCrLfsMsg = sMsg & "Value returned by Increment: " & Increment(x) & vbCrLfsMsg = sMsg & "Value returned by Decrement: " & Decrement(x) & vbCrLfsMsg = sMsg & "The value of x is now " & x & vbCrLfsMsg = sMsg & "The value of y is " & y & vbCrLfMsgBox 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 ExplicitDim xx = 10Private Function Increment(lVar)lVar = lVar + 1Increment = lVarEnd FunctionPrivate Function Decrement(lVar)lVar = lVar - 1Decrement = lVarEnd 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 yy = 20If x = 0 ThenDocument.Write "Initializing <I>x</I> in the second script block" & "<BR>"x = 10End IfDocument.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
#includeserver-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
#includedirective 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
#includedirective. 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 CServerPrivate sName, sProtocol, sSoftware, sURL, lPortPublic Property Get Name( )Name = sNameEnd PropertyPublic Property Get Port( )Port = lPortEnd PropertyPublic Property Get Protocol( )Protocol = sProtocolEnd PropertyPublic Property Get URL( )URL = sURLEnd PropertyPublic Property Get Software( )Software = sSoftwareEnd PropertyPrivate 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 SubEnd 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 oServerSet 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, oDriveSet oDict = WScript.CreateObject("Scripting.Dictionary")Set oFS = WScript.CreateObject("Scripting.FileSystemObject")Set oDrives = oFS.DrivesFor Each oDrive in oDrivesIf oDrive.IsReady ThenoDict.Add oDrive.DriveLetter, oDrive.FreeSpaceEnd IfNextSet GetFreeSpace = oDictEnd Function%>
Example 2-13: A WSH Script That Uses an Include File <PACKAGE><JOB ID=GetFreeSpace><SCRIPT LANGUAGE="VBScript" SRC="Lib.vbs" /><SCRIPT>Option ExplicitDim oSpace, aDrivesDim sMsg, sDriveDim iCtrSet oSpace = GetFreeSpace( )aDrives = oSpace.Keysfor iCtr = 0 to UBound(aDrives)sDrive = aDrives(iCtr)sMsg = sMsg & sDrive & ": " & oSpace(sDrive) & vbCrLfnextMsgBox 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) = "" ThenIsBlank = TrueElseIsBlank = FalseEnd IfEnd 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 oFrmSet oFrm = Document.frmInfoIf IsBlank(oFrm.txtName.Value) Or _IsBlank(oFrm.txtAddress.Value) Or _IsBlank(oFrm.txtCity.Value) Or _IsBlank(oFrm.txtState.Value) ThenfrmInfo_OnSubmit = FalseAlert "Please make sure the Name, Address, City " & _"State fields are not blank."End IfEnd 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
© 2001, O'Reilly & Associates, Inc.
webmaster@oreilly.com