My first attack at this problem was to store the data as global variable data mostly arrays in the parent document that creates the frames.. Table 53-1 Files Comprising the Decision Help
Trang 1Decision Helper
The list of key concepts for this chapter’s application
looks like the grand finale to a fireworks show As
JavaScript implementations go, the application is, in some
respects, over the top, yet not out of the question for
presenting a practical interactive application on a Web site
lacking control over the server
The Application
I wanted to implement a classic application often called a
decision support system My experience with the math
involved here goes back to the first days of Microsoft Excel
More recently, I put the concepts to work for the former
MacUser magazine in an application that assisted Macintosh
shoppers in selecting the right model for their needs Rather
than design a program that had limited appeal (covering only
one possible decision tree), I set out to make a completely
user-customizable decision helper All the user has to do is
enter values into fields on a number of screens; the program
performs the calculations to let the user know how the
various choices rank
Although I won’t be delving too deeply into the math
inside this application, it’s helpful to understand how a user
approaches this program and what the results look like The
basic scenario is a user who is trying to evaluate how well a
selection of choices measure up to his or her expectations of
performance User input includes the following:
✦ The name of the decision
✦ The names of up to five alternatives ( people, products,
ideas, and so on)
✦ The factors or features of concern to the user
✦ The importance of each of the factors to the user
✦ A user ranking of the performance of every alternative
Scripted image mapsScripted charts
✦ ✦ ✦ ✦
Trang 2What makes this kind of application useful is that it forces the user to rate andweigh a number of often-conflicting factors By assigning hard numbers to theseelements, the user leaves the difficult process of figuring out the weights of variousfactors to the computer.
Results come in the form of floating-point numbers ranging from 0 to 100 As anextra touch, I’ve added a graphical charting component to the results display
The Design
With so much user input necessary for this application, conveying the illusion
of simplicity was important Rather than lump all text objects on a single scrollingpage, I decided to break them into five pages, each consisting of its own HTMLdocument As an added benefit, I could embed information from early screens intothe HTML of later screens, rather than having to create all changeable items out oftext objects This “good idea” presented one opportunity and one rather largechallenge
The opportunity was to turn the interface for this application into somethingresembling a multimedia application using multiple frames The largest framewould contain the forms the user fills out as well as the results page Anotherframe would contain a navigation panel with arrows for moving forward andbackward through the sequence of screens, plus buttons for going back to a homepage and getting information about the program I also thought a good idea would
be to add a frame that provides instructions or suggestions for the users at eachstep In the end, the design became a four-frame window, as shown in the firstentry screen in Figure 53-1
Figure 53-1: The Decision Helper window consists of four frames.
Trang 3Using a navigation bar also enables me to demonstrate how to script a
client-side image map — not an obvious task with JavaScript
The challenge of this design was to find a way to maintain data globally as the
user navigates from screen to screen Every time one of the entry pages unloads,
none of its text fields are available to a script My first attack at this problem was to
store the data as global variable data (mostly arrays) in the parent document that
creates the frames Because JavaScript enables you to reference any parent
document’s object, function, or variable ( by preceding the reference with parent), I
thought this task would be a snap Unfortunately, Navigator 2 had a nasty bug that
affects the storage of parent variables that depend on data coming from their
children: if any child document unloads, the data gets jumbled The other hazard
here is that a reload of the frameset could erase the current state of those variables
My next hope was to use the document.cookieof the parent as the storage bin
for the data A major problem I faced was that this program needs to store a total
of 41 individual data points, yet you can allot no more than 20 cookies to a given
URL pathname But the cookie proved to be the primary solution for this
application (although see the “Further Thoughts” section at the end of the chapter
about a noncookie version) For some of the data points (that are related in an
array-like manner), I fashioned my own data structures so that one cookie could
contain up to five related data points That reduced my cookie demands to 17
The Files
Before I get into the code, let me explain the file structure of this application
Table 53-1 gives a rundown of the files used in the Decision Helper
Table 53-1
Files Comprising the Decision Helper Application
File Description
index.htm Framesetting parent document
dhNav.htm Navigation bar document that contains some scripting
dhNav.gif Image displayed in dhNav.htm
dhIcon.htm Document for lower-left corner frame
dhIcon.gif Icon image for lower-left frame
dh1.htm First Decision Helper entry page
dh2.htm Second Decision Helper entry page
dh3.htm Third Decision Helper entry page
dh4.htm Fourth Decision Helper entry page
dh5.htm Results page
chart.gif Tiny image file used to create bar charts in dh5.htm
dhHelp.htm Sample data and instructions document for lower-right frame
dhAbout.htm Document that loads into a second window
Trang 4A great deal of interdependence exists among these files As you see later,assigning the names to some of these files is strategic for the implementation ofthe image map.
The Code
With so many JavaScript-enhanced HTML documents in this application, youcan expect a great deal of code To best grasp what’s going on here, first try tounderstand the structure and interplay of the documents, especially the way theentry pages rely on functions defined in the parent document My goal indescribing this structure is not to teach you how to implement this application,but rather to take the lessons I learned while building this application and applythem to the more complex ideas that may be aching to get out of your head andinto JavaScript
index.htm
Taking a top-down journey through the JavaScript and HTML of the DecisionHelper, start at the document that loads the frames Unlike a typical framesettingdocument, however, this one contains JavaScript code in its Head section — codethat many other documents rely on:
<SCRIPT LANGUAGE=”JavaScript”>
<! start // global variable settings of current dh document number var currTitle = “”
function setTitleVar(titleVal) {
currTitle = “” + titleVal }
function getCookieVal (offset) {
var endstr = document.cookie.indexOf (“;”, offset)
if ((“” + endstr) == “” || endstr == -1) endstr = document.cookie.length return unescape(document.cookie.substring(offset, endstr)) }
function getCookie (name) {
var arg = name + “=”;
Trang 5var alen = arg.length;
var clen = document.cookie.length;
function setCookie (name, value) {
document.cookie = name + “=” + escape (value) + “;”
}
Because this application relies on the document.cookieso heavily, these
functions (slightly modified versions of Bill Dortch’s cookie functions — Chapter
16) are located in the parent document I simplified the cookie writing function
because this application uses default settings for pathname and expiration With
no expiration date, the cookies don’t survive the current Navigator session, which
is perfect for this application:
When this application loads (or a user elects to start a new decision), it’s
important to grab the cookies you need and initialize them to basic values that the
entry screens will use to fill entry fields when the user first visits them All
statements inside the initializeCookies()function call the setCookie()
function, defined in the preceding listing The parameters are the name of each
cookie and the initial value — mostly empty strings Before going on, study the
cookie structure carefully I refer to it often in discussions of other documents in
this application
Trang 6The following functions should look familiar to you They were borrowed eitherwholesale or with minor modification from the data-entry validation section of theSocial Security number database lookup in Chapter 48 I’m glad I wrote these asgeneric functions, making them easy to incorporate into this script Because many
of the entry fields on two screens must be integers ranging from 1 to 100, I broughtthe data validation functions to the parent document rather than duplicating them
in each of the subdocuments:
// JavaScript sees numbers with leading zeros as octal values, so // strip zeros
function stripZeros(inputStr) {
var result = inputStr while (result.substring(0,1) == “0”) { result = result.substring(1,result.length) }
return result }
// general purpose function to see if a suspected numeric input // is a positive integer
function isNumber(inputStr) {
for (var i = 0; i < inputStr.length; i++) { var oneChar = charAt(i)
if (oneChar < “0” || oneChar > “9”) { return false
} } return true }
// function to determine if value is in acceptable range for this // application
function inRange(inputStr) {
num = parseInt(inputStr)
if (num < 1 || num > 100) { return false
} return true }
To control the individual data entry validation functions in the mastercontroller, I again was able to borrow heavily from the application in Chapter 48:
// Master value validator routine function isValid(inputStr) {
if (inputStr != “” ) { inputStr = stripZeros(inputStr)
if (!isNumber(inputStr)) { alert(“Please make sure entries are numbers only.”) return false
} else {
if (!inRange(inputStr)) { alert(“Entries must be numbers between 1 and 100 Try another value.”)
Trang 7return false }
}
}
return true
}
Each of the documents containing entry forms retrieves and stores information
in the cookie Because all cookie functions are located in the parent document, it
simplifies coding in the subordinate documents to have functions in the parent
document acting as interfaces to the primary cookie functions For each category
of data stored as cookies, the parent document has a pair of functions for getting
and setting data The calling statements pass only the data to be stored when
saving information; the interface functions handle the rest, such as storing or
retrieving the cookie with the correct name
In the following pair of functions, the decision name (from the first entry
document) is passed back and forth between the cookie and the calling statement
Not only must the script store the data, but when the user returns to that screen
later for any reason, the entry field must retrieve the previously entered data:
The balance of the storage and retrieval pairs do the same thing for their
specific cookies Some cookies are named according to index values (factor1,
factor2, and so on), so their cookie-accessing functions require parameters for
determining which of the cookies to access, based on the request from the calling
statement Many of the cookie retrieval functions are called to fill in data in tables
of later screens during the user’s trip down the decision path:
Trang 8function setPerformance(i,str) {
setCookie(“perf” + i,str) }
function getPerformance(i) {
return getCookie(“perf” + i) }
One sequence of code that runs when the parent document loads is the one thatlooks to see if a cookie structure is set up If no such structure is set up (theretrieval of a designated cookie returns a null value), the script initializes allcookies via the function described earlier:
if (getDecisionName() == null) {
initializeCookies() }
<FRAMESET COLS=”104,*”>
<FRAMESET ROWS=”250,*”>
<FRAME NAME=”navBar” SRC=”dhNav.htm” SCROLLING=no>
<FRAME NAME=”icon” SRC=”dhIcon.htm” SCROLLING=no>
dhNav.htm
Because of its crucial role in controlling the activity around this program, let’slook into the navigation bar’s document next To accomplish the look and feel of amultimedia program, this document was designed as a client-side image map thathas four regions scripted corresponding to the locations of the four buttons (seeFigure 53-1) One function is connected to each button
The first function is linked to the Home button For the listing here, I justpresent an alert dialog box replicating the action of navigating back to a real Website’s home page:
Trang 9Each of the two arrow navigation buttons brings the user to the next or previous
entry screen in the sequence To facilitate this functionality without building tables
of document titles and names, you call upon the currTitleglobal variable in the
parent document This value contains an integer in the range of 1 through 5,
corresponding to the main content documents, dh1.htm, dh2.htm, and so on As
long as the offset number is no higher than the next-to-last document in the
sequence, the script increments the index value by one and concatenates a new
location for the frame At the same time, the script advances the help document
( lower-right frame) to the anchor corresponding to the chosen entry screen by
setting the location.hashproperty of that frame Similar action navigates to the
previous screen of the sequence This time, the index value is decremented by one,
and a dialog box appears when the current page is already the first in the sequence:
Clicking the Info button displays a smaller window containing typical About-box
data for the program ( Figure 53-2) In an earlier version of this application, the
script made two calls to the window.open()method to work around a Navigator 2
bug for the Mac and UNIX platforms:
Trang 10Figure 53-2: The About Decision Helper
screen appears in a separate window
The Body of the document contains the part that enables you to script a side image map Using tags to define client-side image maps, as I do here, differsfrom the method used in the Netscape technical note in only one regard: thecontent of the HREF=attribute for each <AREA>tag Instead of pointing to anentirely new URL (the prescribed way), your attributes point to the JavaScriptfunctions defined in the Head portion of this document When a user clicks on therectangle specified by an <AREA>tag, the browser invokes the function instead
client-<BODY>
<MAP NAME=”navigation”>
<AREA SHAPE=”RECT” COORDS=”23,22,70,67” HREF=”javascript:goHome()”>
<AREA SHAPE=”RECT” COORDS=”25,80,66,116” HREF=”javascript:goNext()”>
<AREA SHAPE=”RECT” COORDS=”24,125,67,161” HREF=”javascript:goPrev()”>
<AREA SHAPE=”RECT” COORDS=”35,171,61,211” HREF=”javascript:goInfo()”>
<HTML>
<HEAD>
<TITLE>DH1</TITLE>
Trang 11<SCRIPT LANGUAGE=”JavaScript”>
<! start
function loadDecisionName() {
var result = parent.getDecisionName()
result = (result == null) ? “” : result
After the document loads, it performs three tasks (in the onLoad=event
handler) The first task is to set the global variable in the parent to let it know
which number of the five main documents is currently loaded Next, the script
must fill the field with the decision name stored in the cookie This task is
important because users will want to come back to this screen to review what they
entered previously A third statement in the onLoad=event handler sets the focus
of the entire browser window to the one text object This task is especially
important in a multiframe environment such as with this design When a user
clicks on the navigation panel, that frame has the focus To begin typing into the
field, the user has to tab (repeatedly) or click it to bring the focus to the field By
setting the focus in the script when the document loads, you save the user time
<H4>Step 1 of 5: Type the name of the decision you’re making Then
click the “Next” arrow.</H4>
<P><P>
In the text field itself, an onChange=event handler saves the value of the field in
the parent’s cookie for the decision name No special Save button or other
instruction is necessary here because any navigation that the user does via the
navigation bar automatically causes the text field to lose focus and triggers the
onChange=event handler:
The copy of this file on the CD-ROM also has code that allows for plugging in
sample data (as seen on my Web site) and a textarea object that you can use for
debugging cookie data
Trang 12For the second data-entry screen (shown in Figure 53-3), five fields invite the user toenter descriptions of the alternatives under consideration As with the decision namescreen, the scripting for this page must both retrieve and save data in the fields
In one function, the script retrieves the alternative cookies (five total) and stuffsthem into their respective text fields (as long as their values are not null) Thisfunction script uses a forloop to cycle through all five items — something thatmany scripts yet to come in this application frequently do Whenever a cookie isone of a set of five, the parent function has been written (in the following example)
to store or extract a single cookie, based on the index value Text objects holdinglike data (defined in the following listing) are all assigned the same name, so thatJavaScript lets you treat them as array objects — greatly simplifying the placement
of values into those fields inside a forloop
for (var i = 0; i < 5; i++) { var result = parent.getAlternative(i) result = (result == null) ? “” : result document.forms[0].alternative[i].value = result }
} // end >
</SCRIPT>
</HEAD>
Figure 53-3: The second data-entry screen