DAY 7

Building a Home for Your Code


CONTENTS


On this, the last day of the first week, you will learn one more fundamental concept: how VBScript code is organized. VBScript, like most other languages, stores code in what we call procedures. As you will see, you can design procedures in different ways for different purposes. Procedures are containers for groups of code that accomplish specific tasks. Procedures can call and be called by other procedures, giving the programmer a great deal of flexibility. Today, you will learn how to create and use procedures, as well as the benefits of using them.

A procedure is a grouping of code statements that can be called by an associated name to accomplish a specific task or purpose in a program. Once a procedure is defined, it can be called from different locations in the program as needed.

Procedures come in two varieties: subroutines and functions. Each have special characteristics that set them apart, and today's lesson considers both subroutines and functions individually. Having been introduced to subroutines and functions, you will learn about a type of procedure called an event. Events respond to user-initiated activities, such as clicking a button. Another type of procedure, called a method, performs a specific task of an object. You will learn how all these procedures work together as well as where you should place them in an HTML document.

Subroutines

The first type of procedure I will discuss is the subroutine. A subroutine is a container that holds a series of VBScript statements. Suppose you'd like to create a block of code that accomplishes some specific task. Maybe you need to accomplish that task in various places throughout your code. All you need to do is create, or declare, the subroutine in your script. Once you've declared the subroutine, you can call it anywhere within your code. When your program calls a subroutine, the flow of the code is temporarily diverted to the statements within the subroutine. Once the subroutine has finished executing, control returns to the code that called the subroutine and execution picks up from there.

A subroutine is a block of code that can be called from anywhere in a program to accomplish a specific task. Subroutines can accept starting data through subroutine declaration variables called parameters. However, subroutines do not automatically return a result code or an argument to the caller.

Declaring a Subroutine

You declare subroutines using the Sub keyword and end them using the End Sub statement. The structure of a subroutine is

Sub Subroutine_Name(argument1, argument2, ..., argumentn)
   ...code within the subroutine
End Sub

where Subroutine_Name is the name of the subroutine and argument1 through argumentn are optional arguments, often called parameters, that you can pass to the subroutine. If you choose not to pass any arguments to the subroutine, the parentheses are optional, as you will see in a moment.

An argument or a parameter is a variable that a procedure requires in order to execute. In VBScript, arguments must be supplied in the order specified by the procedure.

The name of a subroutine should adequately describe what the subroutine is for. Make the name as descriptive as you can, and name the subroutine in plain English rather than in some cryptic abbreviations only you understand and might even forget a week later. You must name subroutines using the same rules as variables; letters and numbers are fine as long as the first character is not a number, and you cannot use symbols. If you enter an invalid name for a subroutine, the VBScript run-time interpreter will alert you of the problem when you attempt to try your script. The following are some valid subroutine names:

WelcomeTheUser
PrintInvoice
Meters2Yards

The following are some unacceptable subroutine names:

User.Welcome
2Printer
Miles*1.609

If you want, a subroutine can require that the code statement that calls that subroutine provide one or more arguments or variables that the subroutine can work with. Any time you need preexisting data to perform the task within the subroutine, arguments are very helpful. For example, the following subroutine accepts an argument to be used in the subroutine as a message to be displayed to the user:

Sub ShowMessage(CurrentMessage)
   MsgBox CurrentMessage, vbOkOnly, "Important Message"
End Sub

In this case, CurrentMessage is the argument, and it is treated like any other variable in the subroutine. It is treated exactly as if it had been declared with a Dim statement with one very important difference. The CurrentMessage argument variable starts out pre-initialized with a value that was supplied by the code that called this subroutine. When you declare a procedure and specify that it will receive arguments, you can specify how it interacts with those argument variables. You can either give the subroutine a copy of a value supplied as an argument, or you can refer it to the actual variable that the caller is using. If you give the subroutine a copy of the variable, the subroutine can then make changes to its own copy without changing the original. If, on the other hand, the subroutine simply references the variable owned by the caller, it cannot change the variable-it doesn't own it. I'll discuss this in more detail later in today's lesson.

Often, a subroutine might not require any arguments, and you can drop the parentheses. Suppose you have a subroutine that simply displays information to the user. In that case, the subroutine doesn't need any arguments, as in the following case:

Sub ShowAboutMessage
   MsgBox "This Web page was designed by the WebWizard."
End Sub

In this case, you can see that the code lists no arguments, so it does not require the parentheses. On the other hand, if you declared a procedure using one or more arguments, you'd use the parentheses:

Sub ShowAboutMessage(Message)
   MsgBox Message
End Sub

Calling a Subroutine

Now that you've learned how to create a subroutine, how do you call one? You can call a subroutine throughout the rest of the application once you've declared and created it. You can call subroutines by using the Call keyword or just entering the name of the subroutine on a line of code. For example, to call a subroutine called ShowMessage, you could enter

ShowMessage "This is the message."

You could also use the Call keyword and enter

Call ShowMessage("This is the message.")

