Day 20

Mastering Object-Oriented Programming

by A. A. Katz


CONTENTS

Today you'll concentrate on gaining an in-depth understanding of object-oriented programming. In previous days you've used these concepts to build forms and applications. However, to take advantage of the remarkable productivity offered by object-oriented programming, you'll need to get truly comfortable with the terminology and concepts that power IntraBuilder's OOP language.

JavaScript, as originally conceived by Netscape, is an object-based (not object-oriented) language. In client-side JavaScript you can use and manipulate existing objects, but you can't create new objects nor derive objects from existing ones. These limitations are imposed by JavaScript's design as a browser-based HTML language. There's just no place to build or store a sophisticated library of custom objects.

Because IntraBuilder runs on the server, it doesn't suffer from the same limitations. Borland has equipped it with a robust, fully object-oriented language whose innate reusability can cut your programming time by half-or more.

Let's start with the three main characteristics that define a truly object-oriented language such as IntraBuilder JavaScript: encapsulation, inheritance, and polymorphism.

Encapsulation

All true objects must be encapsulated. They should be self-sufficient, self-contained, and totally protected from any other object. Encapsulation is a common-sense issue. You can't have a button on one form accidentally show up on three others. You can't have data entered into one input form leak onto another. All the data within a form is encapsulated by the form, and contained and protected from all other objects. Without encapsulation, you can't even have more than one button in an application. Imagine clicking one button on a form and having all buttons fire! Though they share the same form, each button is totally encapsulated from every other button. That doesn't mean that they're not visible or addressable. They are. But changes made to one button do not impact any of the others.

TIP
When you design your IntraBuilder programs, give serious thought to encapsulation. The rules have changed. In procedural programming, it was considered a sin to code a function more than once. In object-oriented programming, every stand-alone function creates one more external dependency, one more breach in your wall of encapsulation. At certain times (particularly with large reusable functions), you will want to use callable non-class functions because they're too complex or too reusable to paste into every form that requires them. Just remember that any object that uses external functions is that much less portable than one that doesn't!

Inheritance

Inheritance is the characteristic that provides the most benefit in object-oriented programming. In the past, you reused code as much as possible. Most of us built huge shared function libraries. Although they saved a good deal of typing, they had a major drawback: Their reusability was severely restricted. To adapt a function for a new use, you had to rewrite the function with special tests for the new use or copy the code to create an entirely new function. Rewriting a long-working function requires a whole new test cycle, and not just for the code that's been changed. You've got to test the rewritten code against legacy applications in which the original function was used in order to be sure you haven't broken anything. Copying code to adapt for a new use loses most of the benefits of a function library. Every time you make a change (or correct an error), you have to find every far-flung copy of the function and make the changes over and over again.

Inheritance, on the other hand, lets you reuse an object without touching the original code. All of the characteristics of any object are brought forward to its child, its grandchildren-to all the generations derived from it. The parent object contributes all its attributes and behavior to each succeeding generation, yet remains an independent entity.

What a huge improvement over the old function library! You can create an entirely new use for an object without risking any legacy uses. You don't copy any code; in fact, you only code the attributes and behaviors that have changed.

As in human inheritance, characteristics are not just inherited from the prime predecessor, but they can also be obtained from any of the generations between. A child can inherit blue eyes from a parent, even though the grandparent's eyes were brown. The same is true for JavaScript objects. Each object you create through inheritance can stand as the parent of your next level of inheritance. Figure 20.1 shows the different levels of inheritance.

Figure 20.1 : Deriving custom button classes.

In Figure 20.1, Wide Button inherits all the attributes and behaviors of Normal Button: 3-D, gray background, black HTML, and clicks when pressed. The only attribute that has changed is the width of the button. You now have two objects: Normal Button and Wide Button.

The Next Customer Record Button is derived from Wide Button. It inherits all the characteristics of Wide Button, including its width, as well as all the characteristics of Normal Button not overridden in Wide Button. This new object includes a method to move the rowset forward one row in the Customer table. It has behavior new to its own generation.

Keep in mind that although two of these buttons inherited characteristics from their forebears, they remain totally independent entities. If you were to try to change the width of the Next Customer Record Button, it would be that button's width you're changing, not the width of the Wide Button from which it is derived. You would be accurate if you thought of inherited characteristics as defaults-because they are.

Three terms are used when discussing inheritance. An object created through inheritance from another object is said to be derived from its parent object. A derived class is called a subclass. The parent class is called a superclass. In the following class declaration, MyCustomForm is derived from Form, MyCustomForm is a subclass of Form, and Form is the superclass of MyCustomForm:
class MyCustomForm extends Form

