Chapter 9

Creating Your Own JScript Objects


CONTENTS

In Chapter 4 "JScript Objects," we learned about JScript arrays and their close relationship with JScript objects. That same chapter also taught us how to create functions to do meaningful work. In this chapter, we build upon that foundation and learn how to create complex arrays, functions, and objects. In the process of this exploration, we reveal some of the hidden power of JScript.

We begin with a discussion of variable declarations and their relationship to the critical topic of function parameters and their validation. We also build up an extremely valuable function, known as word(), which is used in several subsequent efforts.

Associative arrays are one of JScript's most powerful features. They are also one of the most error-prone. The second part of this chapter revisits the topic of associative arrays and examines extensions to them. Since arrays are the only way to store and access information in a database-like manner, we also learn how to fill arrays from lists and use extended arrays.

The final section of this chapter develops two important objects-the Image object and the Text object. The Image object stores a great deal of information about an image, and can also draw that image as a plain image, framed image, or linked image. The Text object stores text and also information about how the text is to be formatted. Both of these objects are developed as arrays of objects, similar to a small database. Both can also be easily extended for your own purposes.

Many of the examples in this chapter are too large to be listed in their entirety within the chapter. All the source code can be found on the CD-ROM.

Global and Local Variables

We have already explored the distinction between global variables, which are visible in every function, and local variables, which are visible only in the function in which they are declared. If you want to declare variables global to the whole window, define them outside of any function. Whether you precede the declaration with var or not, it is still global. However, any declarations that you make inside functions should be preceded with var if you want them to remain local. As you know, it is not strictly necessary to declare variables within functions. JScript treats any unknown variable it sees as a new variable, as if you had declared it. If you do not explicitly declare such a variable, or do not precede it with var, it becomes a window global variable. Check your functions very carefully for variable declarations. If you accidentally make a variable a global variable by not using var, it can cause no end to trouble.

Tip
Particular care should be taken with variables having common names, such as i, j, or iter. Always check the declaration of these variables carefully.

It is unfortunate that the rules for the use of var are confusing. Remember that placing var in front of a variable declaration restricts it to the scope in which it is declared. The scope of a variable is the context in which that variable can be used. In the case of a local variable declared in a function, the scope of that variable is the function itself. In the case of a window script, it is the window. If you do not use var in a function or a window script, the variable's scope is global-it can be used anywhere in the window.

Although JScript does not force you to declare or initialize variables before you begin function statements, it is often very useful to do so. First, it allows you to document the purpose of each variable. This is not only good programming practice, it is also genuinely useful. When you return to a function six months after you wrote it, you may not remember what the variable countme does, unless you explicitly document it as // array index variable. Second, if you explicitly declare each variable, you have the opportunity to initialize each one to some meaningful default value.

The basic rules for JScript functions and their parameters and variables are described in the "Objects, Properties, and Methods in JScript" section of Chapter 4 "JScript Objects."

Caution
If you declare global variables within a function, you must execute the function first for the variables to be defined.

The following code fragment illustrates this idea:

function norl()
{
  myFuncVar = 5
}
norl()
newval = myFuncVar + 3

This code uses the variable myFuncVar inside the function norl. Because there is no var statement preceding the first use of myFuncVar, this variable will become a global variable, but only after the function norl is first executed. As written, the code will operate correctly, and newval will have the value 8. If we modify the third line to read var myFuncVar = 5 and then attempt to execute this code, the alert shown in Figure 9.1 will result. Since the variable was declared with var inside a function, it is local and "out of scope" to anything outside the function. Similarly, if you comment out the call to the function norl() on line 5, this code will also generate the same error.

Figure 9.1: JScript alerts you when you have used an undefined variable.

Parameter Checking

Functions, particularly ones that carry out critical tasks like opening windows, should always check to see whether their parameters are valid. This usually involves making sure that they are the correct type or are within some expected range. First and foremost, then, we need routines to determine if the parameters are valid. Strings are relatively safe as parameters, but numbers and Booleans are not. This is because JScript often converts a nonstring, such as 5, to the corresponding string "5," but it converts to numerical or Boolean values only if it can. If a string parameter is specified as the number 5, no error occurs, because the numerical value 5 and the string "5" may be freely converted to each other. If a numerical parameter is specified as the string "five," an error very definitely occurs, because JScript does not know how to convert that string to a number.

If you discover a bad parameter, you have two choices. You can simply abandon all processing or you can change the bad parameter to some safe default. It is often preferable to do the latter, and place an error message in a variable that calling routines can check. The library functions which we will develop later in this chapter use two of these; they are aWinErr and aStringErr. Of course, there is nothing you can do to keep the user from entering something totally unanticipated in a text field. The best you can do is try to limit the destruction that ensues.

Now, let's examine a few functions from our repertoire to see how they work. Three checking functions are presented: one for numbers or restricted characters, one for Booleans, and one for character encoding/decoding.

Numbers or Restricted Character Sets. The function isIn, shown in Listing 9.1, is multipurpose. It can check to see whether all of the characters in a parameter string are also found within a comparison string. If the comparison string is empty, it uses a string of digits (0-9) as the comparison string. This function returns a Boolean value to indicate if the comparison succeeded or failed. It can also strip unwanted characters from the parameter value and return a string that contains only acceptable characters. The test code is found in the file c9-testr.htm on the CD-ROM.


Listing 9.1  c9-testr.htm-A Multipurpose Parameter-Checking Function
function isIn(astr,nstr,strip)
// astr:  item in question
// nstr:  allowable character string; defaults to numbers
// strip:  determines whether return is true/false 
// or only allowable characters.  Defaults to false.
{
//declare and initialize variables to make sure they stay local
	  var cc=''
	  var dd=''
	  var bstr = ''
	  var isit  
	  var i = 0
	  // make error string empty
	  aStringErr = ''
	  //force number to a string
	  astr = '' + astr
	  //default to checking for a number
	  if (nstr== null || nstr == '') nstr = '1234567890'
	  //make sure that 'strip' is a Boolean
	  strip = (isBoolean(strip))
	  //force to string; remember, this can return a Boolean
	  strip += ''
	  //NOT a Boolean-complain
	  if (strip == 'false')
			 {
					strip = false
					aStringErr = '"Value" must be (T/t)rue or (F/f)alse.'
					 aStringErr += 'It is neither. Defaulting to false.'
					 alert(aStringErr)
					 return(null)
			 }

	  //now that everything is set up, let's get down to business

	  isit=false

	  // begin loop which cycles through all of the characters in astr
	  for (i = 0 ; i < astr.length ; i++)
			 {
					cc=astr.substring(i, i+1)

					// begin loop which cycles through
					// all of the characters in nstr
					for (j =0 ; j< nstr.length ; j++) 
						  {
								 dd = nstr.substring(j, j + 1)
								 isit = false
								 if (cc == dd)      // so far so good
										{
											  isit = true
// accumulate good characters
											  bstr += cc
// no need to go further
											  break        
										}
						  }  // end of j loop 
//you found a mismatch; disqualify the item immediately
//unless you are going to strip the string.
						  if (isit == false && strip == 'F')
								 break;
						  else continue
			 }  // end of i loop
	  
	  if (strip=='T') return bstr  // return stripped string
	  else return isit             // or return true/false (Boolean)
}

The isIn() function takes three parameters. The parameter astr is the item to be checked. The parameter nstr contains the set of characters that are permissible in astr. By default, nstr will be "1234567890," which will have the effect of checking to see whether astr is a numerical quantity. The Boolean parameter strip indicates whether unacceptable characters (characters not in nstr) should be stripped out of astr.