Notice that in the first method, you do not place parentheses around the arguments of the subroutine. On the other hand, if you use Call, you must enclose the arguments in parentheses. This is simply a convention that VBScript requires. What if a subroutine has no arguments? To call the subroutine ShowAboutMessage, you could enter

ShowAboutMessage

or

Call ShowAboutMessage()

or you could use

Call ShowAboutMessage

The first method simply lists the name of the subroutine. The second method uses Call but doesn't require parentheses because the subroutine has no arguments. Whether you use the parentheses when you call or declare a subroutine with no arguments is a personal preference about writing code. When you call a subroutine without the Call statement, it can be more difficult to figure out the difference between a subroutine and a variable in your code, especially if your code is lengthy. Although the choice is up to you, it is generally recommended that you always use the Call statement when calling subroutines for the sake of readability.

Exiting a Subroutine

The code within your subroutine will execute until one of two things happens. First, the subroutine might get down to the last line, the End Sub line, which terminates the subroutine and passes the baton back to the caller. This statement can appear only once at the end of the subroutine declaration. The second possibility is that VBScript could execute the following code statement:

Exit Sub

when placed inside the subroutine. You might use this statement if you need to provide more than one exit point for the subroutine. However, you shouldn't need to use this very often if your subroutine is constructed properly. Consider the following subroutine:

Sub ConvertFeetToInches
   Feet = InputBox("How many feet are there?")
   If Feet < 0 Then
      Exit Sub
   Else
      MsgBox "This equals " & Feet * 12 & " inches."
   End If
End Sub

This subroutine contains an Exit Sub statement, which could be avoided by changing the subroutine to

Sub ConvertFeetToInches
   Feet = InputBox "How many feet are there?")
   If Feet >= 0 Then
      MsgBox "This equals " & Feet * 12 & " inches."
   End If
End Sub

Here, the If…Then conditional structure was rewritten to avoid the need for the Exit Sub statement. In this case, the subroutine could be changed to avoid Exit Sub. Keep in mind, however, that you may occasionally need to use Exit Sub to exit a subroutine, particularly when handling errors.

Note
For more information on how to handle errors, refer to Day 17, "Exterminating Bugs from Your Script."

Functions

The second type of procedure is called a function. Like a subroutine, a function also holds a series of VBScript statements. The only difference is that a function actually returns a value to the code statement that called it.

A function is a block of code that can be called from anywhere in a program to accomplish a specific task. Functions can accept parameters and can also return a result code to the caller.

You've seen in earlier lessons how you can fill a variable with a value supplied on the right side of an assignment statement:

ZipCode = 49428

In the same manner, you can fill a variable with a value supplied by a function you define:

ZipCode = GetZipCode("Jenison")

As with the subroutine, the flow of code is redirected to the function while the code within the function executes. Once the function has finished executing, control returns back to the code that called the function, the value from the function is assigned to the calling code, and execution picks up from there.

Declaring a Function

To declare a function, use the Function keyword instead of the Sub keyword. You end functions using the End Function statement. The structure of a function is

Function Function_Name(argument1, argument2, …, argumentn)
     ...code within the function
End Sub

where Function_Name is the name of the function and argument1 through argumentn are optional arguments you can pass to the function. As with the subroutine, the parentheses are not required if no arguments are passed to the function.

As before, make sure the name of the function adequately describes what the function does. The same naming conventions that apply to the subroutine also apply to the function. Also, arguments are passed to the function the same way they are passed to a subroutine. For example, if you had a function named GetMiles with an argument variable used to pass the number of kilometers into the function, your declaration would look like this:

Function GetMiles(Kilometers)
   GetMiles = Kilometers * 1.609
End Function

In this example, the function returns the number of miles. Notice that it looks like the programmer has accidentally used the same variable name as the function in storing the number of miles. In reality, there's nothing accidental about this. In order to pass a value back from a function, you must set a special function "variable" that is named after the function. VBScript automatically knows that your intention is to pass the value back to the caller, not to create a temporary variable within the function. If you don't set this "variable" in your program, the result will be a zero or an empty string. It's very important, therefore, that you make sure your function returns some valid value. This value should be assigned to the function name itself within the block of code that makes up the function.

It's very easy to get busy coding within a function, creating variables as you need them, and forget to store the result in the return "variable" for the function. For example, you might have a function like what's shown in Listing 7.1.


Listing 7.1. A function that doesn't return a result.
Function GetAge
   Dim Age
   Dim Valid
   Do
      Age = InputBox "Please enter your age ( 5 - 120, please): "
      If Age >= 5 and Age <= 120 Then
         Valid = vbTrue
      Else
         MsgBox "The age you have entered is invalid. Please enter it again."
      End If
   Loop While Valid = vbFalse
End Function

This function contains a loop that asks the user for his age and continues to ask the user until the age falls within the correct range. Once it does, the condition of the control structure is no longer met and the loop completes. The programmer was so happy she got the function working that she forgot one thing-to pass the age back to the caller! One very important line at the end was omitted.

The function in Listing 7.1 should be written as shown in Listing 7.2.