TIP
You've already encountered one of the biggest benefits of inheritance when you created your custom form class. Because objects are derived on-the-fly in IntraBuilder, any change made to the parent, grandparent, or any previous generation will be carried through automatically to the current object. Inheritance makes global changes incredibly simple. If, for example, you change your logo or a toolbar menu used on all of your forms, you need to change only the property at the highest level, and that change will trickle down to all objects derived from it and its descendants.

Polymorphism

Polymorphism (which means, literally, many shapes) is the ability of objects to alter inherited behavior. Let's take automobiles and helicopters as an example. Assume that you already have defined an automobile object and you want to create a helicopter object. Many attributes and behaviors of an automobile are applicable to a helicopter. Sounds like a good case for inheritance. (You wouldn't have to write much code to describe the differences.) Unfortunately, the differences between helicopters and automobiles are not just attributes, but behaviors as well. Automobiles start, but helicopters take off. Polymorphism lets you take the start behavior of automobile and morph it into the take-off behavior of helicopters without having to create a whole new entity from scratch.

In IntraBuilder, polymorphism is accomplished by giving you the ability to override an inherited behavior. The same start behavior that turns over the engine in an automobile can be overridden in the child helicopter object. Telling the object to start means something entirely different in the child than it did in the parent. Polymorphism gives you increased flexibility to base objects on other objects that might not, at first, appear to be a perfect fit.

What's an Object?

Now that you've learned about the characteristics of objects, you should probably learn their definition. Although objects are more easily described than defined, I don't think it would be too far off to say that an object is a self-contained entity that has built-in attributes and behaviors. An automobile is an object. It has attributes: length, height, width, body style, color, and weight. It has behaviors: start, go forward, go backward, accelerate, decelerate, and stop.

An IntraBuilder form object has attributes and behaviors not all that dissimilar to an automobile object. It has height, width, and color. It opens, closes, and releases.

Property is the programming term for an attribute of an object. Method is the programming term for the behavior of an object.

Properties

The attributes of any object are variables. The color property of a control can be selected from any number of predefined colors. Listing 20.1 shows how to set property values using a with block.


Listing 20.1. Creating an HTML control and setting properties in a with block.

 1: with (this.html1 = new HTML(this)){

 2:     height = 1;

 3:     left = 17;

 4:     top = 2;

 5:     width = 10;

 6:     color = "black";

 7:     text = "<H2>Hello</H2>";

 8: }


The with block in Listing 20.1 creates an HTML control and sets several properties. Line 2 sets the height property to 1. Just like procedural variables, properties can have any value assigned to them within an acceptable range. An error would occur if line 4 tried to set the top property to a date value.

NOTE
You can find source files for each listing in the Day 20 folder on the CD-ROM. The listings in this chapter are script files. The naming convention for the scripts works as follows: Listing 20.1 is l20_1.js, Listing 20.2 is l20_2.js, and so on.

The difference between properties and variables is that properties are attached to their parent object and are only visible from within the object itself. For all intents and purposes, properties are static variables scoped to an object.

Methods

The behavior of an object is defined the way behavior has always been defined in programming languages-as functions. Just as a variable becomes a property when it's attached to its parent object, functions that define the behavior of the object become methods. Listing 20.2 shows how to create a Form_onServerSubmit() method within a class. Also like properties, methods are both attached to and visible only through their parent objects. Methods are static functions scoped to an object.


Listing 20.2. A form class definition containing a single method.

 1: class TestForm extends Form {

 2:    with (this) {

 3:       height = 20;

 4:       left = 36;

 5:       top = 6;

 6:       width = 60;

 7:       title = "";

 8:   }

 9:

10:   function Form_onServerSubmit() // a method of TestForm

11:   {

12:    if ( this.hiddenAction.value == "LOOKUP" ) {

13:       _sys.forms.run( "LOOKUP", parseInt( this.hiddenMsg.value ) );

14:    }

15:   }

16: }

17:

18: function Hello()

19: {

20:    alert("Hello World") ;

21: }


A function becomes a method just by being included within a class block. Lines 10 through 14 define the Form_onServerSubmit() function as a method of the TestForm class. Line 16 ends the class block. Any functions defined after line 16 are not methods. Lines 18 through 21 define a Hello() function that is not a method.

Methods that are called automatically in response to built-in events are called event handlers. Because you neither run nor call these methods, they are said to be fired when IntraBuilder automatically runs them. The onServerLoad event-handler is fired when the application is loaded at the server.

Events and Function Pointers

As I'm sure you realize by now, events are triggers that fire under certain predetermined circumstances. But you might not realize that events are also properties. The onServerLoad event is a property of all IntraBuilder form controls. It's not a method; it doesn't run. It just tells the form where to find the code to fire when onServerLoad is triggered. It points to a method that contains the actual behavior you expect the object to execute.

