JavaScript Application Cookbook by Jerry Bradenbaugh This errata page lists errors outstanding in the most recent printing. If you have any error reports or technical questions, you can send them to booktech@oreilly.com. (Please specify the printing date of your copy.) This page was last modified on April 3, 2000. Here's the key to the markup: [page-number]: serious technical mistake {page-number}: minor technical mistake : important language/formatting problem (page-number): language change or minor formatting problem ?page-number?: reader question or request for clarification Confirmed errors: (47) 1st sentence should read: "To create (instantiate) your own object, you must first declare it somewhere, otherwise JavaScript won't know how to build objects of that type. That is, you must tell JavaScript about what goes into your object by means of a "blueprint." For this purpose, you declare a constructor function that contains the blueprint, like so:" {63} INFO: This fix is fairly large. Replace the entire Potential Extensions section of this chapter with the following text: Making It Cheat Proof One of the first things you might have said to yourself after tinkering with the application for awhile is, "Hey, I can just check out the answers reading the JavaScript source file." It would be a pain to go through each question and find the letter, but it can be done. You can remove the "Peeping Tom" factor by simply not sending the answers with the application and requiring the user to submit his or test results to a server for grading. We won't get into grading the test on the server, but it won't be much more involved than gradeTest(). OK, maybe somewhat, but the principles will be the same. To remove the grading feature and add the server-side submission feature you'll need to do the following: (.) Add a key in the question constructor in questions.js, then remove any data representing the answers from the object and array logic. (.) Add a value for each key to each question in the units array in questions.js. (.) Enable buildQuestion() so it reflects the key with each answer the user provides. (.) Remove gradeTest() and replace the call to it in buildQuestion() with printResults(). (.) Modify printResults() so users can view their answers and embed the answer data within an HTML form to send the waiting server. Adding the Key to The Constructor Once the user's answers make it back to the server, who knows which questions go with which answers? You'll have to add some type of key on the server side that references each question. So, let's start by adding a key (an object property) to the question constructor in questions.js. Just change this: function question(answer, support, question, a, b, c, d) { this.answer = answer; this.support = support; this.question = question; this.a = a; this.b = b; this.c = c; this.d = d; return this; } to: function question(key, question, a, b, c, d) { this.key = key; this.question = question; this.a = a; this.b = b; this.c = c; this.d = d; return this; } Variable key has been added. So has object property this.key. These two will enable you to reference each question after the test results reach the server side. Even though the questions are shuffled, each key stays with its question. Notice that answers, support, this.answers, and this.support have been removed. Since there is no test grading, these aren't necessary. Modifying Each Question In order to match the structure and expectations of the new constructor, we need to change what is passed in with every call to a question in the units array. In other words, pass in a key, and don't pass in the supporting text or an answer. For the key, try to keep it simple. Use 1 for the first, 2, for the second, and so on. Just put each key as the first argument passed to each question call. The first two would look like this. new question(1, "Select the method added in JavaScript 1.2:", "concat()", "link()", "split()", "join()"), new question(2, "Select the method not supported by JavaScript 1.1:", "sort()", "slice()", "split()", "reverse()"), Notice also that the answer (e.g., a, b, etc.) has been removed. So has the supporting text to display after the test grading. Binding the Key to Each Chosen Answer Now that we've added a key for each question, we need to be able to "bind" it to whatever answer the user picks. This happens in function buildQuestion() in administer.html. Just change lines 46-49 from: makeButton("a", units[qIdx].a) + makeButton("b", units[qIdx].b) + makeButton("c", units[qIdx].c) + makeButton("d", units[qIdx].d) + to: makeButton("a", units[qIdx].key + ":" + units[qIdx].a) + makeButton("b", units[qIdx].key + ":" + units[qIdx].b) + makeButton("c", units[qIdx].key + ":" + units[qIdx].c) + makeButton("d", units[qIdx].key + ":" + units[qIdx].d) + This creates a key:answer string that is passed to makeButton(). When the user chooses answer a to the question assigned a key of 1, the value passed will be 1:a. Choosing answer b produces 1:b, and so on. Removing gradeTest() and Modifying buildQuestion() Since the answers and explanations no longer exist, there is no reason to grade the test or display any results. That means you can get rid of function gradeTest(). Just delete lines 74 - 79 in administer.html. This also means that you can get rid of the call to gradeTest() in buildQuestion() at line 43. Actually, you'll want to replace it with a call to printResults() so the user can see his or her answers and the answers can be embedded in an HTML form. Lines 42-45 go from this: if (qIdx == howMany) { gradeTest(); return; } To this: if (qIdx == howMany) { printResults(); return; } Modifying printResults() All that remains is to print an HTML form of hidden fields, each containing a reference to a question and its answer. The user can then submit this form a server-side script. Line 90 in administer.html currently looks like this: '

Here is how you scored:

'; Change it to this: '

Here are your answers:

' + '
'; The next few lines build the HTML form of hidden fields, one question key and answer each. Replace lines 92-105 with the following: var qSplit = keeper[i].split(":"); results += 'You chose ' + qSplit[1] + '


'; As printResults() iterates through the keepers array, it needs to separate the key:answer string created after the user answers each question. The reason: Each hidden form field has a NAME attribute and a VALUE attribute. NAME will be assigned according to the value of the question key, and VALUE will be assigned the user's answer. That sets things up so a server-side script can grade the results. Variable qSplit is set to an array created by the split() method. Each element of keepers is a string delimited by a colon (:). So, qSplit[0] represents the key; qSplit[1] represents the answer. These are assigned appropriately to the NAME and VALUE attributes of each hidden form field. For example, if a user answered d to a question with keys of 1-3, respectively, the corresponding hidden fields would look like this: To close out the form change line 117 to this: results += '
'; In spite of all these changes, printResults() still displays the questions, four choices, and the user's answers. The test isn't graded, however. All the user has to do is choose Submit to send the answers off for a grading. {66} All the copy from Converting to a Survey can now be ignored. [153] Figure 6-2. First timers fill in their name once ...; The figure is missing!