Listing 7.2. The same function, but now it does return a result.
Function GetAge
   Dim Age
   Dim Valid
   Do
      Age = InputBox "Please enter your age ( 5 - 120 ): "
      If Age >= 5 and Age <= 120 Then
         Valid = vbTrue
      Else
         MsgBox "The age you have entered is invalid. Please enter it again."
      End If
   Loop While Valid = vbFalse
   GetAge = Age
End Function

Here, you can see that the programmer set the function variable equal to the variable Age declared inside the function. Then the programmer assigned that variable to the function, effectively returning it to the caller.

The programmer could have also written the function without the temporary age variable, as shown in Listing 7.3.


Listing 7.3. The same function without a temporary variable that stores the return result.
Function GetAge
   Dim Valid
   Do
      GetAge = InputBox "Please enter your age ( 5 - 120 ): "
      If GetAge >= 5 and GetAge <= 120 Then
         Valid = vbTrue
      Else
         MsgBox "The age you have entered is invalid. Please enter it again."
      End If
   Loop While Valid = vbFalse
End Function

Even though this implementation would work just fine, it's a good programming practice to use a temporary variable and then make the assignment at the end. This is useful in part because it's easier to read the code. The reader can make better sense of your code when every variable is declared and the function uses no variables that do not have declarations. Because the function "variable" has no formal declaration and could potentially be set in several different code branches within the function, it might confuse the surveyor of your code if your code is lengthy.

Another important point to keep in mind is that when you write a function that uses arguments, you must not declare variables with the same name as the arguments using the Dim statement. When you specify arguments in a function, those arguments are automatically declared as variables in the function, but they are set to whatever was supplied when the procedure is called. If you try to declare them again, you will get an error because they're already in use in the function. For instance, this function would result in an error:

Function ConvertToSeconds(OrigHours, OrigMinutes, OrigSeconds)
   Dim OrigSeconds
   OrigSeconds = OrigHours * 3600 + OrigMinutes * 60 + OrigSeconds
   ConvertToSeconds = OrigSeconds
End Function

The function would be incorrect because the variable OrigSeconds is already part of the argument list. Rather, you could assign the result directly to the function output variable or use some other variable:

Function ConvertToSeconds(OrigHours, OrigMinutes, OrigSeconds)
   Dim Total
   Total = OrigHours * 3600 + OrigMinutes * 60 + OrigSeconds
   ConvertToSeconds = Total
End Function

Calling a Function

Now that you've seen how to declare a function, you need to know how to call it. The benefit of using a function is that you can pass back a piece of data to the caller. The subroutine does not enable you to do this because it does not return anything. You will see a way to change variables in the calling code with a subroutine later today, but the function is a better way to get data back and forth. To call a function, you simply use the syntax

return_variable = function_name(argument1, argument2, …, argumentn)

Notice that in this case, the syntax is quite a bit different from the subroutine. Here, you can assign the function to a variable (or another expression that can be updated with a value, such as a property, which will be covered in later lessons), or you needn't assign it to anything. The parentheses are optional only when no arguments are passed to the function.

For an example of its use, suppose you have a function called GetAge. To use the GetAge function, you could enter the statement

UserAge = GetAge()

or

UserAge = GetAge

Notice that this function doesn't need any arguments, and the result is assigned to a variable named UserAge. The following function requires three arguments-hours, minutes, and seconds-and returns the number of seconds:

Function GetSeconds(Hrs, Min, Sec)
   GetSeconds = Hrs * 3600 + Min * 60 + Sec
End Function

Note
The terms arguments and parameters are usually used synonymously.

You could then call this function using a statement like

NumSeconds = GetSeconds(2, 34, 25)

or

NumSeconds = GetSeconds 2, 34, 25

where the total number of seconds is returned to the variable NumSeconds.

Note
Make sure you never create variables that have the same name as keywords already used by VBScript. These keywords are called reserved words and include terms such as Date, Minute, Second, Time, and so on. For a complete list of reserved words, refer to Appendix A, "VBScript Syntax Quick Reference."

The statement

Call GetSeconds(2, 34, 25)

would also be valid, but it wouldn't be very useful because you're not retrieving the number of seconds from the function! This simply calls a function as if it were a subroutine, without handling the return value. You can also utilize a function within an expression, such as

MsgBox "There are " & GetSeconds(2, 34, 25) & _
" seconds in the time you have entered."

In this case, you must use parentheses around your arguments. You don't need to assign a variable to the return of the function because the return value is automatically used within the statement. Although this is certainly legal, it is not always the best programming practice. If you want to use the result of the function more than once, you must store the result in a variable. Otherwise, you will have to call the function again and waste the computer's resources in doing the calculation all over again. Likewise, storing the value in a variable to avoid repeated calls makes the code more readable and maintainable.

Exiting a Function

To exit a function, you use the same method as when you exit a subroutine, namely the End Function statement. This statement can only appear once at the end of the function declaration. You have seen this statement used in the functions discussed so far. You can also use the statement Exit Function to break out of a function just like you used the Exit Sub statement to exit a subroutine. As before, it's better to exit a function naturally when your code reaches the final End Function statement than to use an Exit Function line of code to terminate the function in the middle of the statements. The code is simply easier to follow when you avoid such forced exit statements.