That wouldn't be possible without function pointers. Like all other pointers (including variables and properties), function pointers contain the address of something else-in this case, code to be executed when a function is called. All methods and functions in IntraBuilder automatically generate a function pointer in memory by storing their address to an internal variable with the same name as the function. Just as form.MyFirstName contains the address of John, MyFunction contains the address in memory where the function MyFunction code is located.


function MyFunction()   // Generates Function Pointer MyFunction

(

   return ("Hello") ;

}

Because a function pointer contains the address of the object and not the executing code itself, it can be stored to another property, passed as a parameter, or overridden by another function pointer with the same name just like variables and properties.


form.Function2(form.MyFunction)   \\Send FP as parameter



form.Function2 = form.MyFunction  \\Store FP to another property



function MyFunction()            \\ Override with another method

{

   return 'Good-Bye' ;

}

NOTE
You cannot send parameters to a function pointer any more than you can send parameters to a variable or property. Parameters can be sent only at runtime, when a function pointer is executed. Therefore, if you want an event to trigger a method with parameters, assign a code block instead of a function pointer to the event. The following statement does not create a function pointer:
onServerLoad = CLASS::MyFunction("Hello")

You can use a code block to pass parameters as follows:
onServerLoad = { ; form.MyFunction("Hello") }

To get a function pointer to execute its code, just add parentheses after the function pointer name: form.MyFunction().

Classes

Although by now you're familiar with classes, because they are the cornerstone of IntraBuilder's server-side JavaScript, they probably merit a closer look.

Classes are the blueprint from which objects are created. IntraBuilder has a real-time object model. The actual working objects don't exist at design time. They are created at runtime as required from the plan described in the class.

An instance of a class is a single object created from a class blueprint. Using the same class, you can create a virtually unlimited number of instances just as you can build any number of houses from the same plans. Each button on a form is another instance of the Button class. Object and instance are synonyms. The process of creating an object (or instance) from a class is called instantiation.

Borland's implementation of classes is interesting. Although some of the class code that defines an object executes, it doesn't execute when the disk file is run. That's an important distinction. Call a function, and it executes; run a script, and it executes. That is, until it sees the keyword class, at which time it returns without running the class code.

That's because classes have to be instantiated, not called or run. Remember that classes are not executable; they are not functions, nor are they methods. A class is just a blueprint. The class code is executed only when you call the new operator. The following statement uses the new operator to create an instance of the TestForm class:


var f = new TestForm() ;

The class constructor runs only until it encounters the first method (or the end of the class block, whichever comes first) and then stops. This is part of IntraBuilder's dynamic object model. Listing 20.3 shows the class constructor of a simple IntraBuilder .jfm file.


Listing 20.3. The structure of the .jfm file.

 1:  // {End Header} Do not remove this comment//

 2:  // Generated on 02/21/97

 3:  //

 4:

 5:  var f = new testForm();

 6:  f.open();

 7:

 8:  class testForm extends Form {

 9:   with (this) {

10:      height = 20;

11:      left = 32;

12:      top = 0;

13:      width = 60;

14:      title = "";

15:   }

16:

17:   with (this.button1 = new Button(this)){

18:      left = 38;

19:      top = 3.5;

20:      width = 10.5;

21:      text = "OK";

22:      onClick = class::button1_onClick;

23:   }

24:

25:   with (this.checkbox1 = new CheckBox(this)){

26:      height = 1.2

27:      left = 38;

28:      top = 5.5;

29:      width = 15;

30:      text = "Save Data";

31:      checked = false;

32:   }

33:

34:   with (this.hidden1 = new Hidden(this)){

35:      left = 38;

36:      top = 8;

37:      value = "";

38:   }

39:

40:   function button1_onClick()

41:   {

42:     if ( form.checkbox1.checked ) {

43:       form.hidden1.value = 'SAVE';

44:     }

45:   }

46

47:}  // This is the end of the Class block.


In Listing 20.3, five areas of the .jfm are indicated, and each area has its own use, execution rules, and streaming rules for the design tools. Lines 1 through 3 signify the end of the header. Any lines you add above line 1 are part of the header. Lines 5 and 6 are the bootstrap section. The constructor spans from lines 8 through 38. The constructor ends with the first method definition on line 40. The methods section is from lines 40 through 46. Any lines you add after line 47 reside in the general area.

Streaming refers to the process of code generation by the IntraBuilder design tools. Whenever you save a form in the Form Designer, IntraBuilder streams out brand new source code based on the visual components, properties, and methods you designed using the visual tools. Streaming is of particular concern because the new source code generated can overwrite your own code if you're not careful.

The following list describes each area of a JavaScript source file. As you read through each item, note how the sections differ by execution requirements. That is, some sections must execute for a form to open, while others are optional.