The first portion of the isIn() function makes sure that astr and nstr are both strings, and initializes nstr to its default value if it was given as the empty string. It also ensures that strip is Boolean (the function isBoolean() is described under "Boolean Validation," as follows). The for loop then examines every character in astr. The substring function extracts each character into the local variable cc and checks to see whether that character may be found in nstr using a second for loop. If any unacceptable characters are found, the isit variable is set to true. This for loop is also responsible for building a new string in the local variable bstr which contains only acceptable characters.

If the strip parameter is true, the function will return the stripped string bstr at the end of its processing. An example of this is shown in Figure 9.2. The characters "l" and "v" are out of range, and are removed. If strip was false, the function returns a Boolean value which indicates whether the astr parameter contained any unacceptable characters. This case is illustrated in Figure 9.3, using the same set of string values.

Figure 9.2: JScript functions may convert unacceptable parameters to an acceptable form.

Figure 9.3: JScript functions may choose to reject parameters that are out of range.

Boolean Validation. When we write code, it is easy to forget exactly how we are supposed to pass a variable. One of the nice things about the JScript language is that it is relatively untyped when compared with strongly typed languages such as Ada or Pascal. In JScript, the same function can return a Boolean, a string, or a number with impunity. The calling routine may have some trouble, though; it complains if you try to hand it a string when it thinks it should get a number. One way out of this dilemma is to forcibly convert the variable to the form you want. You can force an arbitrary value to be a string by concatenating it with an empty string. You can force an arbitrary value to be an integer with parseInt().

The "String Object" section of Chapter 5 "Built-In JScript Objects," discusses string conversion rules and methods.

This kind of type confusion occurs frequently with Boolean values. Should a function return true or "true" or "t" or "T" to indicate success? Listing 9.2 shows the isBoolean() routine. It takes the first character of a putative Boolean, changes it to uppercase, and checks to see whether it is "T" or "F". If the resulting value is either "T" or "F", then the function returns true or false, respectively. If the resulting value is neither "T" nor "F", then isBoolean returns false. Note that only the first letter of the astr parameter is ex- amined, so that fear, Fortran, and felicity will all be interpreted as false, while tundra and TECO will be seen as true. Listing 9.2 is also part of the CD-ROM file c9-testr.htm.


Listing 9.2  c9-testr.htm-A Boolean Validation Function
function isBoolean(astr) 
//astr is the object to check
{ 
	  var isit='' 
	  astr +=''
	  if (astr == null || astr == '') isit= false
	  else
			 {
				  astr = astr.substring(0,1)  // just first letter
				  astr = astr.toUpperCase()   // make it caps
				  if (astr != "T" && astr != "F") 
						 {
								// unacceptable value entered
								isit = false
								 }
						  else
//returns value which caller can test for true/false
//without having to do substrings, etc.
								 isit= astr
			 }
//return is mixed:  can be either a Boolean or a string.
	  return isit
}

Character Validation and Conversion. Most languages have a means of defining a character as a numeric code and, conversely, converting the code back to the character. The ASCII standard is often used for this conversion these days, just as EBCDIC was used 20 years ago. All these character encoding systems, such as ASCII, represent each character as a unique numeric value. In ASCII, for example, the space character has the decimal character code 32 (or 0x20 in hexadecimal), the letter "b" has the decimal code 98 (0x62), and the punctuation mark ampersand "&" has the decimal code 38 (0x26). There are many such character encoding systems; some of them also include characters from languages other than English.

In fact, if we had functions to convert between the character representation and the numerical representation, many forms of parameter validation would become much easier. For example, an isNumber function, which attempts to determine if a parameter is a number, becomes extremely easy to write. One just examines each variable's numerical ASCII code to see if it is in the range represented by the numerical codes for the characters 0 through 9.

These two conversion functions are relatively easy to write. We first need to construct a string that contains all of the printable ASCII characters. We will construct such a string (called the charset string) with a utility function called makeCharsetString(). To convert from the character representation to the numerical representation, we search for the character within the charset string. The numerical representation of that character is its index in the charset string, plus 32. The additional 32 is needed because that is the numerical code of the first printable ASCII character, the space character.

To convert from the numerical representation, we reverse the process. We subtract 32 from the numerical value and then extract the character in the charset string at that location. Listing 9.3 shows the asc function, which converts from character to numeric code, while Listing 9.4 shows the chr function, which converts from numeric code to character. The asc() and chr() functions, along with the makeCharsetString() function (which is not listed) will all be found in the c9-testr.htm file on the CD-ROM.

Note
Case always matters in character encoding. The numerical code for an uppercase letter will always be different from that of the corresponding lowercase letter. For example, b is 98 (0x62) in ASCII, while B is 66 (0x42).


Listing 9.3  c9-testr.htm-A Function to Return the ASCII Code of a Character
function asc(achar)
//achar character whose ascii code you want
{
	  var n = 0
	  var csstr = makeCharsetString()  //get ascii char string
	  //alert(csstr)
	  for (i = 0 ; i < csstr.length ; i++) 
			 {
//when you get a match, you have an index into the string
				  if (achar == csstr.substring(i,i+1) )break
			 } 

//printable characters begin at 32 with [space]
	  return n + 32
}


Listing 9.4  c9-testr.htm-A Function to Return a Character Given an ASCII Code
function chr(x)
{ 
	  var ar = ''
	  var astr = makeCharsetString()    //get ascii string
	  //alert(astr)
	  result = ''
	  if (x >= 32 )          // printable
			 {
					x = x - 32 
					 ar = astr.charAt(x)
					result = ar
			 }
// non printable, return text representation
	  else
			 {
					if ( x == 9 ) result = 'tab'
					if ( x == 13 ) result = 'return'
					if ( x == 10 ) result = 'linefeed'   
			 }
	  return result
}

Note that there are some strategically placed alert() calls in these functions that are commented out. These alerts are for debugging purposes, to ensure that the functions are actually delivering what you want. Note also that the chr() function does not handle most of the control characters, whose numeric codes are below 32. The only control characters that it tells you about are tab, return, and linefeed. This can be used in an ugly but useful way to insert a carriage return/linefeed combination into a string using the expression + chr(13) + chr(10).

There is one additional function we will present. This is the extremely useful word() function, which extracts an indexed phrase from a delimited string. A delimited string is one containing one or more entries separated by a special character referred to as the delimiter or separator. For example, the string "My:name:is:Hanover:Fiste" contains five components-My, name, is, Hanover, and Fiste-separated by the delimiter colon (:).

Note
Delimited strings are used frequently in JScript and other loosely typed languages. Delimited strings are convenient because they allow multiple elements to be represented as a single string. Many spreadsheet and database programs, for example, can export their data as delimited strings. The comma (,) and colon (:) characters are frequently used as delimiters.

'The word() function is shown in Listing 9.5. It takes three parameters: the delimited string inwhat, the delimiter sep, and the index which of the component required. It returns the component requested. Thus, if you ask word() for the third component of the string in the preceding paragraph, it returns "is." Figure 9.4 shows some sample output from the word() function. In this case, we have used the "@" character as the delimiter and asked for the second item in the string. Note that the word() function uses 1-based indexing.


