1108 Part IV ✦ JavaScript Core Language Reference Listing 41-4 (continued) function factorial(n) { if (n > 0) { return n * (factorial(n - 1)) } else { return 1 } } </SCRIPT> </HEAD> <BODY> <FORM> Enter an input value: <INPUT TYPE=”text” NAME=”input” VALUE=0> <P><INPUT TYPE=”button” VALUE=”Calc Factorial” onClick=”this.form.output.value = factorial(this.form.input.value)”> <P>Results: <INPUT TYPE=”text” NAME=”output”> </FORM> </BODY> </HTML> This function is designed to be generalizable, accepting only the input value (n) as a parameter. In the form, the onClick event handler of the button sends only the input value from one of the form’s fields to the factorial() function. The returned value is assigned to the output field of the form. The factorial() function is totally ignorant about forms, fields, or buttons in this document. If I need this func- tion in another script, I can copy and paste it into that script knowing that it has been pretested. Any generalizable function is part of my personal library of scripts — from which I can borrow — and saves me time in future scripting tasks. You cannot always generalize a function. Somewhere along the line in your scripts, you must have references to JavaScript or custom objects. But if you find that you’re frequently writing functions that perform the same kind of actions, see how you can generalize the code and put the results in your library of ready-made functions. And if your audience uses browsers from Navigator 3 onward (and later versions of Internet Explorer 3 onward), consider placing these library functions in an external .js library file. See Chapter 13 for details on this convenient way to share utility functions among many documents. Custom Objects In all the previous chapters of this book, you’ve seen how conveniently the browser document object models organize all the information about the browser window and its document. What may not be obvious from the scripting you’ve done so far is that JavaScript enables you to create your own objects in memory — objects with properties and methods. These objects are not user-interface elements on the page but rather the kinds of objects that may contain data and script func- tions (behaving as methods) whose results the user can see displayed in the browser window. 1109 Chapter 41 ✦ Functions and Custom Objects You actually had a preview of this power in Chapter 37’s discussion about arrays. An array, you recall, is an ordered collection of data. You can create a JavaScript array in which entries are labeled just like properties that you access via the now- familiar dot syntax ( arrayName[index].propertyName). An object typically con- tains different kinds of data. It doesn’t have to be an ordered collection of data — although your scripts can use objects in constructions that strongly resemble arrays. Moreover, you can attach any number of custom functions as methods for that object. You are in total control of the object’s structure, data, and behavior. An example — planetary objects Building on your familiarity with the planetary data array created in Chapter 37, this chapter shows you how convenient it is to use the data when it is constructed in the form of custom objects. The application goal for the extended example in this section is to present a pop-up list of the nine planets of the solar system and dis- play data about the selected planet. From a user-interface perspective (and for more exposure to multiframe environments), the resulting data displays in a sepa- rate frame of a two-frame window. This means your object method builds HTML on the fly and plugs it into the display frame. If you implement this application strictly for IE4+ and NN6, you can apply the same data to reconstruct the displayed table data for each user selection. The example as shown, however, is fully backward- compatible for all scriptable browsers. In this chapter, instead of building arrays to hold the data, you build objects — one object for each planet. The design of your object has five properties and one method. The properties of each planet are: name, diameter, distance from the sun, year length, and day length. To assign more intelligence to these objects, you give each of them the capability to display their data in the lower frame of the window. You can conveniently define one function that knows how to behave with any of these planet objects, rather than having to define nine separate functions. Listing 41-5 shows the source code for the document that creates the frameset for your planetary explorations; Listing 41-6 shows the entire HTML page for the object-oriented planet document, which appears in the top frame. Listing 41-5: Framesetting Document for a Two-Frame Window <HTML> <HEAD> <TITLE>Solar System Viewer</TITLE> <SCRIPT LANGUAGE=”JavaScript”> function blank() { return “<HTML><BODY></BODY></HTML>” } </SCRIPT> </HEAD> <FRAMESET ROWS=”50%,50%” onLoad=”Frame1.doDisplay(Frame1.document.forms[0].planetsList)”> <FRAME NAME=”Frame1” SRC=”lst41-06.htm”> <FRAME NAME=”Frame2” SRC=”javascript:parent.blank()”> </FRAMESET> </HTML> 1110 Part IV ✦ JavaScript Core Language Reference One item to point out in Listing 41-5 is that because the lower frame isn’t filled until the upper frame’s document loads, you need to assign some kind of URL for the SRC attribute of the second frame. Rather than add the extra transaction and file burden of a blank HTML document, here you use the javascript: URL to invoke a function. In this instance, I want the value returned from the function (a blank HTML page) to be reflected into the target frame (no void operator here). This method provides the most efficient way of creating a blank frame in a frameset. Listing 41-6: Object-Oriented Planetary Data Presentation <HTML> <HEAD> <TITLE>Our Solar System</TITLE> <SCRIPT LANGUAGE=”JavaScript”> <! start script // method definition function showPlanet() { var result = “<HTML><BODY><CENTER><TABLE BORDER=2>” result += “<CAPTION ALIGN=TOP>Planetary data for: <B>” + this.name + “</B></CAPTION>” result += “<TR><TD ALIGN=RIGHT>Diameter:</TD><TD>” + this.diameter + “</TD></TR>” result += “<TR><TD ALIGN=RIGHT>Distance from Sun:</TD><TD>” + this.distance + “</TD></TR>” result += “<TR><TD ALIGN=RIGHT>One Orbit Around Sun:</TD><TD>” + this.year + “</TD></TR>” result += “<TR><TD ALIGN=RIGHT>One Revolution (Earth Time):</TD><TD>” + this.day + “</TD></TR>” result += “</TABLE></CENTER></BODY></HTML>” // display results in a second frame of the window parent.Frame2.document.write(result) parent.Frame2.document.close() } // definition of planet object type; // ‘new’ will create a new instance and stuff parameter data into object function planet(name, diameter, distance, year, day) { this.name = name this.diameter = diameter this.distance = distance this.year = year this.day = day this.showPlanet = showPlanet // make showPlanet() function a planet method } // create new planet objects, and store in a series of variables var Mercury = new planet(“Mercury”,”3100 miles”, “36 million miles”, “88 days”, “59 days”) var Venus = new planet(“Venus”, “7700 miles”, “67 million miles”, “225 days”, “244 days”) 1111 Chapter 41 ✦ Functions and Custom Objects var Earth = new planet(“Earth”, “7920 miles”, “93 million miles”, “365.25 days”,”24 hours”) var Mars = new planet(“Mars”, “4200 miles”, “141 million miles”, “687 days”, “24 hours, 24 minutes”) var Jupiter = new planet(“Jupiter”,”88,640 miles”,”483 million miles”, “11.9 years”,”9 hours, 50 minutes”) var Saturn = new planet(“Saturn”, “74,500 miles”,”886 million miles”, “29.5 years”,”10 hours, 39 minutes”) var Uranus = new planet(“Uranus”, “32,000 miles”,”1.782 billion miles”, “84 years”, “23 hours”) var Neptune = new planet(“Neptune”,”31,000 miles”,”2.793 billion miles”, “165 years”,”15 hours, 48 minutes”) var Pluto = new planet(“Pluto”, “1500 miles”, “3.67 billion miles”, “248 years”, “6 days, 7 hours”) // called from push button to invoke planet object method function doDisplay(popup) { i = popup.selectedIndex eval(popup.options[i].text + “.showPlanet()”) } // end script > </SCRIPT> <BODY> <H1>The Daily Planet</H1> <HR> <FORM> <P>Select a planet to view its planetary data: <SELECT NAME=’planetsList’ onChange=’doDisplay(this)’> <OPTION>Mercury <OPTION>Venus <OPTION SELECTED>Earth <OPTION>Mars <OPTION>Jupiter <OPTION>Saturn <OPTION>Uranus <OPTION>Neptune <OPTION>Pluto </SELECT></P> </FORM> </BODY> </HTML> The first task in the Head is to define the function that becomes a method in each of the objects. You must do this task before scripting any other code that adopts the function as its method. Failure to define the function ahead of time results in an error — the function name is not defined. If you compare the data extraction methodology with the function in the array version, notice that the parameter for the index value is gone and the reference to each property begins with this. Later, I return to the custom method after giving you a look at the rest of the Head code. 1112 Part IV ✦ JavaScript Core Language Reference Next comes the object constructor function, which performs several important tasks. For one, everything in this function establishes the structure of your custom object: the properties available for data storage and retrieval and any methods that the object can invoke. The name of the function is the name you use later to create new instances of the object. Therefore, choosing a name that truly reflects the nature of the object is important. And, because you probably want to stuff some data into the function’s properties to get one or more instances of the object loaded and ready for the page’s user, the function definition includes parameters for each of the properties defined in this object definition. Inside the function, you use the this keyword to assign data that comes in as parameters to labeled properties. For this example, I use the same names for both the incoming parameter variables and the properties. That’s primarily for conve- nience (and is very common practice), but you can assign any variable and prop- erty names you want and connect them any way you like. In the planet() constructor function, five property slots are reserved for every instance of the object whether or not any data actually is placed in every property (any unas- signed slot has a value of null). The last entry in the planet() constructor function is a reference to the showPlanet() function defined earlier. Notice that the assignment statement doesn’t refer to the function with its parentheses — just to the function name. When JavaScript sees this assignment statement, it looks back through existing defini- tions (those functions defined ahead of the current location in the script) for a match. If it finds a function (as it does here), JavaScript knows to assign the func- tion to the identifier on the left side of the assignment statement. In doing this task with a function, JavaScript automatically sets up the identifier as a method name for this object. As you do in every JavaScript method you encounter, you must invoke a method by using a reference to the object, a period, and the method name followed by a set of parentheses. You see that syntax in a minute. The next long block of statements creates the individual objects according to the definition established in the planet() constructor. Similar to an array, an assign- ment statement and the keyword new create an object. I assign names that are not only the real names of planets (the Mercury object name is the Mercury planet object) but that also can come in handy later when the doDisplay() function extracts names from the pop-up list in search of a particular object’s data. The act of creating a new object sets aside space in memory (associated with the current document) for this object and its properties. In this script, you create nine object spaces, each with a different set of properties. Notice that no parameter is sent (or expected at the function) that corresponds to the showPlanet() method. Omitting that parameter here is fine because the specification of that method in the object definition means that the script automatically attaches the method to every version (instance) of the planet object that it creates. The last function definition, doDisplay(), is invoked whenever the user makes a choice from the list of planets in the upper frame. This function is also invoked via the frameset’s onLoad event handler so that an initial table is displayed from the default selected item (see Figure 41-1). Invoking the function from the upper frame’s onLoad event handler can cause problems (such as the failure of the other frame) if the frameset is not completely loaded. 1113 Chapter 41 ✦ Functions and Custom Objects Figure 41-1: An external and internal face-lift for an earlier application The onChange event handler in the SELECT list passes that SELECT element’s reference to the doDisplay() function. In that function, the SELECT object is assigned to a variable called popup to help you visualize that the object is the pop- up list. The first statement extracts the index value of the selected item. Using that index value, the script extracts the text. But things get a little tricky because you need to use that text string as a variable name — the name of the planet — and append it to the call to the showPlanet() method. To make the disparate data types come together, use the eval() function. Inside the parentheses, extract the string for the planet name and concatenate a string that completes the reference to the object’s showPlanet() method. The eval() function evaluates that string, which turns it into a valid method call. Therefore, if the user selects Jupiter from the pop-up list, the method call becomes Jupiter.showPlanet(). Now it’s time to look back to the showPlanet() function/method definition at the top of the script. When that method runs in response to a user selection of the planet Jupiter, the method’s only scope is of the Jupiter object. Therefore, all ref- erences to this.propertyName in showPlanet() refer to Jupiter only. The only possibility for this.name in the Jupiter object is the value assigned to the name property for Jupiter. The same goes for the rest of the properties extracted in the function/method. Creating an array of objects In Listing 41-6, each of the planet objects is assigned to a global variable whose name is that of the planet. If the idea of custom objects is new to you, this idea probably doesn’t sound so bad because it’s easy to visualize each variable repre- senting an object. But, as shown in the doDisplay() function, accessing an object by name requires use of the eval() function to convert a string representation to a valid object reference. While it’s not too important in this simple example, the eval() function is not particularly efficient in JavaScript. If you find yourself using 1114 Part IV ✦ JavaScript Core Language Reference an eval() function, look for ways to improve efficiency such that you can reference an object by string. The way to accomplish that streamlining for this application is to place the objects in an array whose index values are the planet names. To assign the custom objects in Listing 41-6 to an array, first create an empty array and then assign the result of each object constructor call to an entry in the array. The modified code section looks like the following (formatted to fit this printed page): // create array var planets = new Array() // populate array with new planet objects planets[“Mercury”] = new planet(“Mercury”,”3100 miles”, “36 million miles”, “88 days”, “59 days”) planets[“Venus”] = new planet(“Venus”, “7700 miles”, “67 million miles”, “225 days”, “244 days”) planets[“Earth”] = new planet(“Earth”, “7920 miles”, “93 million miles”, “365.25 days”,”24 hours”) planets[“Mars”] = new planet(“Mars”, “4200 miles”, “141 million miles”, “687 days”, “24 hours, 24 minutes”) planets[“Jupiter”] = new planet(“Jupiter”,”88,640 miles”,”483 million miles”, “11.9 years”, “9 hours, 50 minutes”) planets[“Saturn”] = new planet(“Saturn”, “74,500 miles”,”886 million miles”, “29.5 years”, “10 hours, 39 minutes”) planets[“Uranus”] = new planet(“Uranus”, “32,000 miles”,”1.782 billion miles”, “84 years”, “23 hours”) planets[“Neptune”] = new planet(“Neptune”,”31,000 miles”,”2.793 billion miles”, “165 years”, “15 hours, 48 minutes”) planets[“Pluto”] = new planet(“Pluto”, “1500 miles”, “3.67 billion miles”, “248 years”, “6 days, 7 hours”) The supreme advantage to this approach comes in a modified doDisplay() function, which can use the string value from the SELECT element directly without any conversion to an object reference: // called from push button to invoke planet object method function doDisplay(popup) { i = popup.selectedIndex planets[popup.options[i].text].showPlanet() } The presence of so many similar objects cries out for their storage as an array. Because the names play a key role in their choice for this application, the named index values work best; in other situations, you may prefer to use numeric indexes to facilitate looping through the array. 1115 Chapter 41 ✦ Functions and Custom Objects Adding a custom method You’re approaching advanced subject matter at this point, so I merely mention and briefly demonstrate an additional power of defining and using custom objects. A custom object can have a reference to another custom object as a property. Let’s extend the planet example to help you understand the implications. Say that you want to beef up the planet page with a photo of each planet. Each photo has a URL for the photo file; each photo also contains other information, such as the copyright notice and a reference number, which displays on the page for the user. One way to handle this additional information is to create a separate object definition for a photo database. Such a definition may look like this: function photo(name, URL, copyright, refNum) { this.name = name this.URL = URL this.copyright = copyright this.refNum = refNum } You then need to create individual photo objects for each picture. One such defi- nition may look like this: mercuryPhoto = new photo(“Planet Mercury”, “/images/merc44.gif”, “(c)1990 NASA”, 28372) Attaching a photo object to a planet object requires modifying the planet con- structor function to accommodate one more property. The new planet constructor looks like this: function planet(name, diameter, distance, year, day, photo) { this.name = name this.diameter = diameter this.distance = distance this.year = year this.day = day this.showPlanet = showPlanet this.photo = photo // add photo property } Once the photo objects are created, you can then create each planet object by passing one more parameter — a photo object you want associated with that object: // create new planet objects, and store in a series of variables Mercury = new planet(“Mercury”,”3100 miles”, “36 million miles”, “88 days”, “59 days”, mercuryPhoto) To access a property of an photo object, your scripts then have to assemble a reference that works its way through the connection with the planet object: copyrightData = Mercury.photo.copyright The potential of custom objects of this type is enormous. For example, you can embed all the copy elements and image URLs for an online catalog in a single docu- ment. As the user selects items to view (or cycles through them in sequence), a 1116 Part IV ✦ JavaScript Core Language Reference new JavaScript-written page displays the information in an instant. This requires only the image to be downloaded — unless the image was precached, as described in the image object discussion in Chapter 18. In this case, everything works instan- taneously — no waiting for page after page of catalog. If, by now, you think you see a resemblance between this object-within-an-object construction and a relational database, give yourself a gold star. Nothing prevents multiple objects from having the same subobject as their properties — like multiple business contacts having the same company object property. More ways to create objects The examples in Listings 41-5 and 41-6 show a way of creating objects that works with all scriptable browsers. If your audience is limited to users with more modern browsers, additional ways of creating custom objects exist. From NN3+ and IE4+, you can use the new Object() constructor to generate a blank object. From that point on, you can define property and method names by simple assignment, as in the following: var Earth = new Object() Earth.diameter = “7920 miles” Earth.distance = “93 million miles” Earth.year = “365.25” Earth.day = “24 hours” Earth.showPlanet = showPlanet // function reference When you create a lot of like-structured objects, the custom object constructor shown in Listing 41-6 is more efficient. But for single objects, the new Object() constructor is more efficient. NN4+ and IE4+ scripters can also benefit from a shortcut literal syntax for creat- ing a new object. You can set pairs of property names and their values inside a set of curly braces, and you can assign the whole construction to a variable that becomes the object name. The following script shows how to organize this kind of object constructor: var Earth = {diameter:”7920 miles”, distance:”93 million miles”, year:”365.25”, day:”24 hours”, showPlanet:showPlanet} Colons link name/value pairs, and commas separate multiple name/value pairs. The value portion of a name/value pair can even be an array (using the [ ] con- structor shortcut) or a nested object (using another pair of curly braces). In fact, you can nest arrays and objects to your heart’s content to create exceedingly com- plex objects. All in all, this is a very compact way to embed data in a page for script manipulation. If your CGI, XML, and database skills are up to the task, consider using a server program to convert XML data into this compact JavaScript version with each XML record being its own JavaScript object. For multiple records, assign the curly-braced object definitions to an array entry. Then your scripts on the client can iterate through the data and generate the HTML to display the data in a variety of forms and sorted according to different criteria (thanks to the JavaScript array- sorting powers). 1117 Chapter 41 ✦ Functions and Custom Objects Object watcher methods NN4+ includes two special functions for objects that were designed primarily for use with external debugging tools: watch() and unwatch(). The watch() method instructs JavaScript to keep an eye on a particular property in an object (any JavaScript-accessible object) and execute a function when the value of the property changes by assignment (that is, not by user interaction). You can see how this works in the simplified example of Listing 41-7. Three but- tons set the value property of a text box. You can turn on the watch() method, which calls a handler and passes the name of the property, the old value, and the new value. An alert in the listing’s function demonstrates what those values contain. Listing 41-7: Object Watching in NN4+ <HTML> <HEAD> <TITLE>Object Watching</TITLE> <SCRIPT LANGUAGE=”JavaScript1.2”> function setIt(msg) { document.forms[0].entry.value = msg } function watchIt(on) { var obj = document.forms[0].entry if (on) { obj.watch(“value”,report) } else { obj.unwatch(“value”) } } function report(id, oldval, newval) { alert(“The field’s “ + id + “ property on its way from \n’” + oldval + “‘\n to \n’” + newval + “‘.”) return newval } </SCRIPT> <BODY> <B>Watching Over You</B> <HR> <FORM> Enter text here: <INPUT TYPE=”text” NAME=”entry” SIZE=50 VALUE=”Default Value”><P> <INPUT TYPE=”button” VALUE=”Set to Phrase I” onClick=”setIt(‘Four score and seven years ago ’)”><BR> <INPUT TYPE=”button” VALUE=”Set to Phrase 2” onClick=”setIt(‘When in the course of human events ’)”><BR> <INPUT TYPE=”reset” onClick=”setIt(‘Default Value’)”><P> <INPUT TYPE=”button” VALUE=”Watch It” onClick=”watchIt(true)”> <INPUT TYPE=”button” VALUE=”Don’t Watch It” onClick=”watchIt(false)”> </FORM> </BODY> </HTML> . important in this simple example, the eval() function is not particularly efficient in JavaScript. If you find yourself using 1114 Part IV ✦ JavaScript Core Language Reference an eval() function, look. NAME=”Frame1” SRC=”lst41-06.htm”> <FRAME NAME=”Frame2” SRC= javascript: parent.blank()”> </FRAMESET> </HTML> 1110 Part IV ✦ JavaScript Core Language Reference One item to point out. user selects items to view (or cycles through them in sequence), a 1116 Part IV ✦ JavaScript Core Language Reference new JavaScript- written page displays the information in an instant. This requires only