IntraBuilder provides two keywords for use with classes to implement polymorphism: class:: and super::. The class:: and super:: operators are called scope resolution operators, which is just a fancy way of saying that these powerful keywords specify which class's method you're addressing. The class:: operator means that you should look in this class (the subclass) for the method. The super:: operator tells IntraBuilder to look in the parent (or superclass) for the method. In addition to the built-in keywords, you can also use any object reference variable (see following sections) as a scope resolution operator by adding the scope resolution symbol, the double colon, like this: form.nextform::open(). Scope resolution operators work only with methods, not with properties.

You'll remember from the beginning of today's discussion that IntraBuilder implements polymorphism by overriding methods. As a result, you can have an onServerLoad method of the subclass that's totally different from the onServerLoad method of the superclass. Overriding a method doesn't destroy the original. It leaves it both intact and accessible. There will come a time when you'll want the choice of accessing the superclass onServerLoad or the subclass onServerLoad. The scope resolution operators help you specify which one you want to execute.

One important use of overridden methods is to replace built-in methods with your own. A good example is Form::open(). IntraBuilder has no PreOpen or Init method, so what do you do if you need to set a property before the form opens? You override the built-in Form::open() method with one of your own. Then, when your method has done its work, you use the scope resolution operators to invoke the built-in open method of the Form superclass. Listing 20.4 shows an example of overriding the Form::open() method in order to set a date in real time.


Listing 20.4. Using super:: to override a method.

 1: // {End Header} Do not remove this comment//

 2: // Generated on 02/21/97

 3: //

 4: var f = new SuperForm();

 5: f.open();

 6: class SuperForm extends Form {

 7:   with (this) {

 8:       height = 20;

 9:       left = 0;

10:       top = 0;

11:       width = 60;

12:       title = "Super";

13:    }

14:

15:    with (this.html1 = new HTML(this)){

16:      height = 1.2;

17:       left = 16;

18:       top = 5;

19:       width = 10;

20:       color = "black";

21:       text = "Text1";

22:    }

23:    function open()  // overrides form's open() method

24:    {

25:     form.html1.text =  new Date().toLocaleString().substring(0,8);

26:     super::open(); // now invoke the superclass open()

27:    }

28: }


In Listing 20.4, check out function open() on line 23. Declaring this function within a subclass (in this case, SuperForm) causes IntraBuilder to override the built-in open() method (from class Form) with yours. However, the superclass open() method still exists and is still accessible. In line 25 you set the html1.text property to the current date, and in line 26 you call super::open() to activate the form. This code allows you to create your own pre-open type of event, setting properties before the form is activated.

Classes of Classes

IntraBuilder supports five distinct types of classes, each of which has a distinct source code file format:


Listing 20.5. A from-scratch class.

 1: class Time {

 2:    function MilTime()  // returns current time as military time

 3:    {

 4:      this.dDate = new Date();

 5:      this.hour = ''+this.dDate.hour;

 6:      this.minute = ''+this.dDate.minute;

 7:      if (this.minute.length == 1) {

 8:         this.minute = '0'+this.minute;

 9:      }

10:      if (this.hour.length == 1){

11:         this.hour = '0'+this.hour;

12:      }

13:      return  this.hour+':'+this.minute;

14:    }

15:    function AmPmTime()  // returns current time as Am/Pm format

16:    {

17:      this.cDate = new Date().toLocaleString();

18:      return this.cDate.substring(9,14)+' '+this.cDate.substring(18,20);

19:    }

20: }


Listing 20.5 is an example of a simple from-scratch class. The Time class's MilTime() method (in line 2) converts the current time to military time (23:30). The AmPmTime() method (in line 15) converts the current time to AM/PM format. This kind of utility is a perfect candidate for a from-scratch class. It has no visual elements, but the code is highly reusable.

Although each type of class sports its own file extension, you are not restricted to the specified file type. In fact, all of the source file types are nothing more than scripts. How IntraBuilder treats each type depends on the contents, not on the extension. However, having the various extensions makes organizing your work much easier and allows the IntraBuilder Explorer to find appropriate files for each tab.

You cannot add parameters to standard classes, just like you cannot add parameters to any built-in method. The standard class Form takes only a single parameter: title. Check IntraBuilder's online help for each class for the parameters that the standard class accepts.

Instantiation

Instantiation is the process of creating an instance-an actual working object-from the specifications of its class. There's really only one way of launching an object in IntraBuilder, and that's using the new operator.

When you instantiate an object, IntraBuilder returns the address of the object to a variable, property, or array element. (See the "Object References in Variables" section, later in this chapter.) Follow these three basic steps to create and access an object:

  1. Load code into memory. The source code for the class must be loaded into memory before you can derive an object from the class. There are three methods for loading source code from a disk file. You can tell IntraBuilder to load a JavaScript file into memory with the _sys.scripts.load() method. You can #include a file within another, which loads both when the first one is loaded. When you run a form using _sys.forms.run(), IntraBuilder automatically loads the .jfm script.
  2. Create an instance (instantiate) using the new operator, like this:
    var f = new MyObject() ;
  3. Activate or access the object. If the object is a form, calling its open() method activates the form. Or the object might not have any visual components, in which case you can read and write properties or execute its methods as soon as the object is instantiated.