Passing Arguments into Procedures

Earlier today, you saw the two ways you can pass arguments into a procedure. The manner you use is determined by the way you declare the procedure. Based on that declaration, when a procedure is called, you either provide a copy of a variable to a procedure so that it has a local copy to modify if necessary, or you refer the procedure to the original variable itself. Either way, the variable owned by the caller cannot be modified by the procedure. If you refer the procedure to the original variable, you are in effect supplying the memory address of that variable to the procedure. However, VBScript hides the memory address details from you. From the programmer's perspective, you are simply providing the variable name to the procedure. But VBScript does not allow you to change the variable-the interpreter will trigger an error when the Web page loads if you try to do so.

Note
The current VBScript interpreter in Internet Explorer does not support the ByRef parameter familiar to many Visual Basic programmers. In Visual Basic 4.0, for example, if you use a ByRef parameter, you can make changes to that variable within the subroutine that defined the parameter and the changes are reflected back to the variable supplied in that parameter position by the calling code. This is a capability that one might expect to be introduced in subsequent releases. You might wish to check the current documentation to ascertain the current implementation level if you are interested in this feature. You can refer to Microsoft's online VBScript documentation at www.microsoft.com/vbscript and look up Sub and call under the language reference search. Alternatively, you can check to see if there is an update sheet for this book at Macmillan's site (www.mcp.com), or at the authors' site (www.doubleblaze.com/vbs21day).

To illustrate this concept, suppose your secretary wishes to use a letter you sent to a client as a template for creating another letter. You don't mind sharing the letter with her, but you must not allow the original to be changed in any way. You could make a copy of your letter and give it to her, after which she could mark it up if she wished and make changes to it. In this case, she would not change the original because it would be in your hands. Or you could simply show her the original and only allow her to examine it without changing it. In that case, she wouldn't get her own copy, but she could use the contents of the letter to make her own letter if she wished, as long as she didn't change the original.

Giving your secretary a copy of the letter can be likened to passing a variable into a procedure by value. You give the procedure a copy of the variable that is totally divorced from its original. To do this, you simply place the keyword ByVal in front of the variable in the declaration of the procedure.

Passing by value means that a copy of the original value is given to the procedure. The procedure can change its own copy, but it won't affect the original owned by the caller.

To refer the procedure to the original without giving it a copy of the variable, you omit the ByVal keyword. This is the default case and is called passing a variable by reference. If you try to modify a variable passed to a procedure by reference, VBScript will give you an error message when the Web page loads into the browser. The procedure can read the contents of a variable passed in by reference, but it cannot change its contents because the procedure doesn't own the variable.

Passing by reference means that the procedure is allowed to read the variable owned by the caller. The procedure is not allowed to change the value of the variable because it is not the owner of that variable.

If you wish to pass variables into a procedure by value, you simply declare the procedure using the format

Sub Subroutine_Name(ByVal argument1, ByVal argument2, ... ByVal argumentn)

when creating a subroutine and

Function Function_Name(ByVal argument1, ByVal argument2, ... ByVal argumentn)

when creating a function. To pass variables by reference, you simply omit the ByVal keyword as shown here for a subroutine:

Sub Subroutine_Name(argument1, argument2, ... argumentn)

and here for a function:

Function Function_Name(argument1, argument2, ... argumentn)

As an example of passing a variable by value, consider the sample Web page shown in Fig-ure 7.1.

This Web page is named byval.htm and is located on the CD-ROM that comes with the book. This Web page contains a text box where the user can enter a number. That number is then passed into a subroutine, which takes the value and doubles it, displaying it to the user. Then, when the subroutine has finished, the caller resumes by displaying the value of the variable it passed in. The code for this Web page is shown in Listing 7.4.


Listing 7.4. Passing variables to a procedure by value.
<SCRIPT LANGUAGE="VBScript">
<!-- Option Explicit

   Sub cmdDouble_OnClick()

      Dim Number
      Dim Doubled

      Number = txtBefore.Value
      Doubled = Doubler(Number)

      txtAfter.Value = Number
      txtNewValue.Value = Doubled

   End Sub

   Function Doubler(ByVal Number)
      Number = Number * 2
      Doubler = Number
   End Function

-->
</SCRIPT>

As you can see in Figure 7.1, the number remains unchanged after the Doubler function is called. The number returned by the function is indeed double that of the one passed to it. Since the calling subroutine passed the variable by value, the function could modify its own copy of the variable.

Figure 7.1 : A Web page that doubles a value entered by the user.

What would have happened if the variable had been passed by reference? Figure 7.2, which displays the Web page named badbyval.htm, shows the result.

Figure 7.2 : A Web page with a procedure that tries to modify a variable passed to it by reference.

As you can see, an error results. Listing 7.5 shows the incorrect code.


Listing 7.5. Incorrectly trying to modify a variable passed by reference.
<SCRIPT LANGUAGE="VBScript">
<!-- Option Explicit

   Sub cmdTest_OnClick()

      Dim Number
      Dim Doubled

      Number = txtBefore.Value
      Doubled = Doubler(Number)

      txtAfter.Value = Number
      txtNewValue.Value = Doubled

   End Sub

   Function Doubler(Number)
   ' This code is incorrect for the sake of this example, as described above!
      Number = Number * 2
      Doubler = Number
   End Function

