Fundamental Elements of LotusScript

This section covers all the basic fundamental pieces of LotusScript, from variables to loops , classes, and script libraries. By the end of this section, you should be able to program very basic LotusScript programs that you will apply in the next section when you learn when, where, and how to use LotusScript in the Domino world.

Constants

A constant is a holder of information that cannot be changed later on. It keeps its information exactly as it was when it was first defined. You must do two things to set up a constant: name it and specify the permanent value.

Take a look at these examples:

Const MAX_LIST_ELEMENTS = 100 Const SORT_ASCENDING = "A"

A constant is first defined by the LotusScript command Const , which tells the system that a permanent value is about to be defined for the area the definition is in. The second element is the name of the constant. Notice that the name is in all capital letters . This is a standard programming practice. That way, when you read code that has a constant in it, the constant value stands out clearly. The last piece of information that is needed is the value you are assigning to the constant.

Constants are extremely useful when you have certain values that will hardly ever change or that will never change. For example, instead of typing 3.141592657 for the value of PI , you could make a constant called PI and just use it wherever it is needed:

Const PI = 3.141592657

This saves keying time and reduces mistakes.

Another example involves the upper and lower bounds of the application. Business users have a habit of not being able to predict the boundaries or limits of their business processes. So, instead of hard-coding values, you can use constants. (If you see hard-coded values in a program, watch out for trouble! They are very difficult to maintain, especially in big programs. Never use them ”use constants instead.) For example, let's say that your business user has decided that its users should never select more than 3 storerooms out of the 10 that are available to fill the requisition . You must check for the number wherever storerooms are chosen . Instead of hard-coding the value, you could make a constant:

Const MAX_STOREROOM_PICKS_ALLOWED = 3

Then, when the business area finds out that people actually can pick up to five storerooms, you only need to go back and change the only constant value and then resave your LotusScript program. All will be well.

The final reason for using constants is that program code is much easier to read. For example, using the storeroom example, which code snippet communicates more information?

If pickCount > 3 then... If pickCount > MAX_STOREROOM_PICKS_ALLOWED then...

If you didn't know what the 3 was for, you'd have to search the remaining code to try to figure out its meaning. By using a well-named constant value instead, it is very clear what is being checked for, without having to burrow down into layers of code.

Variables

A variable in LotusScript is a holder of information that is changeable later (unlike constants). You must do two things to variables: name them and specify their data type. Then you can use them by changing their values (data).

As stated previously in the "Communication" section, variables should be named in a manner that clearly indicates what they are for, that is concise , and that is atomic (that is, has only one meaning).

Here are some examples:

Some people also use what is known as reverse Hungarian notation. You see this quite a bit in Microsoft code. In this case, the variable name is " decorated " with indicators that communicate more than just its name. It also communicates its data type (see the upcoming section "Data Types").

Here are some examples:

The advantage of reverse Hungarian notation is that as you read the code, you do not have to refer back to the variable declaration to determine what data type it is. The only drawback to this technique is that if the data type of the variable changes, you should also change its notation. Many people don't do this, which results in poor communication. I use reverse Hungarian notation in LotusScript; I do not use it anywhere else.

Data Types

A data type is a word that tells the computer what you would like to work with. LotusScript has the data types shown in Table 14.1.

Table 14.1. Data Types

Data Type Value Range Size
Byte (Notes/Domino 6) to 255 1 byte
Boolean (Notes/Domino 6) (false) or “1 (true) 2 bytes
Integer -32,768 to 32,767 2 bytes
Long -2,147,483,648 to 2,147,483,647 4 bytes
Single -3.402823E+38 to 3.402823E+38 4 bytes
Double -1.7976931348623158E+308 to 1.7976931348623158E+308 8 bytes
Currency -922,337,203,685,477.5807 to 922,337,203,685,477.5807 8 bytes
String String length (0 to 32K chars) 2 bytes/char
Variant* Contains anything 16 bytes
Note: Good programmers rarely use Variants. They are sometimes required, but they are also slow and bulky.

A variable is declared in three parts all on one line of code. Part one is the Dim statement, which stands for Dimension, a holdover from BASIC. The next part is the variable name. These two parts are required. Good programmers, however, will declare the last part, the data type. (Note: If the data type is not declared, the system assumes that it is a Variant.)

Here is the layout and an example:

Part One Part Two Part Three
Command Name Type
Dim sFirstName as String
Dim nArrayIndex as Integer
Dim cFinalAmount as Currency

To review, the first part, Dim is a LotusScript command that tells the system that you want to make a new variable. The next part tells the system what the name of that variable should be. The third part tells the system what data type that variable should be.

TIP

You can declare more than one variable per Dim statement. There is also a "gotcha" when putting variables on one line if you are not careful.