This multi-step process for creating objects from classes is the core of IntraBuilder's dynamic object model. Older object-oriented environments stored static objects in binary format. The values of properties could be changed after the objects were instantiated, but new properties and methods couldn't be added. In a dynamic object model, you can easily add custom methods or properties as soon as the object has been instantiated-before it has been activated or used.

Although some OOP purists claim that this violates the rules of encapsulation (and it does, to some extent), the benefits of dynamic objects far outweigh any disadvantages. If you want to add a simple property to a single instance of a form, you don't have to create a whole new class; just add the property dynamically. It saves the overhead of loading a new disk file plus the handles associated with an additional class and its components. The same is true for methods. You might want one single instance of a maintenance form to perform one single action for this particular application. No problem. Just add a new method to the form after it has been instantiated, and the method will work exactly as it would if it had been included in the original class. Listing 20.6 shows how to add a property and a method to an existing form instance.


Listing 20.6. Dynamic properties and methods.

 1: function CallMyForm()

 2: {

 3:    _sys.scripts.load('My.Jfm');

 4:    form.MyForm = new MyForm();

 5:    form.MyForm.User = form.User; //create custom property

 6:    form.MyForm.Form_onServerLoad = class::Form_onServerLoad;

 7:    form.MyForm.open()

 8: }


Listing 20.6 assumes that you're launching a second form from within one that is already open. Line 3 loads the new form's class script (.jfm) into memory. Line 4 instantiates, storing the form's address in a custom property of the first form, form.MyForm. Then, before opening the second form, line 5 adds a custom property to the second form. Line 6 adds a new method to the second form that wasn't in its original class definition-the same method you are using on the original form.

TIP
It takes some experience to determine what works well as a class or dynamically. If there is any possibility of reuse, create a new subclass. If you need only a single instance of the object to be changed for a single application, the dynamic model of creating properties and assigning methods can be very useful. Another major use is tools. One example is an application manager class-a class that automates instantiation and controls your application. This manager class might need to add a new identification property to each form it opens. Without the dynamic object model, you would have to add this property to every form in your library.

Managing the Object Reference

You can't master object-oriented programming without mastering the object reference. This handle-a variable, property, or array element that contains the address of an object-is absolutely key to managing your objects.

As you become more experienced writing IntraBuilder applications, you'll find that you need to access objects from within other objects. This talk between objects is called interprocess communication. Interprocess communication can be as simple as instantiating a simple business class and setting properties, or as complex as stringing forms together to build your application. In any case, the object reference is the handle you use to identify each object.

Therefore, where you store your object references can be critical. Variables, properties, and arrays all have scope. Depending on where and when they are declared, and whether they're attached to objects, they might be visible at some times and invisible at others. In the case of variables, they can disappear entirely.

Let's look at the instantiation code that the IntraBuilder Form Designer streams out for each form:


var f = new MyCustomForm()

IntraBuilder declares a variable, f, in which it stores the address of this instance of MyCustomForm(). If you plan to ever address this form from anywhere except within the form itself, this code won't work. The variable f only exists for the few milliseconds in which instantiation takes place and then goes out of scope. The minute the object has been fully created in memory, f no longer exists, leaving no way to identify this form from anywhere else in your application!

Because variable f has no scope outside of the instantiation process itself, it is highly recommended that you don't launch your forms using _sys.forms.run(), which executes the Form Designer's bootstrap code. This built-in instantiation (and, for that matter, the ability to run a .jfm) was designed strictly for development and testing purposes-for launching forms from the IntraBuilder Explorer or the Script Pad. It was never intended that you should run a .jfm from within a program-unless your program is so simple that no form or object needs to talk to any other form or object.
Instead of _sys.forms.run(), use the multi-step process of loading the .jfm into memory (_sys.scripts.load()), and then instantiate the object yourself using the new operator. This approach gives you more control over your objects and lets you determine what will be in memory at any given point, rather than relying on IntraBuilder.

So where should you store object references? There are several solutions, depending on circumstances.

Object References in Variables

Variables should be used only for quick-and-dirty instantiation. In object-oriented languages, the scope of variables is so limited (within a method or function) that they're difficult to manage and useless for interprocess communication.

Object References in Properties

A property can be an excellent place to store your object reference. However, once again, you have to be careful about scope. Don't store an object reference in another object that's about to be released. Properties are an excellent way to chain your forms together in an application, as shown in Listing 20.7.