Listing 9.5  c9-testr.htm-The Delimited String Processing Function word()
function word(sep,which,inwhat)
// separator character
// which word/pharase
// text in which to look
{ 
	  //alert(inwhat)
	  var n = 0               // start of a phrase
	  var wstr = 0            // holds substring
	  var i = 0               // loop counter
	  var s = 0               // start of winning phrase
	  var f = 0               // end of winning phrase
	  for (i = 1 ; i < which ; i++) 
			 {
					n = inwhat.indexOf(sep,n)     // look for separator
					if (n < 0 )                   // if you do not find it     
						  {     
								 return ''           // return empty string

						  }     
					n++                           // otherwise, loop again
			 }
	  
	  // now we should be a the right place
	  if ( n >= 0)                            // ... but do this only if we
	  {                                       // found the separator
			 //alert(n + '==' + wstr)
			 var s = n                          // phrase starts with n, now s
			 var f = inwhat.indexOf(sep,n)     // get next instance of sep
			 if (f < 0 ) f = inwhat.length     // but if there is none ...
			 wstr = inwhat.substring(n,f)      // must be last phrase in string
	  }
	  //alert(f + '--' + wstr)
	  return wstr                            // return string; it will be
														  // empty if sep was not found.
}

Figure 9.4 : The word() function extracts an indexed phrase from within a character delimited string.

The word() function does its job by using a simple algorithm. The first for loop in this function uses the string method indexOf() to find each occurrence of the delimiter character sep in the input string inwhat. The local variable n is used to hold the current position of the delimiter, while the iteration variable i counts the number of such delimiters found so far. If fewer than the required number which are found, the word() function returns the empty string.

If the required number is present, the if test just after the for loop will succeed. At this point, n is positioned at the beginning of the element we want; it is now necessary to locate the end of that element. This will be either the next occurrence of the delimiter or the end of the inwhat string itself. The local variable f is used to hold the location of the end of the element. Once n and f have been computed, the word() function uses the substring() method to store the desired element in the local variable wstr, which is then returned as the value of the word() function.

More on JScript Functions

You may recall from Chapter 4 "JScript Objects," that functions are developed by declaring them within a script. This section briefly reviews some of things to remember when writing functions, and then delves a little deeper into some of the fine points of functions in JScript. This material is developed further in the next section on associative arrays.

Make sure that you declare your most elementary functions earliest in the header script. This ensures that later functions are able to use them. The same rule applies to objects. Make sure that you do not reference any objects that have not yet been created in any of your functions. Functions in a header script cannot see objects created by the HTML on your page, nor can they see objects created by code executing later in the script. If there is something that must be done with an HTML-generated object, place it in a footer script.

Proper error-checking is also very important. If a function fails, provide a mechanism for the calling routine to detect the failure. If you can, try to keep the damage to a minimum. It is a sign of very poor design when an Internet Explorer Error dialog box comes up immediately after your page is loaded. One major problem with using functions has to do with passing the parameters in incorrect order. If some function expects three parameters that represent two numerical values and a string, but you give it a numerical value, a string, and then the second numerical value, an error very likely will occur. A similar problem arises when a calling function misinterprets what should be passed in a parameter. If a function relies on unchecked input from a user, check that the parameter is at least of the right type. If you detect that it is incorrect, flag it and, if you can, fix it.

Caution
Make sure that all local variables within a function are preceded by var. If a function inadvertently declares a global variable that has the same name as a local variable in another function, inexplicable behavior will often result. In particular, function parameters may become garbled. This type of error is particularly difficult to diagnose, so special care should always be taken with variable declarations.

Storing Your Functions

JScript cannot read (or write) local data files, but it can load and use HTML files. Therefore, even though you cannot create a JScript function library in an ordinary file, you can write a function library in an HTML header and load that library inconspicuously when you want to use it. Function libraries can also be used with frames by designating one particular frame as the "owner" of the library. All of the other frame documents have a global variable called funcs, which refers to that special frame from the perspective of the calling frame. Consequently, you can call a function named myfunction in the function library by using the reference funcs.myfunction from anywhere in any of the nested frames. You can also make a function library in a frameset itself.

The "Bug Database" section of Chapter 17, "Using Frames and Cookies," provides a comprehensive example of using a function library with frames.

Note that the functions given here are meant to be introductory only. Because JScript at present lacks many of the functions that have become standard in most programming languages, we have provided you with a library of some commonly used functions. They can be found in the file C9-TESTR.HTM. This HTML file not only contains the functions in the previous listings, it also creates a little test page that allows you to enter parameters and call a given function with them. It is also useful if you want quickly to look up an ASCII code or convert some HTML text to a form in which it can be displayed by the browser without being interpreted as HTML by the browser.

The eval Function

In HyperCard, one can write Hypertalk in a text field and then "do" the field, which executes the contents of that field. Because of this, your HyperCard scripts can write and execute code on-the-fly. You can do something similar in JScript using eval(). Although eval() sounds as if it should be used only to evaluate mathematical equations, this function can actually do much more. It can evaluate any string that is a JScript statement. Try this experiment. Create an HTML button and attach the following statement as its onClick event handler:

eval('alert("I did it!")')

When you click the button, the Alert box pops up. In the same way, you can pass the contents of an HTML text field or textarea to a JScript event handler. This is illustrated by the function evaluate(), which is found in the file JS-EVAL.HTM on the CD-ROM. This function consists of a single line of code, eval(what). A single HTML button arranges to pass the contents of a textarea to this function, which promptly tries to execute the contents of that textarea as JScript code. The eval statement can be placed directly in the button handler, of course. By placing it in the separate (but trivial) evaluate function, we make it easier to extend in the future.

JScript's Associative Arrays

Associative arrays were introduced in Chapter 4 "JScript Objects," and have been used in several previous chapters in this book. You are already aware that an associative array is a one-dimensional array of pairs. You access the left member of the pair with a numeric index. You access the right member of the pair with a string index equal to the value of the left member of the pair. For example, (left) myArray[1] = "red" but (right) myArray["red"]= "FF0000". Arrays can be created in two ways: by using the new operator on the built-in Array object, or by writing a special-purpose function that takes a generic this object and gives it a size. If you use the former approach, you must specify the number of elements; you may also optionally specify a list of initializers. If you use the latter method, you can also create other properties for the array in the special-purpose function. To actually create the array, you use the new operator together with your special array creation function. For example, if we use the createArray function shown as follows, the statement:

myNewArray = new createArray(6,'')

creates an array called myNewArray with six elements and initializes all of the left members to ''. Note that this is not being done for you by JScript in some magical way. You have to write the creation function and then invoke it with the appropriate parameters.

An array is a primordial JScript object. Its properties, which represent the array members, can be anything. You must set the special property known as length, however, in your creation function. This property gives the length of the array (and is occasionally referred to as the size property). The length property is usually put in the zeroth element of the array during initialization. This means that your arrays will actually have n+1 elements in total: n elements for data, and one extra element at index zero to hold the array size. You need to be sure that your array access functions do not overwrite the zero element. Listing 9.6 shows a general purpose createArray() function.


Listing 9.6  A General-Purpose createArray Function
function createArray(n, init)
{
	  this.size = n  //This initialization is absolutely necessary
	  for (i = 1 ; i <= n ; i++)
			 {
					this[i] = init      //Initialize all of the left hand elements
			 }
	  return this         //Return the  array object to the caller
}

Notice that there is no initialization of the right members of the array. You can arrange also to do this in the createArray function, but only with some effort. This is because the left element must be unique, and we have initialized all the array elements to the same value, namely the value init. If you have two array members containing the same value, you are able to get only to the first one.

In addition to creating arrays, it is often desirable to be able to reset or clear an array so that it may be reused. A special procedure known as a double replacement scheme must be used to clear or reset an existing array. (You can always create a completely new array, of course.) This special approach is needed because you have no way of knowing what values are already stored in the array. In particular, you have no way of knowing that they are unique. The double replacement method uses the following loop to safely reset the array myArray:

myArray[i] = '@@@@@'
myArray['@@@@@'] = ''
myArray[i] = ''

This method uses a special dummy replacement value to manipulate both the left and right sides of the pair. In the preceding example, the string '@@@@@' is used. To avoid the problem of nonunique indices, this dummy value must be highly unusual so that it is extremely unlikely actually to appear in the array.

Caution
Do not try to initialize the right side of an array in the same loop in which you initialize the left side. JScript mixes left and right values for you. If you need to initialize the right side, do it in a separate loop.

Using Associative Pairs

Associative arrays occur far more often than you might think. They occur even in everyday life, although most people do not think of them that way. The picture on the top of a TV dinner box is related to what is in the box. You choose your dinner by looking at the picture because the picture conjures up thoughts of what is in the box. You would not open every box in the freezer and examine its actual contents in order to decide which one to put in the microwave. Programmers tend to think of an association in somewhat less colorful terms such as a = b, x = 3y2, and so on. A Windows .INI file is an excellent example of the use of associative pairs. Every entry has a left element, such as *.DOC, and a right element, such as C:\WINWORD\WINWORD.EXE. In this case, the association is a relationship between a file suffix and the application that created it. Hypertext links are also associations; they are just ordered backward. The following HTML:

<A HREF='http://www.myfavoritelink.com'>My favorite link</A> 

is actually an associative pair. If we were to place this into an array, most of us would place My favorite link on the left side, and 'http://www.myfavoritelink.com' on the right. We often reference complicated, large, and sometimes obtuse objects with less complicated words or nicknames.

An Enhanced Array Object. A simple associative pair array may not be sufficient for your needs. Because an array is just an unstructured object, we can conveniently make it into a more complex object. For example, we can add a description property to an array. This is useful because the array may have a short name that is easy to use elsewhere in our code, but that name may not be very illustrative of the array's purpose.

Another property we might want to add is a property that reflects the "element of interest right this minute" or the "current" element. An example of this might be the strings in a list box. The current property could refer to the currently selected item. This might be called the currentIndex property, or, more tersely, the nDx property. (Remember that JScript is case-sensitive.) Finally, if we are using the list as some kind of a stack, or if we are keeping track of items that are constantly being added or deleted from the list, we might need to know where the next open slot is located. We will call this property the nextIndex property.

But where do we put these properties? Well, properties are just array elements, so the question is where in the element list they should be placed. If we put them at the beginning, the array elements proper do not start at index=1. If we put them at the end, the array elements are in the right place, but it is now more difficult to increase the size of the array. This is because there are referencing problems if you access the properties by their array index rather than by their names. We will examine the advantages and drawbacks of both approaches.

Array Initialization and Storage. While associative arrays are an extremely powerful programmer tool, there are some pitfalls in using arrays. Most of them stem from a lack of initialization or from incorrect initialization. The file c9-array.htm on the CD-ROM contains several array initialization and array manipulation functions. Various array functions are given that present different approaches to the location of the enhanced array object properties, such as currentIndex and nextIndex, and how (or whether), the array is initialized.

The page generated by this file has buttons to allow you to initialize various arrays, reset them, clear them to null, enter a single value, and fill the arrays. Each array is treated in the same fashion. After you have exercised one or more of these functions, you can then look at the contents of each array to see the effect. The array creation functions are organized into two categories: those that place the enhanced properties at the end, and those that place the enhanced properties at the beginning. The first four array creation methods place the enhanced properties at the end of the array and initialize all empty slots to @. They differ in how they handle the enhanced properties.

The final three creation methods place the enhanced properties at the beginning of the array. This set of methods can be used to examine the consequences of not initializing the array or initializing all of the elements (including those of the special properties) to the same thing, and of initializing only the empty elements. These functions are obviously not the only possible ways in which such array functions can be written. Listings 9.7 and 9.8 show two of these functions. Notice that it is not possible to create an uninitialized array and still place the extended properties at the bottom.


Listing 9.7  c9-array.htm-A Function to Create an Array with Special Properties Added at the End
function createArray1_d(n,init)
{ 
	  var i = 0
	  this.length = n + 3
	  for (i = 1 ; i <= n ; i++) 
			 {
					this[i] = init               
			 }
	  this.description = 'desc'
	  this.nDx = 'nx'
	  this.nextIndex = 'ni'
	  return this
}


Listing 9.8  c9-array.htm-An Array Creation Routine with Extended Properties at the Beginning
function createArray3(n,init)
{ 
	  var i = 0
	  this.length = n + 3
	  this.description = 'dc'
	  this.nDx = 'nx'
	  this.nextIndex = 'ni'
	  for (i = 4 ; i <= n + 3 ; i++) 
			 {
					this[i] = init               
			 }
	  return this
}

When you first load the c9-array.htm page, as shown in Figure 9.5, all of its arrays have been created; those arrays which initialize themselves have done so. Initialization has been done only for the left elements. No initialization has been done for the right elements. The viewing routine has been set to look at the first unused element. This will be the element at index n + 1, which is not yet processed or initialized in any way.

For purposes of this discussion, we call those array elements that are not directly related to a property the empty elements. Those that are associated with properties are called the special elements. Each element consists of a left and right element. The left element of the zeroth element of the array has the array size in it.

Caution
Platform dependencies have been reported in the current release of Internet Explorer. You may find array functionality to be different depending on the platform on which you are running the browser. Try the code in c9-array.htm and c9-arrx.htm as test cases.

Figure 9.5 : JScript's associative arrays can be accessed using numerical or named indices.

First, let's look at the those arrays that have the special elements at the end of the array. Click the 1a button. This creation routine initializes all of the empty elements to "@" and the special elements to "". Did you expect to see the property names in the special elements? They do not appear here. They are properties associated with the array object, not values within the array object. This creation scheme works as you would expect.

Before you breathe a sigh of relief, let's look at the next array creation method (associated with button 1b). Because we could not tell which element was which, we decided to initialize the left side of each special element with the name of the property associated with it. All the empty elements have the initial value @ and all the special elements are dutifully null, as shown in Figure 9.6.

Figure 9.6 : JScript fills uninitialized associative array values with null.

Button 1c manipulates an array in which all of the special elements have been initialized to the same value as the empty elements. There are no surprises here. The final creation method of the first series, method 1d, initializes the special elements to two unique characters for each related property. Once again, the behavior is quite uniform.

The three creation methods in the second series put the special elements at the beginning of the array. Using the first of these three methods, it is possible to produce a completely uninitialized array. Still, nothing unusual happens; the empty elements are just null. The second of these creation methods initializes both the special elements and the empty elements. The very last creation method initializes only the empty elements; it also behaves properly.

Instead of clearing or reinitializing the arrays at this point, click the rightmost button named Enter One Value/Array. This button does exactly that. It enters a "red","FF0000" pair into anArray[2] for those arrays with special properties at the end and into anArray[6] for those arrays with special properties at the beginning.

The array created by method 1a offers up no more surprises in this case. The value pair has been entered in the correct place. Notice that the array uses one-based indexing in terms of reference as well. The arrays created by the other methods with the special elements at the end (methods 1b2 through 1c) are also well-behaved, as are the three methods with the special elements at the beginning

To continue our array experimentation, let's reinitialize the arrays, preferably by reloading the file. Then click the Fill Arrays button. This fills the empty elements of the array with pairs in the form of name=month. Repeat your inspection process, and you should uniformly see something like Figure 9.7.