Here is an example of the previous three variables declared on one line:

Dim sFirstName as String, nArrayIndex as Integer, cFinalAmount as Currency

Now, given the four C's rules stated previously, do you think this is a good idea? It works, but you are putting on one line variables that probably have nothing to do with each other. This violates cohesion. For readability, it is better to put them on separate lines unless they relate. Also, a "gotcha" can come up if you put variables on the same line.

Consider this case:

Dim cSubTotal as Currency Dim cTaxAmount as Currency Dim cFinalAmount as Currency

Now, for review, you should be able to determine that there are three variables here, and all of them are of type Currency ”that is, they contain numbers that are suitable for making financial calculations.

These are clearly related , so putting them on the same line does not violate any of our construction rules (cohesion, in this case). So, you could do it like this:

Dim cSubTotal, cTaxAmount, cFinalAmount as Currency

This line is a "gotcha"! Contrary to what you might think, the first two variables are not defined as Currency. They are actually missing their data types. Thus, as was pointed out already, they will default to type Variant, which is a large and slow data type.

If you want to put them on the same line, they should be defined like this:

Dim cSubTotal as Currency, cTaxAmount as Currency, cFinalAmount as Currency

This "gotcha" snags a lot of developers if they are not clearly aware of the format of the Dim command.

So, as long as you are aware of the "gotcha" and you practice sound construction (cohesion, in this case), putting them on the same line is recommended.

 

Now for some examples. If you use the Chapter14.nsf database (LotusScript Editor) from the Web site with the following examples, you should get the same results. Note that the code area is a rich-text field. You can bold lines, change their colors for emphasis, and so on. The program converts the rich text to text and executes it.

Enter the following into your LotusScript Editor:

Option Declare Dim bTestVariable as boolean

This defines a variable, bTestVariable (with name decoration) as Boolean. If you click Run in the LotusScript Editor provided on the Web site, this should happen: Nothing.

Test 1: Add this line under the previous Dim line:

bTestVariable = "Hello"

Now if you now click Run, you should get a Type Mismatch error. Why did you get a Type Mismatch error? What does it mean? You told the computer on the Dim line that the variable bTestVariable was supposed to be a Boolean. From the previous chart, if you check Boolean, it is supposed to be true or false ( “1 or ). But you told the computer that it should be equal to "Hello" . "Hello" is of type String, a collection of characters . The variable is of type Boolean. The types do not match. Thus, the poor computer is confused , panics, stops processing, and tells you "Type Mismatch."

Test 2: Now change "Hello" on the third line to “1 . Click Run.

As before, nothing should happen.

Test 3: Under the bTestVariable line, add this line:

bXYZ = -1

Click Run. What happened ? You should have gotten "Variable Not Declared: BXYZ." Why did you get this? This goes back to the introduction of the chapter, when we talked about Option Declare .

Option Declare

Option Declare tells the computer that no variable can be used without first being declared (that is, you need to Dim it somewhere before it is used).

Option Declare forces developers to declare all their variables. When the declare their variables, they should also define the data type. This forces developers to think about what they are doing. This is called strict typing.