Listing 20.7. Instantiating into a form property.

 1: function LookupButton_onClick()

 2: {

 3:   _sys.scripts.load("Lookup.Jfm");

 4:   form.Lookup = new LookupForm(); 

 5:   form.Lookup.htmL1.text = form.UserName;

 6:   form.Lookup.open();

 7: }


Line 4 instantiates class LookupForm into a custom property of the current form, form.Lookup. Using that object reference, you set the value of a property in LookupForm based on the value of the UserName property in the current form. By using this method for tying your forms together, you can easily pass data along from form to form as they are launched.

Object References in System Properties

In the previous example, you stored the object reference in a property of a form. The limitation to that style of instantiation is the scope of the property: It cannot be seen from any other form or procedure outside of the form, except using the form's own object reference. So, where can you store an object reference that is visible to every object and function in your IntraBuilder application? You store it in a custom property of the global _sys object. Listing 20.8 shows how to add a property to the _sys object.


Listing 20.8. Instantiating into a Custom _sys property.

 1: function LookupButton_onClick()

 2: {

 3:    _sys.scripts.load("Lookup.Jfm");

 4:    _sys.Lookup = new LookupForm(); 

 5:    _sys.Lookup.htmL1.text = form.UserName;

 6:    _sys.Lookup.open(); 

 7: }


Listing 20.8 is almost identical to Listing 20.7, except that the object reference for this instance of LookupForm is stored in a custom property (which is created in line 4 during instantiation) attached to the built-in global _sys object. What is the advantage? Every object and function can see and address this property. The disadvantage is that the form can only be opened once into this property; a new form will overwrite an existing one.

Object References in Arrays

Array objects give you the most flexibility for managing object references. Because you can add new elements as needed or reuse empty ones, arrays don't suffer from some of the scoping and overriding issues of variables and properties. Furthermore, as classes, you can imbed management code to instantiate, open, and close forms right in the array class itself. Listing 20.9 shows how to create a global array.


Listing 20.9. Instantiating into a global array object.

 1: function LookupButton_onClick()

 2: {

 3:    _sys.scripts.load("Lookup.Jfm");

 4:    _sys.scripts.load("Form2.Jfm");

 5:    _sys.aManager = new Array(1); 

 6:    n1 = _sys.aManager.length-1;

 7:    _sys.aManager[n1] = new LookupForm();

 8:    _sys.aManager.add(1);

 9:    n2 = _sys.aManager.add(1)-1;

10:    _sys.aManager[n1].open();

11: }


Listing 20.9 pre-instantiated two different form classes (LookupForm and Form2Form), stored their references in two different elements of a global array (_sys.aManager), and opened only one of the forms. Note that you used add()-1 as the array subscript to identify your forms. The call to aManager.add() on line 5 returns the total number of elements in this one-dimensional array. But array subscripts start at 0, not at 1. Hence, if add() returns 1 (one element), the subscript for that element is 0 (the first element in an array object). Therefore, you use add()-1 to identify each form.

TIP
Pre-instantiation (or creating instances before you need them) brings possible performance advantages. The process of dynamically building a form from its class involves significant overhead. Depending on how many forms you have and how complex they are, it's sometimes advisable to instantiate your forms all at once at the beginning of your IntraBuilder program. Your initial program will load a bit slower (because there's more for the Agent to do), and it uses more memory, but subsequent form openings should be considerably faster.

Using array assignments for object references, you can build an automatic form manager that cuts out a lot of the coding required to instantiate and open forms. Listing 20.10 shows an example of a form manager. It is included on this book's CD as Manager.cc in the Day 20 folder.


Listing 20.10. Manager.cc: A rudimentary form manager.

 1: class Manager extends Array() {

 2:    function Instance(cFileName)

 3:    {

 4:       cClassName = cFileName.substring(0,cFileName.indexOf('.'))+'Form';

 5:       n = this.add(1)-1;

 6:       _sys.scripts.load(cFileName);

 7:       eval("this[n] = new "+cClassName+"()");

 8:       return n;

 9:     }

10:     function Open(cFileName)

11:     {

12:       n = this.Instance(cFileName);

13:       this[n].open();

14:       return n;

15:     }

16: }


Listing 20.10 defines a simple, two-method form management class. The Instance() method loads the form's source file into memory (in line 6), adds a new element to this global array object (in line 5), and instantiates your form, storing its object reference in the new array element (in line 7). Note that you used the eval() function to execute an expression with the new operator. The eval() function evaluates an expression much like the macro-interpreters of some other languages. Because you cannot hard-wire a classname as in f = New MyCustomForm(), you need to use eval() to expand the Classname variable. When the expression on line 7 (the instantiation) is evaluated, it is also executed.

The Open() method both instantiates and opens the form. If you call _sys.Manager.Open(), you don't have to call Instance().