-->
</SCRIPT>

As you can see, the function now gets the value by reference. When it tries to change the variable, VBScript flags the error that appears in Figure 7.2. It is your responsibility as a programmer to make sure you pass variables into procedures properly to avoid errors such as these.

Follow these rules of thumb to stay on safe ground:

You do not have to pass every variable into the procedure in the same way. You can pass some variables in by value and some by reference, as in the following example:

Function ConvertToMeters(ByVal Inches, Feet)
      Inches = Feet * 12 + Inches
      ConvertToMeters = Inches * 0.0254
   End Function

Here, the variable Inches is passed in by value because the programmer designed the function to change the value of this variable. In order to accomplish this, he must declare the variable Inches by value so that the function gets its own copy and can modify it. The variable Feet, on the other hand, never gets changed, so it can be passed by reference.

Figure 7.3 shows a working implementation of this function in the Web page named meters.htm.

Figure 7.3 : The metric converter Web page using ByVal.

The code listing for this Web page is shown in Listing 7.6.


Listing 7.6. Passing variables to a procedure by value.
<HTML>

<HEAD>
<TITLE>The Metric Converter</TITLE>
</HEAD>

<BODY>

<H1><A HREF="http://www.mcp.com"><IMG  ALIGN=BOTTOM
SRC="../shared/jpg/samsnet.jpg" BORDER=2></A>
The Metric Converter</H1>

<HR>

<CENTER><H2>Passing variables both by value and by
reference into a procedure</H2>

<P>Enter a distance in inches and feet and click on the "Convert" button.
The result will be displayed in meters.

<PRE><INPUT NAME="txtFeet" SIZE=10 > feet    
<INPUT NAME="txtInches" SIZE=10 > inches</PRE>
<P><INPUT TYPE="BUTTON" NAME="cmdConvert" VALUE="Convert">
<P><INPUT NAME="txtResult" SIZE=50 ></CENTER>

<HR>

<center>
from <em>Teach Yourself VBScript in 21 Days</em> by
<A HREF="../shared/keith.htm">Keith Brophy</A> and
<A HREF="../shared/tim.htm">Tim Koets</A><br>
Return to <a href="..\default.htm">Content Overview</A><br>
Copyright 1996 by SamsNet<br>
</center>

<SCRIPT LANGUAGE="VBScript">
<!--  Option Explicit

   Sub cmdConvert_OnClick()
   
     Dim Inches, Feet, Meters

     Inches = txtInches.Value
     Feet = txtFeet.Value

     Meters = ConvertToMeters(Inches, Feet)

     txtResult.Value = "There are " & Meters & " meters in " & Feet & _
                       " feet and " & Inches & " inches."

   End Sub

   Function ConvertToMeters(ByVal Inches, Feet)

      Inches = Feet * 12 + Inches
      ConvertToMeters = Inches * 0.0254

   End Function

-->
</SCRIPT>

</BODY>

</HTML>

As you can see from Figure 7.2, the user simply enters the number of inches and feet in the text boxes shown and clicks on the Convert button. That calls the function ConvertToMeters, which converts the values into meters and returns the value, which is then displayed in the result text box on the Web page.

Because the variable Feet never gets changed, you do not have to worry about passing it by value. The variable Inches,on the other hand, does get changed because the function takes the number of feet and multiplies by 12 to convert to inches, adding that result to the original number of inches. In order to perform this operation, the function must have its own copy of the Inches variable, which is why it must be passed to the function by value.

You can, of course, decide to always pass variables into your procedures by value to avoid the possibility of introducing errors into your programs. This is fine, as long as you realize that the variables you modify will not change those found in the caller of the procedure. You also have to go through the extra work of placing a ByVal keyword in front of every variable of your function declarations. On the other hand, passing in values by reference is more efficient for the VBScript interpreter because it doesn't have to clone a copy of the variable for the procedure-it can simply refer the procedure to the variable instead.

Why Are Procedures Useful?

If you're new to programming, you might be wondering why procedures are useful in the first place. Now that you've seen how to use them, I'll discuss why they are so beneficial. There are three primary reasons: readability, maintainability, and correctness.

Procedures are useful any time you have a task that must be accomplished many times, perhaps in many places in your code, throughout your program. Suppose you request an order number from the user, and each time the number is entered, you want to make sure it's valid. One option is to write code that checks each time an order is entered as shown in Listing 7.7.


Listing 7.7. Code that should be placed in a function.
SpouseOrder = InputBox("What order would you like for your spouse?")
If SpouseOrder < 0 Then
   MsgBox "The order number is invalid."
End If
YourOrder = InputBox("What order would you like for yourself?")
If YourOrder < 0 Then
   MsgBox "The order number is invalid."
End If
ChildOrder = InputBox("What order would you like for your children?")
If ChildOrder < 0 Then
   MsgBox "The order number is invalid."
End If