Figure 9.7 : Entering data into an uninitialized array fills in the values.

You can perform a variation on this experiment by reinitializing the arrays and then clearing them using either method. Use the Initialize and Clear (or Clear to Null) buttons, and then add a single value to all of the arrays or simply fill the arrays. The unused elements may be quirky, but the array elements themselves behave properly.

The companion page in the file C9-ARRX.HTM provides a variation on the same theme. It is the same as the code in C9-ARRAY.HTM, except that all its arrays have an extra uninitialized element just after the original eight array elements. As before, a first unused element is displayed. It is worthwhile to go through the same procedures and see how the various arrays behave.

Caution
Initialize your arrays before you use them! Uninitialized arrays cannot be trusted to provide correct results when referenced. They cannot even be trusted to provide consistently incorrect results, and can be a nightmare to debug.

Filling ADocument.write() places all of its output into an ASCII stream. Think of it as one big string that exists somewhere in memory. The browser does not get to interpret the stream until you specifically say, "That's all, folks!" with a document.close(). Once you close the document, everything that you have written (hopefully) is rendered in the browser window. This also means that any script errors will not be noticed until the document is actually closed.rrays from Paired Lists. Listing 9.9 is worth special attention. It provides a function named fillArrayFromLists() which will fill the associative pairs of an array from two parallel lists of delimited elements. It is a lot faster to write such lists than it is to specifically set each right and left element in an array. This function uses the library function word(), which was described earlier in this chapter. You may recall that this function uses a character delimiter to separate the elements in the list. The separator can be any character; the most common one is a comma (,). HyperCard enthusiasts will recognize this as an item list. The fillArrayFromLists function will be found in the file c9-array.htm on the CD-ROM.


Listing 9.9  c9-array.htm-A Function to Load an Associative Array from Two Delimited Lists
function fillArrayFromLists(anArray,aaList,bbList,sep,s,f)
{
	  var lstr = ''// left hand array element
	  var rstr = ''// right hand array element
	  var i = 0// iteration variable
	  var counter = 1
		for (i = s ; i <=f ; i++) 
			 {
					anArray[i] = word(sep,counter,aaList)
					anArray[anArray[i]] = word(sep,counter,bbList)
					counter++          
			 }
}

This function takes six parameters: the array anArray, whose values are to be set; two delimited strings, aaList and bbList, which hold the right and left elements; the string delimiter sep; and the first s and last f indices in anArray, which are to be set. The function executes a simple for loop over all the array indices from s to f, inclusive. In each iteration, it calls the word() function twice to extract the right and left elements from aaList and bbList.

As a demonstration of the power of this function, let us invoke it using two short lists. In our case, we use the asterisk character (*) as the delimiter. The following code shows each person on the list aList being associated with a particular month, as given in the list bList:

var aList = 'Mona*Jane*Barbara*Sandra*Maxine'
var bList = 'January*March*September*February*December'
fillArrayFromLists(array1,aList,bList,'*',1,5)

Using the Enhanced Array Object. Now that we have created our enhanced array object and dissected it at great length, how can we use it and which version should we adopt? Although all of the creation methods we have tested work if the array is properly initialized, we will use the final version of the methods that place their special properties first in our subsequent code. This is the one that did not overwrite our special properties. Most of the time, only the left elements of the associative pair are used, but these lists can also hold an associated object (right element).

Tip
The enhanced array object can be used to implement a string list, object list, or a stack.

One array that arises frequently in JScript applications is an array of newly created child windows, because JScript does not provide such an array by default. Listing 9.10 shows the function winArrayAdd(), which can be used to add a window to such a window array. Note that this function uses global variables to keep track of the next available array slot (in nextWin) as well as the current window (in curWin).


Listing 9.10  A Function to Add a Window to a Window-Tracking Array
function winArrayAdd(aWinHdl)
{ 
	  // set next open slot in winArray to hold this window handle
	  // and that window's creator
	  aWinArray[nextWin] = aNewWin
	  aWinArray[aWinArray[nextWin]] = aNewWin.opener
	  curWin = nextWin   // make this the current window
	  nextWin++          // increment next available slot pointer
} 

Instead of using global variables, we can, of course, use an array with special properties
as well. The code in Listing 9.11 illustrates this form of the window addition function. While Listing 9.11 does not look much different from Listing 9.10, the latter version is the preferred one. This is because it keeps essential window-tracking information together. Other functions may therefore access all the relevant data by simply examining appropriate elements of the array, rather than having to look at the array and also consult some global variables.


Listing 9.11  A Better Version of the Window Add Function Using Special Properties
function winArrayAdd(aWinHdl)
{ 
	  // set next open slot in winArray to hold this window handle
	  // and that window's creator
	  aWinArray[aWinArray.nextIndex] = aNewWin
	  aWinArray[aWinArray[nextWin]] = aNewWin.openerr
// make this the current window
	  aWinArray.ndx = aWinArray.nextIndex
	  nextWin++       // increment next available slot pointer
} 

Arrays of Arrays

One of the biggest criticisms of associative arrays is that they are one-dimensional. The argument can be made that the left and right sides constitute a second dimension, of size two, but this is not really a true multidimensional array. In fact, two-dimensional arrays are really just arrays of arrays. Because of the nature of associative arrays, one can develop such complex structures with relative ease.

Simulating Multidimensional Arrays. Imagine, if you will, an array of colors. In a simple implementation of such an array, the left element contains the name of the color, such as "red," and the right member contains the hexadecimal value for the color. For red, this value is 0xFF0000. There are many shades of red, however. Of course, we could add all of these variant names to the array. It would be much nicer to be able to go to an array, find "red," and then access into a list of colors which were various shades of red. Let's now construct an example in which we can do that. As usual, the left element is initialized to "red." The right element, however, holds a handle to another array called moreReds. The array moreReds contains the typical colorname=hexvalue pair. Listing 9.12 shows a very simple way of creating such a multidimensional array.


Listing 9.12  A First Approach to Creating a Multidimensional Array
colors = new createArray(9,'')

moreRed = new createArray(20,'')
moreYellow = new createArray(20,'')
moreOrange = new createArray(20,'')
moreBrown = new createArray(20,'')
moreGreen = new createArray(20,'')
moreBlue = new createArray(20,'')
morePurple = new createArray(20,'')
moreGray = new createArray(20,'')
moreWhite = new createArray(20,'')

...

Not only is this tedious, but it's not even a complete solution. We have to put each of these secondary arrays into the right elements of the first array. There must be an easier way to do this. We need to develop an approach to getting the various values into the multidimensional array and getting them out again.

Listing 9.13 contains three functions. The createArray() function is very similar to the array constructor we have already seen. The fillcolorArray() function fills both the left and right elements of an associative array, which is passed in as a parameter. This means that if you execute the statement:

anArray = fillColorArray(anArray,'green','gr')

the left elements are filled with green1, green2,..., and the right elements are filled with gr1, gr2,.... The third function, lotsOfColors(), is used to orchestrate the creation and initialization of this array. This function also uses our old friend the word() function.

One might think of using eval() to metamorphose a constructed string into an array handle, as in the following statement:

dstr = eval(dstr = new createArray(20,''))

In fact, this is not necessary. Here, we see another tribute to the flexibility of JScript variables, because the following statement works just fine:

dstr = new createArray(20,'')

Try this in C or Pascal! The resulting value of dstr is then passed to fillColorArray() to be stuffed with values. When this function returns, dstr is plugged into the right value of the current array pair.