MANAGER.CC should be instantiated at the beginning of your program, either in a startup script or in the header of your first form. It only takes two lines of code:


_sys.scripts.load('Manager.Cc');

_sys.Manager = New Manager();

Then, instead of using the built-in object management keywords, you use the methods of this custom array class to instantiate forms. The simplest way is to call its Open() method:


n = sys.Manager.Open('test.jfm');

_sys.Manager[n].release();

A call to the Open() method is all that's needed to open a form using MANAGER.CC. The call to release() demonstrates that you can reference a form opened in MANAGER.CC using the subscript n.

This simple version of the Form Manager assumes that you're not changing the class names generated automatically by the Form Designer. MANAGER.CC obtains the form's class name from the source filename (the name you saved it as) plus the word form. So, Test.Jfm usually has a class name of testForm. Because JavaScript is case-sensitive, be sure to use lowercase only when sending the filename to methods. Or, you can update MANAGER.CC to force the parameter cFileName to lowercase.


n = _sys.Manager.Instance("test.jfm");

_sys.Manager[n].UserName = form.UserName;

_sys.Manager[n].open();

This code uses the form manager to open the test form. Before the form opens, a custom property is added. The first line calls the form manager, the second line creates a custom form property, and the last line opens the form.

The _sys object is global only to a single IntraBuilder Agent. When you are running applications with more than one IntraBuilder Agent, you cannot share information through the _sys object.

Getting Rid of an Object

Getting rid of an object might not be as simple as you think. A number of the IntraBuilder standard classes include a release() method to inactivate the object and remove it from memory. However, some objects don't have explicit release() methods, and your custom from-scratch classes won't have release methods. However, you can ensure that objects don't linger past their time by controlling their object references.

IntraBuilder does reference-counting. Each time you open a form, a counter is incremented for that particular instance. Each time you store the object reference to another variable, the counter is also incremented.


var f = new testForm();  //counter = 1

var n = f;               //counter = 2

form.oForm = f;          //counter = 3

form.oForm.release();    //counter = 2

You'll note that the counter never gets down to 0, which means that your form remains in memory, even though it was explicitly released. This can cause havoc in your applications. The solution is to be sure to eliminate each and every reference. This can be done in a variety of ways:

Internal Object References

In addition to the external object reference that results from calling the new operator, IntraBuilder sports two internal object references that are generated and managed automatically.

Each form has its own internal object reference stored in an internally generated variable called form. There are two reasons for having a generic reference variable. First, it makes sure you don't orphan a form when you stub out external references without releasing the form; and second, it provides a wonderful generic variable that can be passed and referenced by any object or method on the form. Here are some examples:


form.height = 20;   // reference a property



form.close();       // call a method



f = new MyForm()

f.oParent = form    // sent to another form as a property

f.update(form)      // sent to a method as a parameter

The form reference is identical to the external reference and can be used in the same way any external object reference is used, with one exception. It can only be used from within the form.

The other object reference generated automatically is this. The this reference points to the current object. In other words, whereas form always refers to a single object (the form itself ), this is a moving target. During instantiation, this refers to the class itself, as shown in the following constructor code:


class TestForm extends Form {