No disadvantages exist to doing this (unless you are lazy or you don't care about writing good code). However, there are many benefits:

Regarding the second benefit here, catching misspelled variables is a nasty bug to search for. Consider that a programmer uses the variable name PolicyNum in one part of the program but then later uses PolicyNumb without realizing it. The old saying that you can look at something a thousand times and not see that something obvious applies here. If Option Declare is turned on, the PolicyNumb errors out quickly.

TIP

In Domino Designer, right-click in the Programmer's pane and select Programmer's Pane Properties. On the second tab you will find the option Automatically Add Option Declare.

 

Variables hold values. This will be demonstrated by way of an example as described in Test 4. In this example, the LotusScript Editor tprint command sends text output to the editor's screen. Note that this is not a LotusScript command; it works only in the editor.

Test 4: Let's resume the previous example, which the "Variable Not Declared" error returned. Remove the bXYZ line. In its place, put this:

tprint bTestVariable

Then click the Run button. In the output window, you should see the result of true , which is what you assigned to the variable (remember that “1 = true ).

Test 5: Change the line where bTestVariable is assigned. Change it to (zero) instead, and rerun the program. You should get false as your answer (remember that = false ).

Test 6: Now change the value to 5 . What do you get? You get true . Why? LotusScript actually has this rule: is false , anything else (that is, not ) is true . LotusScript converted your 5 to a “1 ( true ). You can test this by changing your tprint line to this:

tprint bTestVariable + 3

You might expect to get back 8 because you made bTestVariable equal to 5 . But because bTestVariable can be only 0 or “1 , LotusScript had to convert it; what you should have seen is 2 (-1 + 3) = 2 .

One more variable type must be demonstrated: the String.

Test 7: Add this to the example:

Dim sTestString as string STestString = "Hello" tprint sTestString

Now click Run. Assuming that you haven't changed anything, you should get the value 2 from Test 6 and then the output "Hello" .

Test 8: Alter the tprint line that you just added so that it looks like this:

tprint sTestString + ", World"

Results should now be "Hello, World" instead of just "Hello" . This demonstrates that you can add two strings together.

Test 9: There is another way to do the exact same thing ”at least, on the surface:

tprint sTestString & ", World"

If you click Run now, you should get the same results as before.

Test 10: What happens if you put a number there instead of a String? Change the line to read as such:

tprint sTestString & 99

When you click Run now, you should get "Hello99" .

Test 11: This test demonstrates the difference between using + and & . Change the & back to a + so that the line reads like this:

tprint sTestString + 99

When you click Run now, did it work? Nope. You received a "Type Mismatch" error. Why? The + works only with types that are the same. So, it can add two Strings together and add two numbers together, but it cannot add a number to a String (and vice versa). The & , on the other hand, always converts its parameters to Strings. That's a useful tip to know.

While we are talking about strings and conversions, you should be aware that a number of converters are built into LotusScript to handle conversions from numbers to Strings and vice versa:

At this point, you might want to use the LotusScript Editor application (Chapter14.nsf) to test and work with the other data types and the conversions listed. When you are comfortable with how variables are declared, their types, and how they can be used to store data, you can proceed to more advanced features of LotusScript. However, we will return to the subject of variables one last time before ending this section, to talk about variable scope.

Loops

Looping commands built into LotusScript permit code to be executed repeatedly until certain conditions are met.

Eight kinds of loops exist in LotusScript:

NOTE

In previous releases of Domino, one of the reasons given for using LotusScript was to do loops. With the addition of @While , @DoWhile , and @For in the Formula language, this is no longer the case.

 

The major differences in the Do...Loop family of loops are where the ending conditions are checked and how ending conditions are checked.

Test 12: Open the LotusScript Editor database (Chapter14.nsf) and make a new Program document. In that document, enter the following code:

option declare dim nCount as integer tprint "DO UNTIL" nCount = 0 do until nCount = 5 tprint nCount nCount = nCount + 1 loop tprint "DO WHILE" nCount = 0 do while nCount < 5 tprint nCount nCount = nCount + 1 loop tprint "LOOP UNTIL" nCount = 0 do tprint nCount nCount = nCount + 1 loop until nCount = 5 tprint "LOOP WHILE" nCount = 0 do tprint nCount nCount = nCount + 1 loop while nCount < 5

Click Run. You will note that each returns the same result: a list of numbers ” 0,1,2,3,4 . The important thing to recognize is how the end cases are tested between each type and where they are tested. For example, one of the most common things in Lotus Notes is to loop through a group of documents. Here is how many people write this loop (it should not be a surprise because it is based on the Lotus Help file):

Dim db As New NotesDatabase( "Shadow/TPAS", "resdata.nsf" ) Dim view As NotesView Dim doc As NotesDocument Set view = db.GetView( "Programs" ) Set doc = view.GetFirstDocument While Not ( doc Is Nothing ) doc.updateTime = now doc.save true,true,true Set doc = view.GetNextDocument( doc ) Wend

The goal here is to prevent the execution of the loop if the first document is nothing . With loops that test at the end, this would not be possible ”the loop code would execute first and then the test would fire. This code accomplishes this task. However, notice the While Not (doc Is Nothing) . This is a double negative and is hard to understand; it violates the communication principle discussed earlier.

By using a different loop, you can communicate your intentions much more clearly and can accomplish the same thing:

Dim db As New NotesDatabase( "Shadow/TPAS", "resdata.nsf" ) Dim view As NotesView Dim doc As NotesDocument Set view = db.GetView( "Programs" ) Set doc = view.GetFirstDocument Do until ( doc Is Nothing ) doc.updateTime = now doc.save true,true,true Set doc = view.GetNextDocument( doc ) Loop

This loop is easier to read: Do the code in this loop until the doc variable is nothing . So, when you get the first doc , if it is nothing , the loop never executes; if it is something , it executes at least once.

NOTE

nothing is a special reserved word in LotusScript used to designate an object that has no value.

 

ForAll...End ForAll is used to iterate through a list, an array, or any container that can be iteratively processed . The special thing about ForAll is that it knows where to start and where to end; you do not need to worry about that. Here is an example program that processes a list:

option declare Dim vehicleInfo List As String vehicleInfo("Year") = "1995" vehicleInfo("Make") = "Dodge" vehicleInfo("Model") = "Ram" vehicleInfo("VIN") = "1B5HC17Z3553S2928" ForAll info In vehicleInfo tprint ListTag(info) & " = " & info End ForAll

If you enter this into the LotusScript Editor and run it, you should get the following as output:

9/9/2002 5:07:07 PM> Year = 1995 9/9/2002 5:07:07 PM> Make = Dodge 9/9/2002 5:07:07 PM> Model = Ram 9/9/2002 5:07:07 PM> VIN = 1B5HC17Z3553S2928

NOTE

In the previous example, the info variable was not declared. This is the correct syntax. If you declare it beforehand, you will get an error.

 

As the ForAll goes through the container ”in this case, a list ”it puts each value of the current element into the reference variable ”in this case, info .

I chose to use a list as the container in this example because the ForAll loop has a special relationship with lists. You can see that relationship in this example because it makes use of the LotusScript command ListTag . ListTag returns the name of the current list element in a ForAll loop, instead of the value of the current element (which is the reference variable). The ListTag command is valid only inside a ForAll loop and works only with the reference variable. A more detailed discussion of lists is covered a little later.

A For...Next loop is used to loop a certain number of times. In fact, the variable that is used in a For loop is called a counter variable.

Here is an example of a For...Next loop:

option declare dim nCounter as integer for nCounter = 1 to 15 tprint nCounter next

If you enter this code into the LotusScript Editor, your output should be the numbers 1 to 15 . A For...Next loop does have an optional stepping parameter, which is the value to increment the counting variable. By default, this is 1 . However, you can change it, as in this example:

option declare dim nCounter as integer for nCounter = 1 to 15 step 2 tprint nCounter next

This program counts, but by increments of 2 rather than 1. Therefore, this program lists the odd numbers from 1 to 15.

The Lotus documentation suggests using Do While instead of the While...Wend loop. This gets back to the consistency issue. Because Do While does the same thing as While...Wend , and because the Do...Loop family of loops has more variations, it is better to be consistent and just use the Do While...Loop when you need this type of loop.

Execution Control

Besides looping, you can control execution of a program in two ways. (There's a third, less desirable, method, which we will not cover.) The first way is via IF-THEN-ELSE statements; the second is via SELECT CASE statements.

Here is an example of an IF-THEN-ELSE statement:

Option Declare dim nCount as integer nCount = 5 if nCount = 1 then tprint "The selection was 1" elseif nCount = 2 then tprint "The selection was 2" elseif nCount = 3 then tprint "The selection was 3" else tprint "It wasn't a valid selection!" end if

The output from this would be "It wasn't a valid selection!" because 5 is not covered by any of the elseif statements or the leading if statement. If you change the nCount to 1 , you should get "The selection was 1" as a result.

You can do the same thing with a SELECT CASE statement, as follows :

Option Declare dim nCount as integer nCount = 5 select case nCount case 1: tprint "The selection was 1" case 2: tprint "The selection was 2" case 3: tprint "The selection was 3" case else tprint "It wasn't a valid selection!" end select

One thing to note as a good programming tip: If you have an IF-THEN case, you should always try to understand the ELSE part because not understanding what you are excluding is often a source of bugs . Consider this example:

If nYear < 2003 Then Print "Valid Year" End If

Well, what if it isn't less than 2003 ? Shouldn't something be done? Perhaps a state variable should be set? An error message should be displayed? Often you'll see just an IF-THEN in a program without the ELSE , which should warn you that something might be missing.

Containers

The term container is used to describe variables that can hold more than one value. Containers may also hold other containers.

These containers are supported in LotusScript:

An array is a collection of other variables of the same data type. An array is indexed by a number, and this index is called a subscript. An array can have up to eight indexes or dimensions on it. See Listing 14.1.

Listing 14.1 A Simple Array

option declare dim processor(1 to 5) as string processor(1) = "486DX4" processor(2) = "Pentium" processor(3) = "Pentium II" processor(4) = "Pentium III" processor(5) = "Pentium IV" dim nCount as integer for nCount = 1 to 5 tprint processor(nCount) next

The array is first defined by the Dim statement. This is a variable with the name processor . It is of type String. But it is a special variable because of the 1 to 5 piece that is added next to the name. That changes the variable type from a simple variable that holds only one thing (called a scalar ) to a container, which is a variable that holds more than one thing. How many things can processor hold? The answer is five.

After you have told the system what you want to create, the next step is to put some data into the new array. You do this by referencing each element of the array by its index or subscript.

So, processor(1) is the first element of five in the array processor. By using the (1) , you tell the computer that you want the first one. Thus, processor(1) = "486DX4" sets the first element of the processor array equal to the string "486DX4" . You initialize your data for each element.

Next you will print it. How do you loop through all the data elements in this array? Well, you know how many elements there are, you know the first and last elements, and you will go through them only one way. Therefore, the best bet here is to use a For...Next loop, which is what we have done.

The key item in the For...Next loop is the tprint line. Here you begin to combine several of the concepts reviewed previously. If you do not understand this line, review the following and then go back and read the previous sections.

In this line, you will print the value of a variable called processor . But processor is an array ”that is, it holds more than one value. To determine which value to print, you must tell the array your choice. However, you do not want to hard-code each and every print statement. Therefore, you use the loop and the variable nCount to start at 1 and go to 5 . Instead of putting a hard-coded number into the print statement, you use the variable, like this:

Tprint processor(nCount)

This line tells the system to fetch a value from the variable processor . The item number that it should fetch is the current value of nCount . The For...Next loop moves the value of nCount from 1 to 5 . Thus, all five elements of the array are processed. See Listing 14.2.

Listing 14.2 A Multidimensional Array in a More Complex Example

option declare dim salesZone(1 to 3) as string dim salesPerson(1 to 3, 1 to 4) as string salesZone(1) = "North" salesZone(2) = "Central" salesZone(3) = "South" salesPerson(1,1) = "Roger Smith" salesPerson(1,2) = "Tom Reynolds III" salesPerson(1,3) = "Ellen Daultry" salesPerson(2,2) = "Thomas Fenwick" salesPerson(2,3) = "Tim Roscrans" salesPerson(3,1) = "Sarah Smith" salesPerson(3,2) = "Tiffanie Reynolds" dim nSalesZoneIndex as integer, nSalesPersonIndex as integer for nSalesZoneIndex = 1 to 3 tprint "ZONE: " & salesZone(nSalesZoneIndex) for nSalesPersonIndex = 1 to 4 tprint " " & nSalesPersonIndex & " = " & salesPerson(nSalesZoneIndex, nSalesPersonIndex) next next

The output in the editor from this example should be as follows:

9/9/2002 6:25:02 PM> ZONE: North 9/9/2002 6:25:02 PM> 1 = Roger Smith 9/9/2002 6:25:02 PM> 2 = Tom Reynolds III 9/9/2002 6:25:02 PM> 3 = Ellen Daultry 9/9/2002 6:25:02 PM> 4 = 9/9/2002 6:25:02 PM> ZONE: Central 9/9/2002 6:25:02 PM> 1 = 9/9/2002 6:25:02 PM> 2 = Thomas Fenwick 9/9/2002 6:25:02 PM> 3 = Tim Roscrans 9/9/2002 6:25:02 PM> 4 = 9/9/2002 6:25:02 PM> ZONE: South 9/9/2002 6:25:02 PM> 1 = Sarah Smith 9/9/2002 6:25:02 PM> 2 = Tiffanie Reynolds 9/9/2002 6:25:02 PM> 3 = 9/9/2002 6:25:02 PM> 4 =

Notice that you did not fill in all the values, but that didn't matter. Why? The system initializes the variables if you do not. There are default initializations for each variable type. Numbers, bytes, and Booleans are set to , Strings are set to an empty String ( "" ), and Variants are set to Empty .

Also, the previous example included what is called a nested loop, which is a loop inside another loop. The outer loop printed the sales zone. The inner loop printed the people in that sales zone.

A list is a one-dimensional collection of elements that are indexed by a name rather than by a number, as arrays are. As such, they are very useful in making key-value pairs. The values of lists can be anything from numbers to objects.

Lists are made up of three pieces: the listname , the listtag , and the value.

listname(listtag) = listvalue

Traversing a list is typically done via the ForAll loop. Here's an example:

Option declare Dim employees List As String employees("V14532") = "James Pomeroy" employees("K14532") = "Thomas Rice" employees("K14533") = "Betty Smith" employees("P20011") = "Lori Payne" tprint employees("P20011") tprint employees("K14532") tprint employees("V14532")

The output (from the LotusScript Editor) should be this:

9/9/2002 7:23:14 PM> Lori Payne 9/9/2002 7:23:14 PM> Thomas Rice 9/9/2002 7:23:14 PM> James Pomeroy

A Variant is a unique data type in LotusScript. It can hold anything. Because of this, it is usually slow. Variants should not be used unless necessary (that is, when needed to hold other containers) or unless there is no better alternative. For example, working with the contents of document fields is sometimes most easily done with a Variant and a ForAll loop.

Here's an example:

option declare dim vTest as variant vTest = "Hello" tprint vTest vTest = 5 tprint vTest vTest = true tprint vTest

The previous program code should produce the following output:

9/9/2002 7:29:22 PM> Hello 9/9/2002 7:29:22 PM> 5 9/9/2002 7:29:22 PM> True

Now, pay attention to what just happened. Remember before when you tried to assign a String ( "Hello" ) to a Boolean type? We got the response "Type Mismatch Error." But here there were no errors, even though you assigned completely different things to the same variable. The reason is that a Variant can legally contain anything ”which also makes it hard to tell what should be in a Variant at any given time. Because of this, Variants are hard to debug and understand in code. This is an example of what is known as weak typing.

It is a good LotusScript programming practice to avoid weak typing because it does not communicate to the system exactly what you are trying to do. If you communicate to the system exactly what you are trying to do, it can tell you when you are not doing what you intend ”as in the "Type Mismatch Error" from before. The extensive use of variants in a program indicates poor design and shows that you don't understand the problem and solution. Variants usually lead to coupling within the same routine as the same variable is used for completely different things.

Therefore, you should avoid using Variants unless you certainly need them ”and in many cases only Variants can do the job. In those cases, it will be clear what the intended use of the variable is. In all other cases, variables should be declared with a specific type.

In addition to built-in types, developers may define their own types. This allows you to extend LotusScript as appropriate.

Here's an example:

option declare type Employee firstName as string lastName as string employeeNumber as integer end type dim EmployeeRecord as Employee EmployeeRecord.firstName = "John" EmployeeRecord.lastName = "Smith" EmployeeRecord.employeeNumber = 99 tprint EmployeeRecord.firstName tprint EmployeeRecord.lastName tprint EmployeeRecord.employeeNumber

The output from this example should be the firstname , the lastname , and then the employeenumber .

When you have defined a variable with your own custom type, you can use it similar to any built-in variable. The only difference that you might have noticed is the dot notation. This is used not only here with user-defined data types; you'll see it again when we cover classes and objects. The dot notation means that the variable might have different parts. This is different from arrays or lists, which access their elements by either an index number or an index name. Here the element is accessed directly by its name. So, the format is as follows:

variable "." Element

EmployeeRecord.employeeNumber gives you the employeeNumber from the EmployeeRecord variable. You can serialize (that is, save to disk) records using the LotusScript I/O command Put , and you can read them in using Get .

An object is an in-memory instance of class as described later. The key to understand is that there can be many objects in memory, all controlled by the same class definition. Classes define the attributes and actions of objects. Objects are created to provide those attributes and actions to programs running in memory.

A class is like a user-defined data type, except that, in addition to utilizing different variables and types to build a record, it includes subroutines and functions. We will cover classes and objects later in this chapter.

A component is defined as an object (as done previously), but one that is an independent binary program in its own right. Launching and controlling Microsoft Word is an example of one program using another as a component. This specific example is called OLE automation, but it amounts to the same thing: using Word as an object for Notes.

Example: Launch MS Word from Lotus Notes (this is a good example of a valid use of a Variant):

Dim v As Variant Set v = createobject("word.application") v.visible=True v.documents.add Set v = Nothing

Let's analyze this example line by line so that you can understand what is going on.

Line 1: We establish v as the Variant to hold the reference to the MS Word component. We cannot do what is known as early binding because LotusScript does not have a defined type for MS Word. If we could, we would not declare v as a variant, but instead we would declare it as MSWORD or whatever its data type is. Because we cannot do that, we will instead do what is known as late binding, defining the type when it is assigned. That is why we must use a Variant here.

Line 2: Here is where we create the MS Word object from the system and assign it to the variable. This is called late binding. The LotusScript command CreateObject can create any component registered in the Windows Registry and hand its reference back to the variable. When this line is finished executing, MS Word is actually running on the computer, but you cannot see it.

Line 3: On this line, we know that the MS Word component has a property called visible (we know that from the MS Word VBA Help). If this is set to true , Word appears; otherwise , it remains hidden. Word has an excellent print engine, and sometimes it is useful to print heavy print jobs via Word rather than Notes, but without the user knowing what you are doing. In that case, we would leave Word invisible ( visible = false ).

Line 4: Word has another property called documents , and documents itself has a method (or command) called add . Guess what happens when you call the add method of the documents property of MS Word? That's right, it adds a new document to MS Word.

Line 5: We will leave Word running, so we will not shut it down. However, we need to release the memory used by Lotus Notes to connect to Microsoft Word. This is accomplished by setting the Variant back to nothing , which is a LotusScript keyword meaning, well, nothing! If you do not do this, your program will start to leak memory. This is a required step for component programming in LotusScript.

Subroutines and Functions

Subroutines and functions permit developers to reuse code that has been written previously. Code reuse saves time and testing costs, and it increases the chances of success. It also promotes consistency in design and development.

In addition to allowing a developer to reuse code, subroutines and functions permit the implementation of information hiding, the technique of putting code together that belongs together under a clear name that accomplishes a single task. This code then can be used by calling the name that you gave it. Then you no longer need to worry about how it runs inside; you can focus on how to use it to solve the problem at a higher level (this is called abstraction).

A subroutine is a collection of LotusScript commands all put under a common name that accomplishes a single task (remember the rules for naming and cohesion).

For example, let's say that you need to print the number of kilobytes from a given file size in bytes. You could keep repeating the same formula in your LotusScript code. But that leads to maintenance problems, exposes a formula that should be hidden, and does not permit you to reuse your code. You can write a subroutine to handle the task of printing the size of files in kilobytes. To get the number of K in a file, divide the file by 1024. (I am using the definition of 1K = 1024 bytes, 1M = 1024K, 1G = 1024M, and so on.) Then print the result. Here is the routine:

option declare sub printInKilobytes(filesize as long) tprint cstr(filesize/1024) & "Kb" end sub

If you enter this into the LotusScript Editor provided as chapter14.nsf on the Web site and click Run, nothing should happen. All you have done is define the subroutine.

The subroutine is defined first by the command sub , which says that a subroutine is being declared. Next comes the name of the subroutine, printInKilobytes . The last part, the parameter list, is optional. In this example, it is the (filesize as long) piece. The parameter list is similar to using Dim in a program: It says that this subroutine is expecting to receive some value of type Long, and that inside this subroutine that value will be referred to by the name of filesize . It is important to note that filesize works only inside this subroutine. When we discuss variable scope, this will be explained more fully.

That defines the header of the subroutine, but not the body. The body of the subroutine in this example is only one line:

tprint cstr(filesize/1024) & "Kb"

This line says to convert to a String ( cstr ) the following equation (filesize divided by 1024) and then append another string ( "Kb" ) to it. After all that is done, it says to print it using the tprint command.

The last line of the subroutine is the end sub line. As you can guess, this indicates the end of the subroutine.

If you add this line to the program after the end sub line and then click Run, you should get some result:

PrintInKilobytes 1024

The result should be 1Kb .

Try changing the number and verifying the results as you run it.

Our good programming rules say that a subroutine should perform only one clear, concise function and should be named accordingly . However, this subroutine does two conceptual things: It converts a number and prints it, and its name almost hides the dual functionality. This violates our good programming standards. The best way to get around this is to introduce a variant of the subroutine, called the function. A function is exactly the same as a subroutine, except that it returns a value to the calling program.

Let's change the code so that it does not violate the good programming rules:

option declare function convertToKilobyteNotation(valueToConvert as long) as string convertToKilobyteNotation = cstr(valueToConvert/1024) & "Kb" end function tprint convertToKilobyteNotation (1024)

Notice that this function is almost identical to a subroutine, except for the name: It is more accurate about what it is doing. Let's review this function line by line as we did with the subroutine.

On the first line, instead of sub is the LotusScript command function , which tells the system that a function is being defined. Next is its new name. Then comes the option parameter list again, with the same parameter. This time, however, we have given it a more generic name, valueToConvert because this function doesn't have to be tied to only converting file sizes (that would be an example of conceptual coupling). It can convert any numeric value to kilobyte notation. The last part of this line is different than the subroutine. This is the as string part, which tells that this function returns a value of type String. This part is optional. (If you will define a function without a return value, you should just use a subroutine.)

The body of this function is also different than the body of the subroutine:

convertToKilobyteNotation = cstr(valueToConvert/1024) & "Kb"

Notice that the name of the function is set equal to the formula for converting to kilobyte notation. If you do not set the name of the function, nothing is returned by the function. This is how the data gets returned to the calling routine.

The last line is just the end function line, which ends the processing of the function.

The last line of the program, the tprint line, prints the result of the call to this function. Now the printing is separated from the converting, and it is clear what each part is responsible for.

Classes and Objects

Classes are used to encapsulate the properties (variables) and methods (subroutines and functions) of some conceptual "thing" in one spot. When a class is initialized in memory, it is then called an object. There might be hundreds of objects that are all of the same class. For example, there is a built-in LotusScript class called NotesDocument . You could have thousands of NotesDocument s (these are the objects), but you would have only one class: the definition of those NotesDocument s.

Let's go back to our example that launches Word. It would be really nice to hide the MS Word “specific stuff from the rest of the program and deal with only an object that is made for our purposes.

Let's build a small class to handle MS Word. Here is the code that can be entered into the LotusScript Editor (the Chapter14.nsf database from the Web site).

class WordApp private m_vApp as variant '........................ sub addDocument m_vApp.documents.add end sub '........................ sub new(bVisible as boolean, bAddDoc as boolean) set m_vApp = createobject("word.application") m_vApp.visible = bVisible if bAddDoc then addDocument end if end sub '........................ sub delete if m_vApp is nothing then 'already nothing, do nothing else set m_vApp = nothing end if end sub end class dim oWordApplication as WordApp set oWordApplication = new WordApp(true,true)

After we have defined the class, launching Word involves only two lines of code: dim for the WordApp and set for the WordApp . Behind those two lines of code however, is the Class WordApp .

We will review each of line of the Class statement so that you can get a feel for how classes work.

The first line contains the command Class , which tells the system that a class will be defined. The second part is the class name ”in this case, WordApp . Then comes an optional part, as , and then another class name. This is for inheritance, but we will pass on that for now.

After the class definition line comes the variable declarations, also known as the properties of the class. The keyword private indicates that this variable cannot be seen outside the class (for example, oWordApplication.m_vApp would error). The m_ in front of this variable name is an example of reverse Hungarian notation. It signifies to the reader that this variable is a class member variable. It is available all subroutines and functions in the class, but not to the world. This is an advantage of object-oriented programming. We will cover this when we cover scope.

This class member variable will hold the Word application reference returned by the CreateObject method. We make it a class member variable so that it will last as long as the object lasts.

Next is a routine that adds a document to the Word application. This hides the actual code needed to add a document to Word, so the developer of the class is the only one who needs to know how Word works internally; the developers that use this class instead do not.

Now we come to the first of the two key parts of all classes: the new subroutine. If the new subroutine is defined, it is called when this class is used to create an object in memory. Here we initialize Word and, depending on the options that the calling program has chosen, add a document and choose whether to make Word visible. In this example, the routine is actually called when the set command is issued, as in the following:

set oWordApplication = new WordApp(true,true)

After the new subroutine comes the delete subroutine. This is also a built-in subroutine that can be defined, if you want. It is called before the object is destroyed , either explicitly with the LotusScript delete command or implicitly when the object goes out of scope.

The delete subroutine in a class is a great location to do cleanup work, as in this case when we check to see whether the m_vApp variable is set to nothing . If it is not, we set it to nothing because we want to avoid memory leaks. When this is defined in the class delete subroutine, it takes care of itself for any objects created from this class.

Obviously, this class can become far more sophisticated than it is, providing an interface to MS Word that other developers can use without ever having to know MS Word's VBA. I personally have built classes for Word, Excel, CDO, Outlook, Access, Internet Explorer, MSXML (for XML parsing), MSHTTP (for sending HTTP requests ), PowerPoint, and WSH (Windows Scripting Host).

Having a nice library of OLE automation classes permits you to extend the reach of Lotus Notes to the entire Windows environment. And that brings up our next topic, script libraries.

Script Libraries

A script library is a collection of classes, subroutines, functions, or constant declarations. It is used to hold code that has some commonality within it.

To use a script library, you go to the Options area of whatever design element you are working with (the form, view, button, and so on) and type the command USE followed by the script library name.

Here's an example:

Option Declare Use "MS OLE AUTOMATION"

This loads my OLE automation library that contains the classes for managing Excel, Word, and so on. After that, I can begin to use my classes in whatever section I just stuck the Use command.

NOTE

Most of the time, you'll put the Use command at the highest level possible in whatever design element you are using so that your definitions get the widest availability.

 

Variable Scope

Variable scope is the highest level at which a variable can be seen by other design elements.

For example, think of a pyramid. Pretend for a moment that you can look only up (for LotusScript, this is not pretend ”it's how it works). Now if you are at the bottom of the pyramid, you can see everything above you. But if you are at the top of the pyramid, you can see nothing above you.

Variable scope is like the pyramid. A global variable is a variable that is defined at the top of the pyramid; everything under it can see it and, therefore, change it. A local variable is a variable that is defined at the bottom of the pyramid, as in a subroutine or function. Nothing can see it except the code immediately around it.

To translate that into the LotusScript world, declaring a variable in the Form Globals Declarations section is like being at the top of a pyramid: Everything in the form (that is, everything under it) can see that variable and, therefore, can change it, which is usually very bad. Why is it usually very bad? All sorts of subroutines and functions and code snippets could be written to change that variable without any thought to each other. The more code there is in the form, the more this is likely to occur. And if the initial design of the code has been forgotten, all these little code pieces that change that global variable will probably make a change to it that invalidates it for some other piece of code.

In a nutshell , only things that truly will be shared throughout the form (for example, an ODBC connection, session, or state variables) should be in the global declarations. Everything else either should be declared in a subroutine or function (local variables) or should be declared in a class (class member variables). In all the years that I have been developing Lotus Notes, the neglect in following basic scope rules has been the most common source of problems.

Here is how variables ought to be declared:

A good rule of thumb is this: Variables should be declared closest to where they are used.

Категории