Listing 9.13  Three Functions Used to Create an Associative Array of Associative Arrays
function createArray(n,init)
//n          size of array
//init     what you want all values initialized to
{ 
			 var i = 0
			 this.length = n  // set the size of the array
			 for (i = 1 ; i < n ; i++) 
					{
						  this[i] = init   // fill the array with "init"      
					}
			 return this          // return the newly created array
}

function fillColorArray(anArray,init,init2)
//  anArray is the array to be filled
//  init holds the values for the left side
//  init2 holds the values for the right side
{ 
			 var i = 0
			 var astr = ''
			 var bstr = ''
			 var n = anArray.length     // get array length
			 for (i = 1 ; i <= n ; i++) // iterate over each element
					{
						  astr = init + i       // get left value
						  bstr = init2 + i      // get right value
						  anArray[i] = astr     // set left
						  anArray[astr] = bstr  // set right
					}
			 return anArray             // return modified array
}

function lotsOfColors()
{
	  var cstr
// colors will be an array of 9 colors
// colorstring will be the names of those colors, delimited by commas
	  var colors = new createArray(9,'')
	  var colorstring = 'red,yellow,orange,brown,green,blue,purple,gray,white'
	  for (i = 1 ; i <= 9 ; i++ )    // iterate over array
			 {
// extract the i-th element
					cstr = word(',', i , colorstring)  
					colors[i] = cstr           // set left value
					dstr = 'more' + cstr
// create and initialize right value in next two statements
					dstr = eval(dstr = new createArray(20,''))
					dstr = fillColorArray(dstr,cstr,cstr.substring(0,2))
					colors[colors[i]] = dstr   // set right value
// next three statements are for debugging
// they display the values set in an alert
					astr = colors[cstr][3] 
					bstr = colors[cstr][astr]
					alert( astr + '\n' + bstr)
			 }
}

The function lotsOfColors() warrants close attention. It starts off by creating the associative array, colors, as well as a comma-delimited string that lists the colors it uses. It then cycles through a loop for each color to be processed. It first extracts the color we want from colorstring (as a string) and then sets the left element of the current associative pair to that string. Thus, colors[1] yields "red." That's the easy part. How do we name the array handles that we place into the right member? We can construct an element named "moreRed," but the "moreRed" is a string. Fortunately, JScript permits a string to be enough of a chameleon that it can be turned into an array handle.

Caution
Arrays should be initialized and filled in different routines. If you attempt to perform these operations in the same routine, strange substitutions, which are symptomatic of an uninitialized array, will result.

Using a Multidimensional Array. So far, we have not tried to access the secondary members of this construction. Surprisingly enough, that is not much harder than accessing the usual one-dimensional associative array. In fact, the debugging code at the end of lotsOfColors() already shows how this is done:

astr = colors[cstr][3] 
bstr = colors[cstr][astr]
alert( astr + '\n' + bstr)

The variable astr holds the left element, and bstr the right element. If you attempt to do this in one step, it appears confusing. If you substitute the value of astr in the second expression, you get the following massive expression:

bstr = colors[cstr][colors[cstr][3]]

Note that we placed some convenient but arbitrary values into our subsidiary arrays. We could have had the fillColorArray() function generate successive, properly spaced hex values that were within the appropriate color range. In general, it is more likely that you will want to use these kinds of arrays to keep arbitrary data.

Some JScript HTML Objects

This section reviews some of the fundamental concepts of HTML objects in JScript, and then proceeds to the more complex Image and Text objects described at the beginning of this chapter. By this point, you are well acquainted with the various HTML objects in JScript. The focus of this section is to explore some of their innovative uses. Many of the "tricks" of JScript revolve around its polymorphism, namely its capability to view a single thing in different ways.

JScript strings provide an excellent example of this polymorphism. Strings in JScript can be thought of as HTML objects, in addition to their usual meaning. This is because JScript provides methods for giving strings many of the formatted characteristics of HTML text, such as bold (<B>), italics (<I>), big (<BIG>), link (<A>), and so on. When you use a construction such as:

mystring = 'This is some text.'
mystring = mystring.bold,

mystring becomes <B>This is some text.</B>. We will use methods such as these in the Text object we create.

HTML string methods are discussed in great detail in the "String Object" section of Chapter 5 "Built-In JScript Objects."

Objects Revisited

By now, you are intimately aware that objects are just arrays of properties. However, properties are accessed somewhat differently from array elements, and they usually only use the left side of the array. New properties can be added to an object at any time. This is possible because only the left element of the array is used. Trying to add new array elements on-the-fly when you are using both sides of the array element is fraught with peril, as we have seen previously.

Objects can have properties or methods. Methods are simply functions that have been declared as properties. For example, you might have a color object. Colors are cited in terms of their red, green, and blue components (at least in Web browsers; there are other color-mixing schemes). Many of the common colors also have common names. To declare a color object, we must make a constructor/creator, as shown in Listing 9.14. This is no different from the type of construction methods we have seen for arrays.


Listing 9.14  Creating a Simple Color Object
function createColor(name,red,green,blue)
{
	  this.name        =   name
	  this.red         =    red
	  this.green       =   green
	  this.blue        =   blue
	  this.length      =      4
	  return this
}

myGreenColorObject = new createColor('green','22','DD','22')

Property Taxes. Object constructors really just reserve space for the property array elements. We can access the property values by their array indices, as well as by using the dot operator (.). This is sometimes extremely useful for storing objects. To convert the color object into a string delimiter with the @ character, the code in Listing 9.15 might be used. This type of string is often useful for storing objects persistently, as we shall see in Chapter 17, "Using Frames and Cookies."
Cookies may be used to store objects represented as delimited strings. The "Cookies" section of Chapter 17, "Using Frames and Cookies," describes this approach in great detail.


Listing 9.15  Storing an Object's Properties as a Delimited String
function storeColor(aColorObject)
{
	  var k = aColorObject.length              // number of elements in object
	  var astr = ''                            // initialize to empty string
	  var i = 0
	  for(i = 1 ; i <= k ; i++)                // iterate over elements
			 {
					astr += aColorObject [i] + '@' // append to astr.
			 }
	  return astr                              // return delimited string
}

You can easily reverse this process, as Listing 9.16 shows. The function getColor() undoes the work done by the function storeColor of Listing 9.15. It tears apart the delimited string passed as its second argument astr, and sets the properties of aColorObject accordingly.


Listing 9.16  Retrieving an Object's Properties from a Delimited String
function getColor(aColorObject,astr)
{
	  var lastloc = astr.lastIndexOf('@') // final delimiter
	  var n = 0                           // number of delimiters found
	  var f = 0                           // location of current delimited
	  var i = 1                           // current array index
	  while ( n >= 0 )
			 {
// find next delimiter, store in local variable f
					f = astr.indexOf(astr,n)
// set the i-th array element to the string between the last delimiter
// ( at location n ) and the current delimiter ( at location f )
					aColorObject[i] = astr.substring(n,f)
// if we are at the very last delimiter ( at location lastloc )
					if (f == lastloc)
						  {
// then take the substring from the current position up to the end
// of the string and break out of the while loop
								 aColorObject[i+1] = astr.substring(f+1,astr.length)
								 break     
						  }
// if we are not at the very last delimiter then repeat the process
					else n = f  + 1
// update array index
					 i++
			 }     
}

These examples are not the only ways to perform either of these operations. The storage function storeColor() of Listing 9.15 could also have been implemented as shown in Listing 9.17, for example. We could also use the word() function to retrieve the properties of an object from a string in the getColor() function. This would be slower than the approach shown in Listing 9.16, however.