As you can see from this example, the same check is repeated three times in your code. This results in code that is not only more difficult to read, but also more difficult to maintain.

Rather than type the same code three times, wouldn't it make your code more readable if you created a function and placed the repeating code within that function? Suppose you call the function VerifyOrderNumber and place the common code in that function. Then, the code might look like that in Listing 7.8.


Listing 7.8. Using a function to improve the program.
Sub GetOrders
Do
   SpouseOrder = InputBox("What order would you like for your spouse?")
Loop Until VerifyOrderNumber(SpouseOrder) = True

Do
   YourOrder = InputBox("What order would you like for yourself?")
Loop Until VerifyOrderNumber(YourOrder) = True

Do
   ChildOrder = InputBox("What order would you like for your children?")
Loop Until VerifyOrderNumber(YourOrder)
End Sub
Function VerifyOrderNumber(OrderNumber)

   If OrderNumber < 0 Then
      MsgBox "The order number is invalid."
      VerifyOrderNumber = False
   Else
      VerifyOrderNumber = True
   End If

End Function

If the user enters values greater than zero, the function returns with a Boolean variable indicating the result is valid. Otherwise, the function returns the Boolean value for false, and the loop continues to prompt the user until an order number is entered, calling the function again each time through.

In this case, you have placed all the repeating code within a function so that the code within the function appears just once rather than several times throughout the program. Doing this has several advantages. First of all, it's a lot easier for the reader of the code. He can see what the code does in one word rather than having to wade through all the details. Furthermore, it cuts down on the size of the code listing. Perhaps most important, it makes the code more maintainable.

Note
To keep the example simple, the amount of repeating code you save in this sample code is relatively small. Keep in mind that many blocks of code which are candidates for procedures could be many times larger. When these code blocks are moved to procedures, the advantages of "procedurized" code mentioned here become even greater. If you're an experienced computer programmer, you might realize that a computer has to do more work to call a procedure than to process statements that are simply in the flow of code without a procedure call. However, this extra amount of work is very slight in the context of your overall script and will typically have no visible effect on the speed of your script. In other words, "procedurizing" your code has a lot of advantages and virtually no disadvantages.

Suppose you realize that a valid order number not only should be greater than zero, but that it also must be less than 5000. If you wrote your program without the subroutine, as in Listing 7.8, you would have to look for every place in your program that asks for an order number and modify the code after it to include the additional check. Your code would look like that in Listing 7.9.


Listing 7.9. Code that is a pain to maintain because the repeatable code is not contained within a function.
SpouseOrder = InputBox("What order would you like to place for your spouse?")
If SpouseOrder < 0 and SpouseOrder >= 5000 Then
   MsgBox "The order number is invalid."
End If
YourOrder = InputBox("What order would you like to place for yourself?")
If YourOrder < 0 and YourOrder >= 5000 Then
   MsgBox "The order number is invalid."
End If
ChildOrder = InputBox("What order would you like to place for your children?")
If ChildOrder < 0 and ChildOrder >= 5000 Then
   MsgBox "The order number is invalid."
End If

In this case, you had to make three modifications to your program. You're fortunate because all the changes are in one place; in reality, they could spread all across the program! The better solution is to make the change in one place, as shown in Listing 7.10. Note that the calling code remains identical to the procedure-calling example shown earlier. It is only one statement in the body of the function itself that must change.


Listing 7.10. Using a function to improve the program.
Do
   SpouseOrder = InputBox("What order would you like for your spouse?")
Loop Until VerifyOrderNumber(SpouseOrder) = True

Do
   YourOrder = InputBox("What order would you like for yourself?")
Loop Until VerifyOrderNumber(YourOrder) = True

Do
   ChildOrder = InputBox("What order would you like for your children?")
Loop Until VerifyOrderNumber(YourOrder)

The function would be written in the following way:

Function VerifyOrderNumber(OrderNumber)

   If OrderNumber < 1 and OrderNumber >= 5000 Then
      MsgBox "The order number is invalid."
      VerifyOrderNumber = False
   Else
      VerifyOrderNumber = True
   End If

End Sub

Now, rather than make three changes, you simply have to make one. It doesn't matter how many times you call the function or where you make the calls in your code. Because all the functionality is wrapped up in one location, you only have to change that location itself.

Procedures help enhance a program in three ways. They make the program more readable because there is less code and the surveyor of the code isn't always forced to see the details of every part of the code. The program is also more maintainable because changing code in a function requires one change in only one place instead of many changes scattered throughout the application. Finally, your code is more correct because the likelihood of making a mistake when you're duplicating code across the application is greater than when you have the code in one place.

Clearly, the benefits of using procedures are numerous and important. Procedures are the foundation of a good program, and you will see them used throughout the rest of the applications in this book.

Event Procedures

Earlier this week you were exposed to procedures, although you may not have known it at the time. When you work with controls and other components, you frequently interface with them using event procedures. Event procedures are subroutines that are called automatically by the browser. They are different from regular subroutines in that regular subroutines must be called within the program by statements you write or else they are never used. An event procedure is called as a result of some action taken by the user or the system, such as the user clicking a command button or checking a box on a Web page or the system detecting that a predefined timer has expired.