    with (this) {

    height = 20;

    left = 32;

    top = 3;

}

In this code, the this reference refers to the form. In the following code, this is sent as a parameter to a constructor of the Text class:


with (this.text1 = new Text(this)){

   left = 8;

   top = 5;

   onChange = class::text1_onChange;

}

Within an event-handler method (such as text1_OnChange), this refers to the object that called this method. In the following method, this refers to the object that called text1_onChange-in this case, form.text1:


function text1_onChange()

{

   this.value = this.value.toUpperCase()

}

It's important to note, however that text1_OnChange() could have been called by any object. Its function pointer could have been assigned to any event. The object that this references will be entirely different depending on which one called this procedure when its event fired.

The form and this references are important as generic variables for building black-box classes. If you want to create a thoroughly reusable class that can respond to any object, you can use this in place of the actual reference (such as form.Text1) in your methods. Then you can use the same method regardless of the object that calls it, which is more elegant OOP design.

Containership

IntraBuilder supports several containers, including the Form class, Query class, and Report class. Containers are objects that can contain other objects. A form contains HTML objects, Button objects, and Java objects, among others. The form is said to be the parent of those contained objects. Each object has a read-only parent property that points to the next container up in the hierarchy.

When you need to generically reference the container of a reusable class, use the built-in parent variable, like this:


this.text1.value = this.parent.text1.value

Don't let all these hierarchies confuse you. I talk an awful lot about parent/child in object-oriented programming, but I'm not necessarily talking about the same things all the time. It makes sense because inheritance is such a key feature of OOP. Sometimes superclasses are referred to as parents, or a form that calls another form is called a parent. However, containership is not inheritance, although it relies on a similar metaphor. The only true parent property in JavaScript is the one that identifies the container of a given object.

Building an Object Library

The fundamental goals of object-oriented programming are to build powerful applications in which you hand-code only unique business rules; to deploy robust, full-featured, bug-free programs in weeks instead of months; and lastly to make software development easier, faster, and cleaner with the more applications you ship.

In fact, that's the power that IntraBuilder brings to Internet/intranet development. Its elegant object model and easy-to-use Rapid Application Development tools are designed to revolutionize the way you create software. But they're only as valuable as your knowledge of them and your willingness to use them. To turn this fantasy into a competitive advantage, you have to stop thinking of functions and .jfms as the basis of your application design, and start thinking about objects as the building blocks of a new and better component architecture.

Generation by generation, programming languages have become less and less granular. From assembler (with its voluminous low-level instructions) to today's sophisticated object-oriented development platforms (such as IntraBuilder), the clear trend is toward building more powerful applications from fewer components. Productivity is about leverage, deriving greater output from less effort. Instead of writing each application with line after line of code, your goal should be to build a robust, high-level object library-to drag and drop your future applications, not write, run, and debug them.

Objects or Functions?

When should you write functions? Probably never. There is nothing you can write as a function that you can't write as a method of a class. Starting right now, at Day 20, everything you write should be a class. You can't inherit a function. You can't easily adapt a function-and if you do, you might find that you've broken a slew of legacy code. Inheritance poses no such risk.

Instead of thinking of your applications as flowcharts, think of your applications as having sockets. Analyze your project before you build it. Take apart each functionality you plan to implement and designate it as a socket, a place where you can plug in a reusable class. Then, see how many of the sockets you can fill by existing classes. When that is done, see how many of the sockets you can fill by inheriting from classes that are close to the functionality required. Only when that is done should you consider writing new classes from scratch. After a few outings with IntraBuilder, you'll find fewer and fewer sockets that require new classes. Eventually, you'll build most of your applications without writing any source code except for business classes and queries unique to the project.

Organizing Your Object Library

The following are the general categories of classes that you'll want to include in your object library:

Summary

Using IntraBuilder's tools combined with its server-side JavaScript object-oriented language, you can achieve a productivity you probably never before imagined. The better your object library is, the more advantage you'll take and the more benefits you'll reap from this true RAD (Rapid Application Development) environment for dynamic Web sites.

Q&A

Q:Why not just run the .jfm file to launch a form?
A:You can do that if you want. But the instantiation code streamed by the Form Designer puts the form's object reference into a variable (f) that disappears instantly. Because there is no external reference remaining for the form, you can never address its properties or methods from any other place in your application.
Q:Should everything I write be written as classes instead of functions? Doesn't that make a lot more work?
A:Initially, it sure does. You don't have to define and set properties in functions, and they're a bit more forgiving than classes. You don't have to instantiate functions. As a whole, they involve less code than classes.
But that advantage exists only for a one-time use. The greatest benefit of object-oriented programming is reusability. The next time you need the same, or even similar, functionality in another form or another application, you really start to see the benefits. Because you never know what you'll need in an application years down the road, it makes sense to create classes for anything that has even a remote possibility of reuse.
Q:Why can't I send parameters to a form?
A:Because form is a built-in standard class in IntraBuilder. It has a single designated parameter (Title). You can't add parameters to aArray.Add() or dDate.toString or any other built-in method. Instead of using parameters, instantiate your forms and declare custom properties before opening.
Q:Why can't I send parameters to a function pointer?
A:Because a function pointer is a pointer; it's a variable or property containing an address, and it's not a function call. You can't send a parameter to a variable.

Workshop

The Workshop section provides questions and exercises to help you get a better feel for the material you learned today. Try to answer the questions and at least think about the exercises before moving on to tomorrow's lesson. You'll find the answers to the questions in Appendix A, "Answers to Quiz Questions."

Quiz

  1. How do properties and methods differ from variables and functions?
  2. What is an object reference?
  3. What is an object reference variable?
  4. What is interprocess communication?
  5. What are the internal references in a form?
  6. What are the generic scope-resolution operators?
  7. What is the difference between a class and an object?
  8. What is the difference between an instance and an object?

Exercises

  1. Expand on the form manager to force the first half of cClassName to lowercase. Add a Release() method that scans the array elements to find the current instance and release it. Add a CloseAll() method that loops through all the array elements, finds active references, and closes and releases their forms.
  2. Create a custom business class from scratch. Port some existing business code (if you have some) for a posting routine or an update customer routine. Define properties and methods.
  3. Create a small library of derived HTML custom components. Set color, attribute, and size tags to make a useful set of HTML controls that matches your normal style in page design.