Listing 9.17  An Alternative Color Storage Function Using the for..in Statement
function storeColor(aColorObject) 
     { 
			var astr = "" 
			for (var i in aColorObject) 
					  astr += aColorObject[i] + '@' 
			return astr 
	  }

Creating Your Own Methods. It would be nice if the color object knew how to do its own conversion to and from the delimited string representation. Because we have already written the essential conversion functions, it's easy to make them methods of the color object: We just assign them as properties of the object itself. If we use this approach, our color object constructor is as shown in Listing 9.18.


Listing 9.18  Creating a color Object with Methods and Properties
function createBetterColor(name,red,green,blue)
{
	  this.name           =   name
	  this.red            =    red
	  this.green          =   green
	  this.blue           =   blue
	  this.toString       =    storeColor
	  this.fromString     =       getColor
	  this.length         =     6
	  return this
}

myGreenColorObject = new createBetterColor('green','22','DD','22')

Note
Always set the length property of an object in your constructor. Although the current implementation of JScript will automatically create a length property in the element at index=0, and keep that length property updated as new elements are added, this behavior is not guaranteed.

If you are going to convert a function into a method, you can (and should) alter it somewhat to exploit the fact that it is now a method function. If a function is not a method function of an object, then it may access that object only if the object is passed as a parameter to the function, or if the object has been declared globally. Once you make the function a method of the object, the function can reference the object that contains it via the keyword this. Consequently, we can rewrite the storeColor method to look like Listing 9.19.


Listing 9.19  Storing an Object's Properties Using a Method Function
function storeColor()
{
	  var k = this.length
	  var astr = ''
	  var i = 0
	  for(i = 1 ; i <= k ; i++)
			 {
					astr += this[i] + '@'
			 }
	  return astr
}

Note
In order to change a function into a method function of an object, follow these three steps:
  1. Make the method a property of the object, as in aColor.toString = storeColor.
  2. Do not pass the object as a parameter to the function.
  3. Change all explicit references to the object to the keyword this; for example, 1  =  aColorObject.length becomes 1 = this.length.

Images as Objects

Drawing images is a complex affair. One of the most important things to remember about image manipulation in terms of JScript is that all images must be characterized by HEIGHT and WIDTH modifiers in their HTML tags. If you leave these modifiers out, JScript will misbehave or won't function at all. We will create an Image object that encapsulates all the important Image properties, including the height and width. The Image object will also know how to display itself in several contexts. The constructor for the Image object is shown in Listing 9.20. All of the code for the Image object will be found in the file c9-image.htm on the CD-ROM.


Listing 9.20  c9-image.htm-A Comprehensive Constructor for an Image Object
?
function createImage(title, filename, height, width, vspace, hspace,
                    border, bordercolor, frame, framecolor, href, notes)
{ 
	  this.title          = title
     this.filename       = filename
     this.height         = height
     this.width          = width
     this.vspace         = vspace
     this.hspace         = hspace
	  this.border         = border     
	  this.bordercolor    = bordercolor     
     this.frame          = frame
     this.framecolor     = framecolor
     this.href           = href
     this.notes          = notes
     this.draw           = drawImage
     this.frame          = frameImage
     this.reference     = referenceImage
     this.popup         = popImage
     return this
}

Properties and Methods of the Image Object. If you examine the constructor for the Image object shown in Listing 9.20, you see that it not only encapsulates the normal HTML qualifiers for an image, but also has properties for a file spec, a title, an associated URL, and notes. It can present itself as a plain image, a framed image, or a linked image. It can also pop itself up in a window of its own. The image "draws" itself by presenting you with a string that you can send to a nascent document using document.write(). The code in Listings 9.21, 9.22, and 9.23 shows the methods used to draw a plain image, a linked image, and a framed image.

Documents created on-the-fly, also known as nascent documents, are discussed thoroughly in the "Nascent Document" section of Chapter 8 "Dynamic HTML and IE Objects," p. 229.


Listing 9.21  c9-image.htm-The Image Object's Plain draw Method
function drawImage(how,border)
{
      var astr = ''
// if the image does not use this modifier
// the parameter can be empty or '^'
      astr = '<IMG SRC="' + this.filename + '"' 
		if (how != '') astr += ' ALIGN=' + how
      if (this.height != '') astr += ' HEIGHT=' + this.height
      if (this.width != '' && this.width != '^')
			 astr += ' WIDTH=' + this.width
      if (this.vspace != '' && this.vspace != '^')
			astr += ' VSPACE=' + this.vspace
      if (this.hspace != '' && this.hspace != '^')
			astr += ' HSPACE=' + this.hspace
      if (this.border != '' && this.border != '^')
			astr += ' BORDER=' + this.border
      astr +='>'
      if (this.border != '' && this.border != '^')
			 astr = '<FONT COLOR=' +
			 this.bordercolor + '>' + astr + '</FONT>'
      return astr

}


Listing 9.22  c9-image.htm-The Image Object's Linked draw (Reference) Method
function referenceImage(how,border,ref,atext)
{
      if (ref == '') ref=this.href 
		if (ref == '') ref = location.href
      if (atext == '') atext = 'Your text here!'
      var astr = '<A HREF=' + ref + '>'
      astr += '<IMG SRC="' + this.filename + '"' 
		if (how != '') astr += ' ALIGN=' + how
      if (this.height != '') astr += ' HEIGHT=' + this.height
      if (this.width != '' && this.width != '^')
			 astr += ' WIDTH=' + this.width
      if (this.vspace != '' && this.width != '^')
			 astr += ' VSPACE=' + this.vspace
      if (this.hspace != '' && this.width != '^')
			 astr += ' WIDTH=' + this.hspace
      if ('' + border != '') astr += ' BORDER=' + this.border
      astr +='>'
      astr += atext
      astr += '</A>'
      return astr

}


Listing 9.23  c9-image.htm-The Image Object's Framed draw Method
function frameImage(how,border,leading)
{ 
     var astr = '<TABLE '
	  if (how != '') astr += ' ALIGN=' + how
     if ('' + border != '') astr += ' BORDER=' + border
     if ('' + leading != '') astr += ' CELLSPACING=' + leading
     astr += '><TR><TD ALIGN=CENTER>'
     var bstr = '</TD></TR></TABLE>'
     astr += this.draw('',2)
	  astr += bstr
     return astr
}

Notice that the frameImage method essentially draws the table structure, which is the "frame," and then calls the drawImage method to draw the image. In all cases, the image can have a border. In both the drawImage and frameImage methods, the border color is determined by the current font color. Because the Image object encapsulates the border color, it is as easy to include it as it is to include any other modifier. Referenced image borders, though, do not have such a choice; they will be the link color or vlink color specified for the page.

Using the Image Object. You can use the Image object to make a database of your images. A file name, notes (which can include a caption for the image), and an URL are included as part of the object, as we have seen. The example program on the CD-ROM (file C9-IMAGE.HTM) arbitrarily sets up a trivial database of six Image objects held in an array. It can present these images as plain, framed, or referenced. It can also pop up any image in its own window. Listing 9.24 shows the method function that creates the pop-up.