An event is a subroutine called automatically by VBScript as a result of a user or system action. Subroutine and function calls must be manually specified by the programmer through normal calls in code, but event subroutines are called automatically as a result of what they represent. You just define a subroutine to be carried out when the user clicks on a particular button by means of a special name, for example. Then the button control object itself generates an OnClick event when it is clicked on, and VBScript responds to this event by calling the named subroutine associated with the event.

You don't have to worry about calling event procedures in your code because they are called automatically by the system and the browser, but you do need to create them if you want to write code that responds to these events. For instance, if you place a button on your Web page, you probably want your program to respond in some way when the user clicks it. If you construct an event procedure, you can write the code to do just that. If you fail to place the event subroutine in your code, however, your users can click the button all day long, and nothing will happen because there is no event procedure to process the click events.

The rules for creating and naming event procedures are more rigid and structured than regular procedure naming rules. First of all, the naming conventions for an event procedure are very specific. Except with some special cases discussed on Day 8, "Intrinsic HTML Form Controls," you can't just name an event procedure anything you want. To name an event procedure, you must know two things: the name of the control, or component, and the name of the event you want to respond to. Suppose, for example, that you have a command button on your Web page labeled TestButton. As you will see on Day 8, buttons have an event called OnClick that you can write code for. This event occurs when the user clicks the button. To create an event procedure for this action, you must name your procedure

TestButton_OnClick

Notice that the name of the control comes first, followed by an underscore character, followed by the name of the event. You must spell everything correctly, or else VBScript will be unable to connect the event procedure to the button. The rule for assigning a name to a control or component is

Sub ControlName_EventName()

where ControlName is the name of the control and EventName is the name of the event corresponding to the control. In addition to what you'll learn on Day 8, you will also learn about intrinsic HTML controls and ActiveX controls on Day 9, "More Intrinsic HTML Form Controls," Day 10, "An Introduction to Objects and ActiveX Controls," and Day 11, "More ActiveX Controls." On those days, you will learn much more about event procedures and how to use them. For now, just keep in mind that event procedures are blocks of code grouped in subroutines within your scripts, just like those we have already discussed.

Method Procedures

You may have also heard of method procedures. Method procedures are like predefined procedures you can call, but they are provided by an object. You can't actually see the code for them, nor can you create them. Objects can have a set of methods that were created when the programmer designed the object.

A method is a procedure you can call that is associated with an object. Methods accomplish some specific task or service the object provides. Methods may or may not return values, depending on what they do.

For example, the error object, which you will learn more about on Day 17, has a method called clear that will clear the error number of the error that has occurred. To invoke that method, you can simply call the method using the following convention:

object.method

where object is the name of the object and method is the name of the method. In this case, you could use

Err.Clear

to clear the error code for the error that has occurred. Methods are different from events, functions, or subroutines, however, because you cannot create them. They are an inherent part of an object that you can call.

Procedures, Subroutines, Functions, Events, and Methods!

Wow! What a collection of terms. It's easy to get confused in recognizing these terms, so think of them as shown in Figure 7.4.

Figure 7.4 : Sorting out procedures, subroutines, functions, events, and methods.

Procedures consist of subroutines and functions. Functions can return a value to the caller, and subroutines cannot. Events are special subroutines that are called automatically in response to the user triggering an event, such as clicking a button. Finally, methods are a special kind of procedure that can either be a function or a subroutine. That is, they could return a value or they might not. The only difference is that methods are part of an object. You can call a method in your code if you have access to an object, but you cannot create them or see the code that is executed behind them.

Where to Put Procedures

Finally, you need to understand where you can place procedures in your HTML document. On Day 3, "Extending the Power of Web Pages with VBScript," you learned about the general structure of an HTML document. You saw that, as a rule, you should put VBScript code before the end of the <BODY> section of an HTML document. You can place procedures within one script tag:

<SCRIPT LANGUAGE="VBScript">
<!--
     Sub GetMiles()
          ...code for subroutine
     End Sub

     Function CalculateTime(RunnerTime)
          ...code for function
     End Function
-->
</SCRIPT>

You can also place procedures in separate scripts:

<SCRIPT LANGUAGE="VBScript">
<!--
     Sub GetMiles()
          ...code for subroutine
     End Sub
-->
</SCRIPT>

<SCRIPT LANGUAGE="VBScript">
<!--
     Function CalculateTime(RunnerTime)
          ...code for function
     End Function
-->
</SCRIPT>

A good rule of thumb is to enter all the subroutines and functions first, making sure none is called before it is used. Also, it is much easier to put them as close together as possible, preferably all under the same script tag. That way, you have access to all of them at once and don't have to hunt through your Web page to find them.

Also, notice that not all VBScript code must be stored within a procedure. If you have code outside a procedure, such as the example shown below, keep in mind that this code is automatically executed in order from top to bottom when the browser first loads the Web page:

<SCRIPT LANGUAGE="VBScript">
<!--  Option Explicit

   Dim Miles_Ran
   Dim Total_Miles
   Dim Start_Time
   Call InitializeVariables

   Sub InitializeVariables
      Miles_Ran = 0
      Total_Miles = 0
      Start_Time = 0
   End Sub
