Copyright ©1996, Que Corporation. All rights reserved. No part of this book may be used or reproduced in any form or by any means, or stored in a database or retrieval system without prior written permission of the publisher except in the case of brief quotations embodied in critical articles and reviews. Making copies of any part of this book for any purpose other than your own personal use is a violation of United States copyright laws. For information, address Que Corporation, 201 West 103rd Street, Indianapolis, IN 46290 or at support@mcp.com.
Notice: This material is excerpted from Special Edition Using JavaScript, ISBN: 0-7897-0758-6. This material is provided "as is" without any warranty of any kind. Please see this DISCLAIMER.
by Mark Reynolds
The idea of object oriented programming is not a new one. It actually dates back over thirty years, and has gone through several phases of popularity in that time. Currently, object oriented programming is considered by many to be an established concept which should be part of all modern programming languages. Unfortunately, there are several different conflicting definitions of "object oriented programming." Fortunately, there are some key concepts which are shared by (almost) all versions of objected oriented programming.
An analogy may be made between home ownership and object oriented programming. Everyone's house has a kitchen, some bedrooms and bathrooms, stairs, flooring and so forth. Some homes will have spiral staircases, Art Deco ironwork, and a gazebo in the back yard. Others will have a completely utilitarian layout based on a linear architecture with not a rounded corner in sight. In speaking about your home you will describe both the basic aspects ("yes, of course we have a basement") and also the embellishments ("the basement has a painfully hard cement floor"). In speaking about what an "object" means in JavaScript it will be necessary to also speak at two levels. The basic aspects of the way JavaScript handles objects is known as its object model. The embellishments constitute the extensive set of features of the predefined objects in JavaScript, as well as those aspects of the language which may be used to create and use new, user defined objects.
This chapter you will learn the particulars of the JavaScript object model, and will also be introduced to the various built-in objects and their uses. After completing this chapter you will be able to:
Before we can delve into object oriented programming in JavaScript it is first necessary to review some of the basic concepts of object oriented programming itself. We have already had a brief introduction in the "Functions and Objects" section of Chapter 2. This section will take use further, and explain several critical and often misunderstand ideas.
We already know that an object is basically a container for related items. Rather than carry around money and credit cards in many different pockets and folders many people choose a more unified method: they keep their money in a wallet. Perhaps they even keep their change in a changepurse. The wallet is a container for related items. This is not to say that all such items must be in that wallet; for even the most organized individuals this is often a near-impossible goal. As a flexible principle, however, it is of enormous utility.
Objects operate the same way. Objects collect related data items in a single place and make it simpler, or at least more logical, to access those items. As we have already seen JavaScript refers to the items collected within an object as its properties. You will also recall that JavaScript objects may not only store data, they may also store functions. It is useful to keep functions which manipulate data items in a specific way with those data items themselves. These functions are known as the methods of an object.
The JavaScript Date object is a perfect example of the benefits of this kind of organization. As the name implies a JavaScript Date object is used to store a date, and also a time. The Date object also has a very particular set of methods which are useful in converting string representations of dates in Date objects. While these functions are vitally important when manipulating strings such as "Nov 23, 1990" they do not really have sweeping application elsewhere. In a word, they are date-specific. It makes good sense to keep these methods with Date objects, rather than making them generally available functions.
In addition to the concepts of object, property and method there is a fourth, somewhat more subtle, concept which is also of great importance: the instance. The relationship between an object and an instance of an object is the same as the realtionship between a data type and a variable of that data type. In the typeless language such as JavaScript this distinction is blurred, but is still present. Another way to think of this distinction is to think of an object as a set of shelves, some of the which may be occupied while others are not. You convert that object into an instance when you completely fill in all the empty shelves.
While the object Date is an abstract thing which does refer to any specific date, and instance of the Date object must refer to some specific date. Its empty slots which specify the actual day, month, year and so forth have all been assigned specific values.
Now that we have presented the basic object foundation upon which JavaScript rests it is time to consider how these concepts are implemented. How does one create objects and instances in JavaScript? In fact we already know part of the answer to this question, as objects are created by defining a very special sort of function.
Let us pursue the home ownership analogy even further and define a house object. The fundamental properties of our house object will be
To define an object to hold this information we write the function shown in Listing 4.1. Note that this function makes use of the extremely important keyword this. This keyword always refers to the current object. In this case it refers to the current object which we are creating.
Listing 4.1 Defining a function to create a house object
function house( rms, stl, yr, garp ) { // define a house object this.rooms = rms; // number of rooms (integer) this.style = stl; // style, e.g. Colonial, Tudor, Ranch (string) this.yearbuilt = yr; // year built, integer this.hasgarage = garp; // has a garage? (boolean) }
There are several things to notice about this object definition. First of all, the name of the function is the name of the object: house. Second, this function does not return anything. When functions were first introduced in Chapter 2 ("Functions and Objects") it might have seemed mysterious how a function could actually do useful work without a return statement, since everything inside a function is local. Using a function to create an object works by modifying this, so that it need not return anything.
This example shows how a house object is defined. It does not create a specific house instance. The house object has four slots to hold the four properties rooms, style, yearbuilt and hasgarage. A specific house instance will fill those slots with actual values. Instances are created using the new statement combined with a function call. The keyword new is required, since it tell JavaScript that we are creating an instance rather than just calling a function. We could create an instance of house, named myhouse, as follows:
var myhouse = new house( 10, "Colonial", 1989, true );
Note that the instance myhouse is treated just like any other variable. It must be declared using var. Now that myhouse has been created we can refer to its properties using the dot . operator. myhouse.rooms will have the value 10, myhouse.style will be the string "Colonial," myhouse.yearbuilt will be 1989, and myhouse.hasgarage will be the boolean value true. The fact that rooms and yearbuilt are integers, style is a string and hasgarage are booleans is only implicit, of course. There is nothing stopping us from creating a house instance in which the hasgarage property has the string value "yes" rather than a boolean value. Care must be taken to avoid this kind of type confusion.
Object properties are typeless, just like all other variables in JavaScript. The new operator will not protect you against inadvertantly assigning an inappropriate value to a property.
Many programming languages support array data types. An array is an indexed collection of items all of which have the same underlying type. In C or Java, for example, we can say int iarr[10]; which defines a collection of ten integers. These integers are referred to as iarr[0] through iarr[9]. These two languages used zero-based indexing, which means that the first element of the array is at location zero and the last element of the array is at one less than the length of the array, which is 9 in this case. Other languages have one-based indexing, in which the elements range from 1 up to the length of the array. This might seem more intuitive, but zero based indexing is actually the more common form.
JavaScript also has arrays which used zero based indexing. In JavaScript, however, arrays and objects are really two views on the same concept. Every object is an array of its property values, and every array is also an object. Our myhouse instance, for example, is an array with the following four elements:
myhouse[0] = 10; // rooms myhouse[1] = "Colonial"; // style myhouse[2] = 1989; // yearbuilt myhouse[3] = true; // hasgarage
There might not seem to be a lot of advantage to referring to objects in this more numeric and less information manner. You have to remember which index corresponds to which property. However, this alternate form of access makes it possible to access the properties sequentially, rather than by name, which is sometimes very useful. If we know that house objects always have four members then we can write the function shown in Listing 4.2 to display the property values.
Listing 4.2 A function which displays the properties of a house
function showhouse( somehouse ) { // display properties of a house instance for( var iter = 0; iter < 4; iter++) { // four properties exactly document.write("<BR>Property " + iter + " is " + somehouse[iter]); } }
If we call this function as showhouse( myhouse ) the four properties of the myhouse instance will be displayed. This function must be called with an instance, not an object. It would be an error to try showhouse( house ). We will revisit this function when we have learned more about methods and the for..in statement, since there are several alternate ways of writing it.
One deficiency of the showhouse function show strike you immediately. It relies on the implicit knowledge that every house instance has exactly four properties. If we were to augment the definition of a house object by adding a property known as taxrate (a floating point quantity describing the current real estate taxation rate on the house), then the showhouse function would need to be modified to increase the loop count in the for statement from 4 to 5. If we neglected to do so then the showhouse function would only print the first four properties, and would never print the taxrate.
An even more disastrous error would occur if we defined the house object to have only three properties, but forgot to drop the loop count to 3 then the reference to somehouse[3] would refer to a nonexistent array member. This type of error is known as an out of bounds error, since it refers to an array element which was not within the boundaries of the array. There is a very simple way to avoid this problem, and to write the showhouse function in a more general manner.
Define all objects with a length property, which gives the number of properties in the object. Make the length property the first property.
Using this tip we can rewrite the definition of the house object to include a length property as the first property, and then generalize the showhouse function to be completely independent of an a priori knowledge of the house object. This code for the new house object and showhouse function is shown in Listing 4.3.
Listing 4.3 A better house object which knows its own length
/* This function creates a house object whose first property, at array index 0, contains the number of properties is the house object. */ function house ( rms, stl, yr, garp ) { this.length = 5; // four informative properties, and length this.rooms = rms; // rooms this.style = stl; // architecture style this.yearbuilt = yr; // year constructed this.hasgarge = garp; // does it have a garage? } /* This function displays a house object using its length property to determine how many other properties to display */ function showhouse( somehouse ) { // display properties of a house instance var nprops = somehouse.length; // number of properties for( var iter = 1; iter < nprops; iter++) { // iterate over all properties except length document.write("<BR>Property " + iter + " is " + somehouse[iter]); } }
This house object function takes four parameters, as before. It sets its length property to this number plus 1, since there are four meaningful properties (rooms, style, yearbuilt and hasgarage) and the length property itself. Each of the meaningful properties have moved up 1, so that if we say myhouse = new house( 10, "Colonial", 1989, true) the array representation of myhouse will become:
myhouse[0] = 5; // total # of properties myhouse[1] = 10; // rooms myhouse[2] = "Colonial"; // style myhouse[3] = 1989; // yearbuilt myhouse[4] = true; // hasgarage
The showhouse function starts by looking at the length property and using that to set the termination condition for the for loop. The hardwired 4 of Listing 4.2 has been replaced by the variable nprops which holds the length of the myhouse array. This version of showhouse only prints the properties of interest; it does not print the length property. This is why the for loop begins at 1 rather than at 0. The property myhouse[0] is the length property.
This use of the length property is a typical example of the true nature of object oriented programming. One of the fundamental ideas in object oriented programming is the idea of encapsulation, which is a windy way of saying keeping related things in the same place. In the previous definitions of house and showhouse, in Listings 4.1 and 4.2, the length of the house object was present in two places. It was implicitly present in the definition of house itself, and it was also present explicitly, as the upper limit in the for loop. The doctrine of encapsulation says that this is bad. The length of an object should only be stored in one place - in the object itself. By the same token it might be argued that the showhouse function should really be part of the house object, too. The "Method Functions" section below will describe how to do this.
Despite the power of this technique it might still seem less than obvious to refer to properties by index rather than by property name. JavaScript provides a third technique, which is a hybrid of the dot . style and the array [] style. Object properties may be referred to not only as indexed array elements but also as named array elements. This type of array is known as an associative array. The set of properties of the myhouse instance could also be listed as
myhouse["length"] = 5; myhouse["rooms"] = 10; myhouse["style"] = "Colonial"; myhouse["yearbuilt"] = 1989; myhouse["hasgarage"] = true;
JavaScript arrays may be accessed by integer index or by property names. Property names are case sensitive. Integer indices are limited by the length of the array. It is a serious error to refer to nonexistent array elements in either way.
There is one final point to be made about the difference between house object and its various instances. Suppose we create another instance of house, named yourhouse, using the following call to new:
yourhouse = new house( 26, "Tudor", 1922, true );
myhouse and yourhouse are both instances of the house object. Both result from filling in the four slots in the house template with four specific pieces of information which define myhouse and yourhouse (as well as the fifth, hidden piece of information, the length). It is possible to dynamically extend an instance by simply tacking on a new property. If you felt the need to also record the fact that your house has 2 toolsheds and a gazebo you can write
yourhouse.sheds = 2; yourhouse.hasgazebo = true;
These two statements add two new properties to the end of the yourhouse array. The sheds (integer) property is yourhouse[5] and the hasgazebo (boolean) property is yourhouse[6]. Dynamic extensions only apply to specific instances. The myhouse instance is not effected, nor is the house object changed in any way. If we execute showhouse( myhouse ) it will print out exactly the same as it did before. If we create a third house named pizza
pizza = new house( 3, "Restaurant", 1993, false );
it will not have either a sheds property or a hasgazebo property. Figure 4.1 illustrates the relationship between the house object and its various instances.
Fig 4.1
Instances inherit their structure from the underlying object, but may also be extended.
Dynamic extensions are completely local to a particular instance. The underlying object and all other instances, past, present and future, are not effected.
There are some situations in which dynamic extensions are absolutely essential, and dramatically simplify programming. For the most part, however, dynamic extensions are should be avoided, as they can be the source of numerous errors. In fact, we have already made one such error in the description above. What would happen if we executed the function showhouse( yourhouse )? Since the length element of the yourhouse instance has not been modified, it will still have the value 5, so that only array elements 1 through 4 (properties "name" through "hasgarage") will be displayed. The two new properties will not be displayed. When we added sheds and hasgazebo we should have also said
yourhouse.length += 2;
to account for the two new properties in this instance. This is precisely the type of error which is easy to make. In general, it would be much better for the house object to always have sheds and hasgazebo properties which are seldom used than to randomly glue them on. In order to see the most efficient way to do this we must wait to reach the "Calling Functions with a Variable Number of Arguments" section of this chapter.
The one common case where dynamic extension is extremely useful is in variable length arrays. Since object properties are just array elements, and since these elements may be referred to using a numerical index, it is easy to write an object creation function which creates an array of arbitrary size and content. The function is Listing 4.4 can be used to define an object which is an array of strings. The number of strings in the array is the first argument, and the initial value for each element is the first argument.
Listing 4.4 A variable length array-of-strings object
function stringarr( howmany, initstr) { // "howmany" strings, initial value "initstr" this.length = howmany; for( var i = 1; i <= howmany; i++ ) { this[i] = initstr; } }
If we call this function as
mystringarr = new stringarr( 100, "spoon" );
it will create an instance with 101 properties. The first, at index 0, will be the all important length property. The next 100, at indices 1 through 100 inclusive, will be initialized to the string "spoon." Presumably at some point in the future these one hundred strings will be set to some other, less uniform values. It is important to initialize all the properties values to something (the empty string "" would do in this case).
If we later find that we need more than 100 strings we do not need to create a new, even longer, stringarr instance. Instead we can dynamically extend the array to include these new strings. It is essential that the length property be updated in this case, as there is no other way of determining how many elements are in the array, short of counting them with a for..in loop (see below). These statements would add three new strings and update the length:
mystringarr[101] = "I'm"; mystringarr[102] = "doing"; mystringarr[103] = "laundry"; mystringarr.length += 3;
Chapter 2 introduced the extremely useful for statement, in a section of the same name. The standard form of the for statement begins with a clause which defines the initial state of the for loop, the condition under which it will terminate, and the way in which it is updated at the end of each iteration. There is also a variant of the for statement which may be used to iterate over the properties of an object. This statement, the for..in statement, has the following form:
for ( varname in objname ) { forbody }
In the for..in statement varname is the name of a variable which will take on the successive property names of the object objname. This form of the for statement also permits the varname to contain a var declaration. Using the for..in statement we can write yet another form of the showhouse function, which does not rely on the presence of a length property. This function is shown in Listing 4.5. This version will actually work on any instance or object, not just on instances of house, so it has been renamed showany.
Listing 4.5 A function which displays the properties of any object
function showany(anyobj) { // display properties of an instance or object for( var iter in anyobj ){ // iterate over all properties document.write("<BR>Property " + iter + " is " + anyobj[iter]); } }
One of the most powerful aspects of object oriented programming in JavaScript is the ability to create objects with functional properties. We have already said that these functional properties are known as methods. Aside from being a convenient organizational principle, there are other distinct advantages to associating functions with objects. We have already seen the special keyword this is used in object creation. It also used in method functions to refer to the current object. To see how this work consider one more variation on the house object and the showhouse function shown in Listing 4.6.
Listing 4.6 The showhouse function as a method of house
/* This function creates a house instance when used with new. */ function house( rms, stl, yr, garp ) { this.length = 5; // four info props and length itself this.rooms = rm; // rooms; prop [1] this.style = stl; // style; prop [2] this.yearbuilt = yr; // year built; prop [3] this.hasgarage = garp; // garage?; prop [4] this.show = mshowhouse; // the showhouse method; prop [5] } /* This function is the show method of the house object */ function mshowhouse() { // note: no arguments! var nprops = this.length; // length of property array, not including show for ( var iter = 1; iter < nprops; iter++) { //iterate document.write("<BR>Property " + iter + " is " + this[iter]); } }
This version of the instance creation function house not only has the usual four pieces of house information (rooms, style, year built and hasgarage), and the length property which gives the number of properties, it also has a final property named show, which is set equal to the function mshowhouse (it has been given a new name to emphasize that it is now a method function). Note that this method is not counted in the length of the property array (although we could have).
The method version of the showhouse function is shown next. It does not have any arguments. Instead, it refers to its enclosing object as this. The usual for loop works as before. Since we have deliberately shortened the length property by one only the properties with indices 1 through 4 are displayed. We have used by a dot . style reference and an array [] style reference with this, which acts just like any normal instance.
Since this function takes no arguments, you might wonder how it is used. The answer is that since the show method is a property just like any other property it may be accessed in the same way. The statements
myhouse.show(); yourhouse.show(); pizza.show(); all work exactly the same as their non-method counterparts showhouse( myhouse ); showhouse( yourhouse ); showhouse( pizza );
This particular method function took no arguments, and was also void; it does not return any value. Method functions may take as many arguments as one wishes, and may also return values. Listing 4.7 shows a very simple method function which takes the current year and an argument, and returns the age of the house as its value. It checks the argument for validity and returns -1 if the current year is actually earlier than the yearbuilt property.
Listing 4.7 A method function for displaying the age of a house
function howold ( curyear ) { // current year passed as arg if ( curyear < this.yearbuilt ) // invalid year: too early return(-1); // no time travel (yet) return( curyear - this.yearbuilt ); // return difference }
This method must be added to the object defining function house in order for it to work, of course. This function would be called by a standard property reference such as
myhouseage = myhouse.howold( 1996 );
This type of function call is no different that a standard function call such as showhouse( myhouse ). The only difference between method functions and other functions is that method functions may use this as an indirect way of naming the object which contains them.
Always make special purpose functions which only operate on instances of an object be methods of that object.
Object properties are typeless quantities. They may be ordinary variables of any implicit type. Our house object contains properties which are implicitly integers, strings and booleans. It also contains functional members (methods). In a very real sense every new object is a new data type, and every instance of that object is a new variable with its object as the underlying, implicit type of that instance. Since JavaScript is typeless does this mean that objects can contain other objects. In a word, yes.
Suppose we create a new object called desc which will hold some common pieces of information about various items. In particular, the desc object will have properties for length, width, height and color, and a method for computing the volume. The definition of this object and its volume method are shown in Listing 4.8.
Listing 4.8 A description object and its volume method
/* The object creation function. The length, width and height properties will be specified in meters. The color will be a string. */ function desc( ln, wd, ht, col) { // describe something this.length = 5; // four properties and length this.length = ln; // length; prop [1] this.width = wd; // width; prop [2] this.height = ht; // height; prop [3] this.color = col; // color; prop [4] this.findvolume = findvolume; // volume computation method } /* The volume computation method. If the ismetric argument is true then the metric volume will be returned; otherwise the volume in cubic feet will be returned */ function findvolume ( ismetric ) { var mylen, mywid, myht; var conv = ( 39.37 / 12.0 ); // conversion from metric to English if ( ismetric == true ) { mylen = this.length; // metric by default mywid = this.width; // ditto myht = this.height; // ditto } else { mylen = this.length * conv; // convert mywid = this.width * conv; myht = this.height * conv; } return( mylen * mywid * myht ); // return volume }
We can now add a desc object as a property of the house object. We could simply add length, width, height and color properties directly to the definition of house, but this would go against another fundamental principle of object oriented programming: object reuse. The desc object is very general. It can be used to describe a house, a car, a boat or a tea cosy. It makes good sense to encapsulate these common properties in the desc object and then reuse that object's definition over and over, by including it with the house, car, boat and teacosy objects. It would be servicable, but wasteful, to repeat the same information in all these object definitions. Listing 4.9 shows the latest version of house object creation function.
Listing 4.9 The house object with a desc subobject
/* This function creates a house instance when used with new. */ function house( rms, stl, yr, garp, desci ) { this.length = 5; // four info props and length itself this.rooms = rm; // rooms; prop [1] this.style = stl; // style; prop [2] this.yearbuilt = yr; // year built; prop [3] this.hasgarage = garp; // garage?; prop [4] this.descr = desci; // description instance; prop [5] this.show = mshowhouse; // the showhouse method; prop [6] this.howold = howold; // the howold method prop [7] }
In order to properly create a house instance we must first create a desc instance, and pass it as the fifth argument to house. It would be an error to pass in a desc object. A house instance, even one with a subobject, must have all its slots filled in; this is what make it an instance. This means that all the slots in the desc property of house must be filled in, as well, so that it, too, must be an instance. Once this has been done, it is then possible to use all the properties and methods of the desc of the house. Listing 4.10 shows code which creates a desc instance, creates a house instance with that description, and then displays the color and volume of the house using the properties and methods of the desc. This type of structure, in which objects and instances can be contained within one another is referred to as an object hierarchy.
Listing 4.10 Creating and using subobjects
/* Create a desc instance and use it to create a house instance */ var mydesc; var myhouse; var mycol, myvol; mydesc = new desc( 20, 18, 15, "beige" ); // fairly big; ugly color myhouse = new house( 10, "Colonial", 1989, true, mydesc ); // mine, though /* Display the color and volume of the house using a reference to the desc properties of myhouse. */ mycol = myhouse.descr.color; // property of property myvol = myhouse.descr.findvolume(true); // submethod document.write("<BR>My house is " + mycol); document.write("<BR>My house occupies " + myvol + " cubic meters");
Once you have become hooked on object oriented programming it often becomes a pervasive aspect of your coding style. Objects begin to show up everywhere. JavaScript has a convenient statement, borrowed from Pascalor performing a set of object manipulations on the same object. Listing 4.10 may have impressed you with the power of its object manipulations. It may have also intimidated you a bit with the amount of typing which is required to get the color of the myhouse instance.
The purpose of the with statement is to permit a number of object references to be made to the same object (or instance) without having to repeat the name of that object. The format of the statement is
with ( objname ) { statements }
objname is the name of an object or instance. Inside the with block any reference to properties of objname occur as if they had been prefixed with objname and the dot . operator. Listing 4.11 shows an expanded version of the second part of Listing 4.10, in which various aspects of myhouse are displayed. The mshowhouse method should now be extended to not only display the properties of its instance, but to also call a similar show method within the desc object (which will also need to be created).
Listing 4.11 Using the with statement as an implicit object reference
/* Display the color and volume of the house using a reference to the desc properties of myhouse. */ var mycol, myvol, myage; with ( myhouse ) { mycol = descr.color; // 1: implicit ref to myhouse.descr.color myvol = descr.findvolume(true); // 2: implicit ref to myhouse.descr.findvolume myage = yearbuilt; // 3: implicit reference to myhouse.yearbuilt document.write("<BR>My house is " + mycol); // 4 document.write("<BR>My house occupies " + myvol + " cubic meters"); // 5 if ( myage > yourhouse.yearbuilt ) { // 6: explicit reference to another instance document.write("<BR>Its newer than yours!"); // 7 } }
Each of the statements labelled 1, 2 and 3 makes an implicit reference to the myhouse object, which was established as the default object to use in the with statement. Note that not every statement within the with block need refer to myhouse. Statements 4, 5 and 7 make absolutely no reference to any house object. Also, statement 6 makes an explicit reference to a different house instance, namely yourhouse.
Statement 6 exposes one of the weaknesses of the with statement. When JavaScript careens through this with block it must decide many times when the implicit myhouse is to be used, and when it is to be skipped. It must examine every reference, in fact. So, for mycol it must decide if you meant the local variable mycol or if there is some property of myhouse named myhouse.mycol. Fortunately, there is an unambiguous choice in every case. There is no mycol property of the house object.
Statement 6 uses an explicit reference to yourhouse. If statement 6 had been written as
if ( myage > yearbuilt ) {
JavaScript would have misinterpreted your intentions as to the meaning of yearbuilt, and would have implicitly translated this statement to
if ( myage > myhouse.yearbuilt ) {
since there is a yearbuilt property of myhouse. This type of error is both common and pernicious. Since JavaScript is an interpreted language, there is no way to see that this inappropriate translation has taken place. There is no compiled output which may be examined. Such errors are very hard to debug. Even though with is very useful, it use should be strictly circumscribed.
With blocks should be as short as possible. Check all statements within the with block to insure that there are no ambiguous references to local variables or to properties of other objects.
Our discussion of the object foundations of JavaScript is almost complete. We have learned that functions are used to define objects and create instances using the new operator. We have also learned that objects, indexed arrays and associative arrays are really all the same. In fact, the unity between all these concepts goes even deeper. JavaScript functions themselves have properties which can be used to fine tune their behavior.
This aspect of JavaScript is still evolving at the time of this writing. However, we can say for certain that all JavaScript functions will have at least these two properties:
The caller property is the name of whoever called the function. The arguments property is an array of all the arguments which are not on the argument list of the function. The caller property permits a function to identify and respond to the environment in which it is called. The arguments allows us to write functions which take a variable number of arguments. The arguments in the function's argument list are mandatory, while those in the arguments property are optional. Listing 4.12 shows a function which takes two mandatory arguments and a potentially unlimited number of option arguments. It returns a string describing its invocation.
Listing 4.12 A function with mandatory and optional arguments
/* Display the color and volume of the house using a reference to the desc properties of myhouse. */ function addem( str1 ) { // one mandatory argument var nopt = addem.arguments.length; // # of optional arguments var sum = 0; // sum of optional arguments var strres; // string result for( var i = 0; i < nopt; i++ ) { // iterate over all optionals sum += addem.arguments[i]; // add them } strres = "Hello " + str1 + ", "+ addem.caller + ", sum is " + sum; return(strres); }
To see how this works, suppose that this function is called from within another function named test1, with the following invocation:
var str = addem( "there", 1, 3, 5, 7 );
What happens? The mandatory argument "there" is assigned to the parameter str1 of the function addem. The remaining arguments are assigned to the variable length array addem.arguments. This has a length property, as do all well behaved arrays, which will have the value 4, since there are four optional arguments. This means that the local variable nopt will be 4. Unlike the examples which we have used, the length property is not at index 0 of the arguments array. The optional arguments begin at addem.arguments[0] and continue up to addem.arguments[3] (four elements total). The for loop in addem adds the optional arguments together, and arrives at 1 + 3 + 5 + 7 = 16, which will be assigned to the local variable sum. Finally, strres is constructed by concatenating various strings, among them the mandatory parameter str1, which is "there" and the addem.caller property, which will have the value test1. The concatenated string is return, and assigned to str; its value will be the string "Hello there, test1, sum is 16."
Notice that the mandatory argument str1 is not part of the optional argument list addem.arguments. Notice also that there need not be any optional arguments. The function call
var str = addem( "on a stick" );
from a function called ofunc would return the value "Hello on a stick, ofunc, sum is 0."
Now that we have covered the foundations of object oriented programming in JavaScript we can begin to look at the actual objects which JavaScript itself provides. These objects can be put into these three categories:
Built-In objects include string objects, the Date object and the Math object. They are referred to as "built-in" because they really do not have anything to do with Web pages, HTML, URLs, the current browser environment, or anything visual. HTML objects, in turn, are directly associated with elements of Web pages. Every link and anchor is a JavaScript object. Every form, and every element within a form, is an HTML object. The hierarchical organization of display elements on a Web page is reflected almost exactly in a hierarchical set of nested HTML objects. We have already had a taste of this hierarchy in the event processing examples of Chapter 3.
Browser objects are at the top of JavaScript's object hierarchy. These objects represent large scale elements of the browser's current environment, and include objects such as window (the current window), history (the list of previously visited pages) and location (the URL of the current page).
The rest of this section will briefly describe the built-in objects of JavaScript. The next two sections will give overviews of the HTML and browser objects. Each of these three categories is quite rich, and Chapters 5 through 7 will provide more in depth information on one of the three categories.
String objects are the most built-in of all the built-in JavaScript objects. You do not even use new when creating a string object. Any variable whose value is a string is actually a string object. Literal strings such as "HelloWorld" are also string objects.
String objects have one property, length, and many methods. The length property gives the length of the string. The methods fall into three categories: methods which manipulate the contents of the string, methods which manipulate the appearance of the string and methods which convert the string into an HTML element.
The following methods may be used on string objects to access, control or modify their content:
The toLowerCase and toUpperCase methods convert the contents of the string entirely to lower case, or to upper case, respectively. So if we define the string variable
var mystr = "Look At This"
then its length property, mystr.length, will have the value 12, since there are twelve characters in the string. In addition we can apply the two case conversion methods and get
mystr.toLowerCase = "look at this" mystr.toUpperCase = "LOOK AT THIS"
These two function do nothing to characters which have no case, so the two spaces in this string are unchanged. We could have also applied the methods directly to the literal form of this string object, so "Look At This".toLowerCase is also equal to "look at this."
The methods charAt and substring are used to extract either a single character from a string, at position idx, or to extract a range of characters, from position fromidx up to but not including position toidx. Character positions are zero based, as are all JavaScript arrays, so that all indices must fall between 0 and one less than the length of the array. Using mystr from above we have
mystr.charAt(5) = "A" mystr.substring(5,7) = "At"
Like the two method functions above these methods both return strings. Care should be take to give these methods valid indices which are actually within the string. The substring method will forgive you if you accidentally specify a toidx which is <= the corresponding fromidx - it will return the emtpy string "" in this case.
Finally, both the indexOf and lastIndexOf methods are used to search for chr with a string. indexOf searchs from the beginning (left side) of the string and lastIndexOf searchs from the end (right side). Both return an integer index if they find the character, and -1 if they do not. Using mystr again, we can search for the character o from both sides:
mystr.indexOf("o") = 1 mystr.lastIndexOf("o") = 2
The first search finds the first o of the word "Look" at position 1 (second character), and the second search finds the second o of "Look" since that is the first o when searching from right to left. Both of these methods also take an optional second argument which specifies an initial index at which to start the search.
The string appearance methods are used to control how a string appears when displayed on a Web page. If you were creating a page with standard HTML tags you would acheive the same effects by using various tags. To make the string "help" appear in italics you would write <I>help </I> for example. The string appearance methods allow you to obtain the same effects in JavaScript without having to use the corresponding HTML elements. These are the string appearance methods:
Most of these methods should be self explanatory, and will be explored further in Chapter 5. The italics method, for example, performs exactly the same function as the I tag in HTML. The only two which take arguments are the fontcolor and fontsize methods. The fontcolor method changes the font color of the string, as if the <FONT COLOR=colr> attribute had been size. Similarly, the fontsize method changes the size of the font used for displaying a string as if the <FONT SIZE=sz> attribute had been given. colr should be a string; sz may be a number or a string. If its a number then this specifies and absolute font size, which if its a string such as "+2" it specifies an increment relative to the current font size. Listing 4.12 shows several examples using the string appearance methods.
Not all HTML style tags have corresponding string appearance methods. You can always directly embed an HTML tag in the string itself if there is no method with the same functionality.
Listing 4.12 String methods can be used to change its style
var bstr = "big"; var sstr = "small"; /* This displays strings with both big and small text. */ document.write("<BR>This is " + bstr.big() + " text"); document.write("<BR>This is " + sstr.small() + "text"); /* The following two strings contain directly embedded HTML tags. They have exactly the same result as the two method calls above */ document.write("<BR>This is <BIG>big</BIG> text"); document.write("<BR>This is <SMALL>small</SMALL> text"); /* If your favorite tag does not have a method, just embed it */ document.write("<BR>This is <STRONG>strong</STRONG> text");
JavaScript provides two string methods for converting strings into Hypertext entities. These should be clearly distinguished from the HTML objects which will be discussed below. These methods are used to create HTML, while the HTML object already are HTML. The two methods in this category are
Both these methods are used to create some form of the anchor <A> HTML attribute. The difference between them is that the anchor method is used to create an anchor with namestr as the value of the NAME attribute, while link is used to create an anchor with the HREF attribute set to hrefstr. Said another way, anchor creates an anchor which is a target while link creates an anchor which is a link. Both methods convert the string on which they operate into the text portion of that anchor. namestr may be any valid string which may be a NAME, so it should not have any embedded whitespace. hrefstr should be a valid URL, since the user is being invited to click on it. Listing 4.13 shows a simple example using these methods which sets up an anchor target and then links to it.
The anchor() string method users the older but more common HTML NAME attribute rather than the newer ID tag.
Listing 4.13 String methods can be used to create HTML anchors and links
var sum4str = "Summary of Chapter 4"; var sum4tar = "Summary4"; /* Create a summary target and a link to it. The following two statements are completely equivalent to this HTML: <A NAME="Summary4">Summary of Chapter 4</A> Click here for a <A HREF="#Summary4">Summary of Chapter 4</A> */ document.write(sum4str.anchor(sum4tar)); document.write("Click here for a " + sum4str.link("#" + sum4tar));
The Math object is used for various forms of mathematical calculations. It contains several properties which are standard constants, such as pi = 3.14159.., as well as a large set of methods which represent common trigonometric and algebraic functions. All Math methods deal with floating point quantities. Angles are expected to be given in radians, not in degrees.
The Math object is our first example of a static object. A static object is one which does not change. All of the slots in the Math object already have values. This makes perfect sense, since you cannot change the value of pi or invent a new meaning for the cos() function (not without creating chaos). The practical consequence of Math being static is that you never use new with Math; you always refer to the Math object directly. Math is the opposite of the String object. The String object has instances but no explicit object; the Math object has only itself, and no instances.
The Math object has the following properties
and these methods
These are all the functions and constants which you will find on any decent calculator. Remember that JavaScript is case sensitive, so you must write Math.PI exactly to get the value of pi. The constants stand for the base of the natural logarithm (Napier's constant, or about 2.71828), the natural log of 10 (about 2.30259), the natural log of 2 (about 0.69315), everyone's favorite pi (about 3.141592653589793), the square root of 1/2 (about 0.7071) and the square root of 2 (about 1.4142).
The methods of the Math object include the common trigonometric functions, including the sine sin, cosine cos, tangent tan and their inverses, the arcsin asin, arccos acos and arctan atan. Each of the trig functions takes an angle in radians and produces a floating number. The values will be between -1 and 1 for the sin and cos methods. Each of the inverse trig functions takes a number, which should be between -1 and 1 for the asin and acos methods, and returns an angle in radians.
The ceil, floor and round methods all take floating point numbers as inputs, and return integers as outputs. The ceil method gives the smallest integer which is greater than or equal to its argument, while floor returns the largest integer which is less than or equal to its argument. The round method gives the nearest integer.
The exp, log, pow and sqrt methods all deal with exponentiation or its inverse. The exp method raises Math.E to the power given by its argument, and is the inverse of the log method, which returns the natural logarithm of its argument, which should be positive. The pow method raises num1, its first argument to the power num2, its second argument. The sqrt returns the square root of its argument. It you inadvertantly give sqrt a negative number it will forgive you and return 0.
Finally, the abs, min, max and random methods perform various useful operations. The abs method returns the absolute value of its argument. min and max give the minumum and maximum value of their two arguments, respectively. The random method takes no arguments. It returns a random floating point number between 0 and 1. For some obscure reason the random method may not be available on all platforms at present. Listing 4.14 presents some simple uses of the Math object.
Listing 4.14 Three useful functions using the Math object
/* Compute the area of a circle given its diameter */ function areaofcir( radius ) { return( Math.PI * radius * radius ); // pi times r squared } /* Given a the coordinates of a point on a circle, determine how far around the circle we must rotate in order to reach that point. Return the angle in radians. */ function angoncircum( x, y ) { var epsilon = 0.00001; // a very small number if ( Math.abs(x) < epsilon ) { // if x is very close to zero if ( y > 0 ) { // positive x axis return(0.0); // 0 degrees } else { // negative x axis return( Math.PI ); // 180 degrees } // end of inner if-else } // end of outer if return( Math.tan( y / x ) ); // division by zero avoided by if test above } /* Given the diagonal size of a television, compute its width assuming that the screen is square */ function tvsize( diag ) { return( diag / Math.SQRT2 ); }
Dealing with dates is one of the most tedious tasks in any language. This is because many humans like to represent dates and times in decidely non-decimal systems. Months come in units of twelve, hours in units of twenty four and minutes and seconds in units of sixty. All these variations are quite illogical from the computer's standpoint. It likes to deal with nice, round numbers, preferably powers of 2 or at least multiples of 10.
The Date object simplifies and automates a lot of the conversion woes associated with going back and forth between a human readable representation such as "Nov 23, 1990" and the internal representation. JavaScript's Date object follows the Unix standard of storing date and time information internally as the number of milliseconds since "The Epoch" of January 1, 1970 (shortly after Unix was first unleashed on an unsuspecting world).
The current version of JavaScript does not permit you to manipulate dates earlier than The Epoch. Attempting to do so will give unexpected and incorrect results.
The Date object has no properties, but many methods. In order to use the Date object you must first understand how to construct instances of it. There are three basic methods of creating a Date instance:
The first form constructs a Date instance which represents the current date and time. This should be accurate to within a second, and will also include information about the your timezone and any corrections to it currently in effect (such as Daylight Savings Time). The second form takes a string of the form "Month Day, Year" such as "November 23, 1990" and converts it to a Date instance. This string may optionally have a time of the form HH:MM:SS at the end, which is used to set the time to HH hours, MM minutes and SS seconds. Hours should be specified using a 24-hour clock, also known as military time, so that 10:15 PM is represented as 22:15:00. The third form takes three integers representing the year, month and day. The year is offset by 1900, so that you write
var ndat = new Date(90, 11, 23);
to create a Date instance named ndat for November 23, 1990. This form may optionally take an additional three integer arguments for the time, so that 1:05 PM on November 23, 1990 is
var ndat2 = new Date(90, 11, 23, 13, 5, 0);
The Date object has a large set of methods for getting and setting the components of a date. These methods are:
Most of these methods perform the obvious operation on their Date instance. nvar.getMonth() returns 10, representing November. It is 10, rather than 11, because months are zero indexed, so that the value of getMonth() is always between 0 and 11, inclusive. The confusingly named getDate, getDay and getTime are worth a slightly closer look. The getDate method returns the day of the month (1 - 31), the getDay method returns the day of the week (0 - 6), and the getTime method returns JavaScript's internal representation of the date, namely the number of milliseconds since The Epoch. This last method might seem to be of dubious utility, but it is useful for comparing two dates to see which is later. The set methods are, of course, used to set the various components of a Date instance. Listing 14.5 shows two simple date manipulation functions.
Listing 4.15 Two useful functions using the Date object
/* Given a date as a string, return the day of the week as an integer between 1 and 7 */ function dayofweek( datestr ) { var dati; dati = new Date( datestr ); // make datestr into a Date instance return( 1 + dati.getDay() ); // get the day of the week and add 1, since } // getDay() returns a number between 0 and 6 /* Compute the number of days to your birthday. Your birthday is specified as the day and month. We will see a better version of this function in Chapter 5. */ function tobday( dayi, moni ) { var today, todayy, todayms; var you, youms; var tdiff; today = new Date(); // today's date todayy = today.getYear(); // current year you = new Date(todayy, moni, dayi); // your birthday this year todayms = today.getTime(); // convert today to ms since The Epoch youms = you.getTime(); // convert your birthday to ms since The Epoch if ( youms < todayms ) { // if your birthday has already passed.. you.setYear(1 + todayy); // look forward to next year youms = you.getTime(); // recompute ms since The Epoch } tdiff = youms - todayms; // number of milliseconds until your next birthday tdiff /= ( 24 * 60 * 60 * 1000 ); // convert to days return( Math.round( tdiff ) ); // round to nearest integer }
In addition to the get and set methods, the Date object also has methods for converting a Date instanceto a string, and two static methods for parsing dates. These methods are
The first three of these methods convert a date instance into a string representing the date and time relative to Greenwich Mean Time (GMT; also called UTC for Universal Coordinated Time), relative to the current Locale conventions, and as just a plain, ordinary string, respectively. The last two methods are used for converting date strings in local time (parse method) or in UTC time (UTC method) into the number of milliseconds since The Epoch. These methods must be referenced as Date.parse() and Date.UTC() since they are static; they may not be used with Date instances. Since they return the internal representation of dates, there values are often simply passed to setTime. These methods will be explored more thoroughly in the last two sections of Chapter 5.
Troubleshooting
I have modified your function tobday in Listing 14.5. My version accepts an arbitrary string as the input birthday. It works perfectly for my birthday, but it fails horribly for my father's birthday. What is wrong? The code looks like:
function tobday2( bdaystr ) { var bdayint = new Date( bdaystr ); ... many lines of code not shown
Since your father was undoubtedly born before January 1, 1970 the very first line attempts to create a Date instance corresponding to a date before The Epoch. This is not currently permitted. Since it seems that you were born after The Epoch the code will work fine for your birthday. Until this restriction is lifted you must convert the year to one after 1970 before you construct a Date instance.
We have now had our first exposure to the built-in String, Math and Date objects. Some of these objects are more built-in that others. While Date acts like an actual object, with the exception of its two static methods, the String object is almost invisible. All normal JavaScript programs will manipulate strings as if they were a separate data type. The essence of a string is part of the JavaScript language.
There is also a small set of functions which are built into JavaScript itself. They are not methods, and are never applied to instance using the dot . operator. They are on the same plane as functions which you create using the function keyword. At present there are five such built-in functions:
The escape and unEscape functions are used to convert to an from the escape code convention used by HTML to represent characters which are not numbers or letters. If you have written any HTML at all then you know that you sometimes need to write %20 to represent a literal space character. The escape built-in function takes a string representing a non-alphanumeric character and returns it escape coding in the form %xx, where xx is a two digit number. Thus, escape(" ") return %20, the code for a space. The unEscape function is the inverse of this. It takes an escape code represented as a string, and returns the character which that code represents, so that unEscape("%20") returns the string " " (with one space).
The parseFloat built-in function attempts to parse its string argument as a floating point number. It only continues parsing the str until it encounters a character which could not possibly be part of a valid floating point number, such as "g". The parseInt built-in function performs a similar operation. It attempts to parse its str argument as an integer in base radix. Thus we would obtain the following values
parseFloat("+3.14williamtell5") = 3.14 parseInt(10111, 2) = 23
Note that everything after the first "w" is ignored, since "w" cannot possibly be part of a float. The second value is obtained because 23 in binary (base 2) notation is 10111.
Finally, the eval function will attempt to evaluate its string argument as an arithmetic expression and return its value. All the normal rules for evaluating expressions, including variable substitution, are performed by the eval function. We will see a lot more of this extremely powerful function. For the moment we will content ourselves with this simple example. If x is a var with the value of 10 then the following two expressions
y = ( x * 14 ) - ( x / 2 ) + 11; z = eval("( x * 14 ) - ( x / 2 ) + 11");
assigns 146 to both y and z.
The JavaScript object model and its very interesting set of built-in objects, methods are function provide what we would expect from any modern programming language. They provide control structures, encapsulation, functions, mathematical operations and so forth. Since JavaScript is design to work with and on the World Wide Web there must also be a linkage between it an the contents of HTML pages.
This linkage is provided by JavaScript's extremely rich set of browser and HTML objects. The browser objects are a reflection of the browser environment, and include objects which can be used to reference the current page, the history list, the current URL. There are also methods for opening new windows, putting up dialog boxes, and writing HTML directly. We have already been leaning heavily on one such method, the write method of the document object.
The browser (or navigator) objects are at the top of JavaScript's object hierarchy, since they represent overall information and actions which are not necessarily associated with a particular Web page. Within a given Web page, however, each HTML element will have a corresponding object, an HTML object, within the object hierarchy. In particular, every HTML form, and every HTML element within every form, will have a corresponding object. Figure 4.2 gives an overview of the JavaScript object hierarchy.
Fig 4.2
JavaScript browser and HTML objects refer to all elementsof a Web page
In this section we will briefly describe the key JavaScript browser and HTML objects, and show how they related to one another. Most of the subsequent chapters in this book will be devoted to in depth discussions of how you can make these objects work for you. Each chapter in section 2, in fact, is devoted to a particular category of JavaScript object (built-in, browser or HTML). The purpose of this section, then, will be to acquaint you with the structure of these objects without going into too much detail on how they are used.
The primary browser objects, in rough order of significance, are as follows:
The window object, as Figure 4.2 shows, is the top object in the JavaScript object hierarchy. Every browser window which is currently open will have a corresponding window object. All the other objects are children of one of the window objects. In particular, every window will be associated with a particular Web page, and the HTML structure of this page will be reflected in the window's document object. Every window corresponds to some URL; that URL will be reflected in the location object. Every window will have a history of the previous pages which have been displayed in that window, which will be represented by the various properties of the history object.
JavaScript maintains an idea of the current window, so that almost all references to subobjects of the current window do not need to refer to it explicitly. This is why all of our output has been done using document.write() rather than window.document.write(). window objects have the following interesting methods (among others):
All these methods are used to manipulate the window state of the browser itself. The alert and confirm methods are used to display their msgstr argument in a dialog box. The alert method is used to alert the user to something about which the user can do nothing. An alert dialog contains a single OK button. The confirm dialog box is more flexible, and displays it message with both an OK and a Cancel button. If the user selects OK then the confirm method returns true, otherwise it returns false. The prompt method is used to solicit user input, in the form of a string. It displays a dialog box with the msgstr and an editable text field. This method also accepts a second optional argument which can be used to set a default value in the input field. This method returns whatever the user typed as a string.
You use the open method of the window object when you wish to open a new browser window. The urlstr argument is a string representing the URL which will be loaded into that window. The wname argument is a string which gives the new window its name. This method returns an instance of the window object representing the new window created. This method also accepts a third argument which can be used to specify a wide variety of display options for the new window (such as whether or not it should display its toolbar). When the close method is invoked from a window instance the underlying window is closed and the URL in it is unloaded.
Every window is associated with a document object. The document object contains properties for every anchor, link and form on that page, as well as all of the subelements of those elements. It also contains properties for its title (the content of the <TITLE> field of the page), its foreground color (the fgColor property), its background color (the bgColor property), its various link colors, and other attributes of the page itself. The document object has the following methods:
The clear method is used to completely erase a document window. The entire contents are wiped out, regardless of how they got there. The clear method is particularly useful if you are constructing a Web page entirely within JavaScript, and want to make sure it is empty before you start. The open and close methods are used to start and stop buffered output. If you call the open method, perform a series of writes and/or writelns, and then call the close method, the results of your write operations will be layed out and will appear on the page. As well will see in the "Scripting Applets and Plug-Ins" section of Chapter 10, the document.open() method is also used to communicate with plug-ins.
Do not confuse the open and close methods of the document object with the window methods of the same names. They perform very different functions, and are not interchangable. Use an explicit reference - document.open() or window.open() - to obtain the appropriate one.
Of course we are intimately familiar with the write method by this time. The write method is used to write any string expression, including one containing embedded HTML, to the current document. Note that the write method actually takes a variable number of arguments, rather than just one. If more than one argument is given, each of the arguments is interpreted as a string and written in turn. The writeln method is identical to the write method, except that it outputs a carriage return after it has completely writing is argument(s). Note that this carriage return will be ignored by HTML, which really does not like embedded whitespace, unless the writeln is inside preformatted text (within <PRE>..</PRE> tags).
The history object is used to refer to the history list of previously visited URLs. The history object has a property known as length, which indicates how many URLs are stored on the history list at present. It also has these three methods:
The go method is used to nagivate the history list. If the where argument is specified as a number then that indicates that the document that number forward or backward in the history list is to be visited. If where is a string representing a URL then that URL is loaded, and becomes the current document. The forward method is identical to go(1), and the back method is the same as go(-1).
The location object describes the URL of a document. It has properties representing the various components of the URL, including its protocol part, its hostname part, its pathname part, and its port number part, among other properties. It also has a toString method which can be used to convert it to a string. If our current document is Netscape's home page then we can use the code shown in Listing 4.16 to display its various components.
Listing 4.16 Accessing the properties of Netscape's home page
var loc = document.location; document.write("<BR>URL is " + loc.toString()); document.write("<BR>Protocol is " + loc.protocol); document.write("<BR>Hostname is " + loc.hostname); document.write("<BR>The top level document is " + loc.pathname);
To understand how HTML objects work in JavaScript let us consider a simple piece of HTML which creates an anchor, a small form, and a link to that anchor. This is not intended to be the HTML for a meaningful Web page, but it will nevertheless illustrate the correspondence between HTML elements and JavaScript HTML objects. Our elementary HTML code is given in Listing 4.17.
Listing 4.17 HTML code for a page with a form, anchor and link
<HTML> <HEAD> <TITLE>A very simple HTML page</TITLE> </HEAD> <BODY> <A NAME="top">This is the top of the page</A> <BR> <FORM METHOD="post" ACTION="mailto:nobody@dev.null"> <P>Enter your name: <INPUT TYPE="text" NAME="me" SIZE=70> </P> <INPUT TYPE="reset" VALUE="Oops"> <INPUT TYPE="submit" VALUE="OK"> </FORM> <HR> Click here to go to the <A HREF="#top">top</A> of the page </BODY> </HTML>
This code create a HTML page with an anchor at the top of the page and a link to it at the bottom. In between is a simple form which allows the user to enter his name. There is a submit button if he gets it right, and a reset button if he doesn't. On success the form's contents are submitted via a post action to the fictitious email address nobody@dev.null.
The important aspect of this example is not its primitive HTML, but the fact that the HTML elements in it are reflected in the JavaScript object hierarchy. We have already seen that we can access the title of this document through the title property of the document object. We can also access the other HTML elements of this document using these properties:
These properties of the document object are arrays representing every HTML element which is an anchor, form or link on the page. In our particular example there is only one of each, so we would refer to the anchor at the top of the page as document.anchors[0], the link at the bottom of the page as document.links[0] and the form in the middle of the page as document.forms[0]. These are the top level HTML objects represented by this document. Each of these elements, in turn, has properties and methods which can be used to describe and manipulate it.
In particular the form object corresponding to forms[0] has subobjects for each of the three form elements (the reset button, the submit button, and the text input field), as well as properties for the submit method and the submit target. forms[0].elements[0] will correspond to the text input field. forms[0].elements[0].name will be the name of that field, as specified by the NAME field, which is "me" in this case. Figure 4.3 recapitulates this HTML code and shows how each element in the page is associated with an HTML object. We will have many more examples of this in subsequent chapters.
Fig 4.3
Anchors, links, forms and form elements are represented as objects in JavaScript.
|