Listing 9.24  c9-image.htm-The Pop-Up Method of the Image Object
function popImage(title)
{
     var w = 50 + parseInt(this.width)    // image width as displayed
     var h = parseInt(this.height) + 50   // image height as displayed
     scrl = 'no'// do scrolling?
	  if (w>640){w=640;scrl='yes'}          // if width too big them scroll
     if (h>480){h=480;scrl='yes'}          // if height too big then scroll
// create HTML attributes for the image
     var whstr ='WIDTH=' + w + ',HEIGHT=' + h +
				  'RESIZABLE=yes,SCROLLBARS=' + scrl
// open a new window
     aNewWin = self.open('',title,whstr)
// if the new window could not be created…
	  if (!aNewWin)
			 {
// notify the user that it failed, and try to indicate
// possible causes
                  var alertstr = "Could not open a new window."
						alertstr += " A window of named " + title
                  alertstr += " may already be open."
						alertstr += " You may also be out of memory"
						alert(alertstr)
						return;
}
// if new window was created successfully…
// create the HTML to display the image
      var astr = '<HTML><HEAD>'
      astr += '<BASE HREF="' + location.href + '">' 
		astr += '<TITLE>' + this.title + '</TITLE>'
      astr += '</HEAD><BODY>'
// write out that HTML
      aNewWin.document.write(astr)
		var bstr = '<CENTER>' + this.draw('CENTER') + '</CENTER>'
      aNewWin.document.write(bstr)
		aNewWin.document.write('</BODY></HTML>')
// close the document
      aNewWin.document.close()
}

The resulting image display is shown in Figure 9.8. The page generated by the CD-ROM file c9-image.htm is covered with pop-up windows containing its tiny database of six images. The images were placed in the pages arbitrarily; the page has no input interface. Any single image can be popped up by placing its number in the text box next to the Image button and clicking the button. Figures 9.9 and 9.10 show the plain and framed versions of the image displayed in a catalog format. The image catalog is nothing more than a scrolling display of all the images in the database.

Figure 9.8 : The Image object can be activated to pop up an image database.

Figure 9.9 : The image database may be displayed as a scrolling window of plain images.

Figure 9.10: The image database may be displayed as a scrolling catalog of framed images.

Most of the draw methods presented here accept a string terminator as a parameter, and also return a string. The terminator is usually the carriage return character, \n. This results in a string with linebreaks suitable for an alert. You can also use <BR> as the string terminator; this yields a string that can be nicely presented in a window. The utility function showInWindow in Listing 9.25 pops up a window and tries to put anything you hand it into that window.


Listing 9.25  c9-image.htm-A Function that Shows an Object in a Pop-Up Window
function showInWindow(tstr,title)
{
     var w = 300
     var h = 300
     scrl = 'yes'
     if (w>640){w=640;scrl='yes'}
	  if (h>480){h=480;scrl='yes'}
	  var whstr ='SCROLLBARS=' + scrl + ',RESIZABLE=yes,WIDTH=' + w +
						',HEIGHT=' + h
     aNewWin = self.open('',title,whstr)
	  var astr = '<HTML><HEAD>'
     astr += '<BASE HREF="' + location.href + '">' 
	  astr += '<TITLE>' + title + '</TITLE>'
     astr += '</HEAD><BODY>'
     aNewWin.document.write(astr)
	  aNewWin.document.write(tstr)
	  aNewWin.document.write('</BODY></HTML>')
	  aNewWin.document.close()
}

The Text Object

The approach we employed for the Image object can be used for text manipulation as well. We will create a Text object that holds various important properties related to how the text is displayed. This Text object will be very similar to the Image object in terms of the type of draw methods which it supports. The constructor function for the Text object is shown in Listing 9.26. All the code for the Text object will be found in the CD-ROM file c9-text.htm.


Listing 9.26  c9-text.htm-The Constructor Function for the Text Object
function createText(type,title,text,size,color,bold,italic,
                  supersub,frame,href,notes)
{ 
	  this.type =       type
     this.title=       title
     this.text =       text
     this.size =       size
     this.color =      color
     this.bold =       bold
     this.italic  =    italic
     this.supersub =   supersub
     this.frame  =     frame
     this.href  =      href
     this.notes  =     notes
     this.draw =       drawText
     this.frame =      frameText
     this.reference = referenceText
     this.popup =     popText
     this.lines =      stringToList
     this.word =       word
     return this
}

Notice that this function is very similar to the constructor for the Image object. It encapsulates some of JScript's own string properties. This object, too, knows how to present itself as plain, framed, or referenced text. It can also pop itself up in a window. Note that it has a type property; that is, the text can be a list, a paragraph, and so on. Its draw method accepts a parameter that allows you to draw the text according to a predefined type method or to draw the text using the properties you have set.

The Text object has some methods that can access the text as lines or words. Again, this is a crude emulation of HyperCard. These methods allow you to define the separator which separates one phrase from another. The stringToList method accepts a prefix and a suffix as well as the list separator. It separates the string into phrases and then adds the prefix and suffix given. Use the \n character as the separator to get logical lines. Our old friend, the word() function, is used to return the word at the position you specify. The implementation of the stringToList method is shown in Listing 9.27.


Listing 9.27  c9-image.htm-A Function to Separate Delimited Strings into Phrases
function stringToList(sep, what, pref,suf)
// sep = separator (delimiter)
// what = object to use
// pref = prefix to prepend
// suf = suffix to append
{ 
			 var n = 0           // location of current separator
          var f = 0           // location of next separator
          var astr = ''      // string being built
          var nstr = ''      // work string
          var finished = false
              if ( what == null ) what = this
          while (f >= 0 )
					{
// get position of next separator
                    f = what.indexOf(sep,n)
// if this the last one?
						  if (f == what.lastIndexOf(sep))
								 {         
// if so then get the end of the string
                              f = what.length
// and set the finished flag
                              finished = true
                         }
// get the string, prepend the prefix and append the suffix
                    nstr = pref + what.substring(n,f) + suf
// add the current working string onto the final string
                    astr += nstr
// if all done then break out of the loop
                    if (finished ) break
// advance to the next separator
                    n = f + 1
               }
	  return astr              // return the completed string
}

Note that this function uses an interesting trick to decide when it has come to the end of the string. It uses lastIndexOf() to get the character position of the last occurrence of the separator. As it walks the string, it checks to see whether the character found equals the last instance of that character. If it does, it sets the phrase end to the end of the string, performs its usual string separation, and then quits.

Another useful routine, which can easily be modified for your own purpose is the makeTitle method. MakeTitle goes through a string and eliminates spaces, but capitalizes the letter after the space. This is useful for catching two-word window names, such as "Hi there," that a user has entered. If you hand the open() command more than one word, it simply does not open the window, nor does it tell you why. The makeTitle method converts such strings into more acceptable titles, such as "HiThere" in this case. The makeTitle function is shown in Listing 9.28. Figure 9.11 shows the result of exercising the pop-up capabilities of the Text object.


Listing 9.28  c9-text.htm-A Function to Create Window Title Strings
function makeTitle(what)
{ 
     var n = what.length       // length of string "what"
	  var i = 0                   // iteration variable
     var cc = ''                 // current character
     var accstr=''              // output string being built
     for (i = 0 ; i < n ; i++)   // iterate over the whole string
          {
// get character at position "i"
					cc = what.substring(i,i+1)
// if that character is not a space, then add it to the output string
                    if ( cc != ' ' ) 
								 {
										accstr += cc
                              continue
                         }
						  else
                         {
// if that character is a space then skip it
                              i++
// grab the first character after the space
                              cc=what.substring(i,i + 1)
// convert it to uppercase 
										cc = cc.toUpperCase()
// add that uppercase letter onto the output string
                              accstr += cc
                         }                         
			 }
return accstr                            // return the output string
}

Figure 9.11: The database of text objects can display itself in pop-up windows.