-->

When the browser first loads the Web page, it's a good time to initialize variables, call procedures that set values to their default conditions, and so on. As a rule, it's usually better to place all such code in the same place so that you don't miss a line or two somewhere down the page. Likewise, it's a good idea to place your script, and all its startup code, at the bottom of your page prior to the </BODY> tag. This ensures that any objects your startup code references have already been defined in the page by the time the code is called as the page loads for the first time. If your code doesn't use any startup code executed as the page loads, this is not a necessary step, but still is a recommended convention for easy script viewing and maintainability.

Summary

Today you have learned how to create and use procedures in your VBScript programs. You can divide procedures into two types: subroutines and functions. Subroutines and functions both contain code that other parts of the application can call. You have seen that a function returns a value to the user when it's finished, whereas a subroutine does not. You have also discovered how to declare a function or subroutine and call it in other places throughout the application.

Both functions and subroutines can accept one or more arguments that they can use to perform their tasks. You can pass either a copy of the arguments or a reference to the actual values to a procedure. When passing variables by value, those variables can be changed because they are copies of the original. In the case of passing variables by reference, however, you can read but not change the variables, because the procedure does not "own" them. You have learned how to pass variables into procedures using both ways and saw practical examples of both cases.

In studying how to construct procedures, you have discovered why they are so useful in the first place. You have learned that procedures help make your code more maintainable, more readable, and more prone to be free of errors. These are significant reasons to use procedures in your application wherever it is practical.

You have also been introduced to a special type of procedure called an event procedure. Event procedures are different from ordinary procedures in that they are called by the system and browser as a result of some activity initiated by the user or some other component on the Web page, such as a timer. Today's lesson briefly introduces event procedures, and next week you'll learn how and why you use them.

Today's lesson also discusses methods, which are special procedures contained within an object. You can call these procedures in your code by referencing them through the object. Although you can't modify a method or see the code, you can call it to accomplish a specific task within the object.

Finally, you have learned how and where to place procedures in your Web page. You can put the procedures all together within one script tag or spread them across multiple script tags. To make your code more readable, you should place all your subroutines in the same script tag in one section. That way, you can debug and find procedures without hunting for them all over your page.

The purpose of today's lesson is to give you a practical grasp on how to create and use procedures. Throughout the rest of the book, we use procedures. Indeed, they have been used for quite some time, although you might not have recognized them. Today's lesson is the last of a series of lessons that presented you with the basics of VBScript programming. Next week you'll put the basics to work and have some fun writing some Web pages of your own.

Q&A

Q
How many arguments can you give a procedure?
A
At the time of this printing, VBScript allowed you to pass up to 127 arguments to a procedure if you wanted. In reality, however, a good suggestion is that you not enter more than 10. The reason for this is that the more parameters you enter, the less readable and maintainable your procedures will be. If you find yourself passing more than ten parameters, you might want to redesign the procedure, possibly breaking it up into smaller pieces.
Q
What happens if I forget to include an event procedure for a control on my Web page?
A
Don't worry; nothing dangerous will happen on your Web page. The particular event that occurs in the control or component on the page simply won't find an event in your code to connect to and therefore will not perform any of your code statements. Of course, you should make sure event procedures exist for every control on your form where you want your code to respond. You will learn much more about this in next week's lessons.

Workshop

Create a Web page that uses VBScript code to ask the user for 10 values. All 10 values must be between 0 and 10. Get all 10 values from the user and make sure they are valid, but do not use any procedures in your code. Now, change the validation rule to accept values between 0 and 75 and test your Web page again. After you've done this, apply the same approach again, but this time, use procedures. By going through this exercise, you will begin to appreciate the value of using procedures in your code.

Quiz

Note
Refer to Appendix C, "Answers to Quiz Questions," for the answers to these questions.

  1. What is the difference between a function and a subroutine?
  2. Given the following code listing, what is the value of the variable A that is passed into the function after the function has been called successfully?


    Sub cmdTest_OnClick()
       Dim A, B, C
       A = txtA.Value
       B = txtB.Value
       C = GetHypotenuse(A, B)
       MsgBox "The hypotenuse of the right triangle is " & C & " units."
    End Sub

    Function GetHypotenuse(ByVal A, ByVal B)
       A = A * A
       B = B * B
       If A + B >= 0 Then
          GetHypotenuse = Sqrt(A + B)
       Else
          GetHypotenuse = 0
       End If
    End Function

  3. The following code listing contains an error:

    Sub cmdTest_OnClick()
       
        Dim Base_Cost
        Dim Total_Cost
        Dim Tax

        Tax = 5     ' Michigan sales tax (5%)

        Total_Cost = CalculateCost(Base_Cost, Tax)

        txtResult.Value = "The total cost is $" & Total_Cost

    End Sub
    Function CalculateCost(Cost, Tax)

       Tax = Tax / 100

       Cost = Cost + Tax * Cost

       CalculateCost = Cost

    End Function

    Find the error and fix the code listing so that it works properly.