As we left it in the previous chapter, the trivia quiz was simply a single page that asked a single randomly selected question. Our task for the trivia quiz in this chapter is to convert it from a single page application to a multi-frame-based application containing six pages. This is not a small change and will require a lot of work. With the enhancements to the trivia quiz in this chapter, this will transform it to something resembling a proper application. When the application is first loaded, the user will be presented with the screen shown in Figure 7-14.
As you can see, this is quite a change from the way the quiz looked in Chapter 6! We'll next look at the strategy for creating this application.
The idea behind using frames is that there will be a page called globalfunctions.htm to hold all the global functions that we use again and again. This will be loaded into a frame called fraGlobalFunctions.
There will also be a page that simply displays the banner, Online Trivia Quiz, which you can see in Figure 7-14. This page is called menubar.htm and will be loaded into a frame called fraMenuBar.
The third and fourth pages are where all the action takes place as far as the user is concerned. The QuizPage.htm is where the quiz is started—it displays the welcome message and Start Quiz button as can be seen in Figure 7-14. The AskQuestion.htm page is where the questions are displayed and answered, and finally, when the quiz is finished, where the results are listed. These will be loaded into the frame called fraQuizPage.
The two other pages are called TriviaQuiz.htm and TopFrame.htm, whose job is solely to define the framesets for the frames containing the other pages. These frameset pages will be contained in the frames called Top window and fraTopFrame.
The frame structure of the application is shown in Figure 7-15, along with the name of each frame. Note that although the fraGlobalFunctions frame is shown, it's actually invisible to the user, which has the advantage of making our code and the questions more difficult to read.
In terms of a tree diagram, the frames look like those shown in Figure 7-16.
We'll now look at each frame in turn and give the code for the page or pages that will be loaded into it.
Figure 7-17 shows the frame structure, with the top window frame highlighted.
Let's create the frameset page that defines the top and bottom frames that we can see.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Wrox Online Trivia Quiz</title> </head> <frameset rows="120,*" border="0"> <frame src="TopFrame.htm" name="fraTopFrame"> <frame src="QuizPage.htm" name="fraQuizPage"> </frameset> </html>
Save this as TriviaQuiz.htm.
This is the page the user loads into his browser. It defines the frames fraTopFrame and fraQuizPage and specifies the pages that will be loaded into them.
The next frame we're looking at is fraQuizPage, whose position in the frames hierarchy is shown in Figure 7-18. This frame will have two pages loaded into it in turn: QuizPage.htm and AskQuestion.htm.
In the previous screenshot we saw what the trivia quiz looks like before the quiz has started. We simply have a start page with a bit of text and a button to click that starts the quiz. When the quiz is finished, this page is loaded again if the user asks to restart the quiz.
Let's create that start page. When you've finished typing the code, save the page as QuizPage.htm.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Quiz Page</title> <script language=JavaScript type="text/javascript"> function cmdStartQuiz_onclick() { window.top.fraTopFrame.fraGlobalFunctions.resetQuiz(); window.location.href = "AskQuestion.htm"; } </script> </head> <body background="bluewash.jpg"> <h2 align="center"> <font color=coral face="Comic Sans MS" size=6> Welcome to the Wrox Online Trivia Quiz </font> </h2> <p> <font color=darkslateblue face="Comic Sans MS" > <strong> Click the Start Quiz button below to challenge your trivia knowledge. </strong> </font> </p> <p> <form name="frmQuiz"> <input name=cmdStartQuiz type=button value="Start Quiz" onclick="return cmdStartQuiz_onclick()"> </form> </p> </body> </html>
You can see that the background to the page is set to an image file, namely bluewash.jpg. You will need to create this file or retrieve a copy from the code download.
Once the Start Quiz button has been clicked, the next page that is loaded into the fraQuizPage frame is AskQuestion.htm, as shown in Figure 7-19 (the questions are randomly selected, so you may have a different start question).
When the Answer Question button is clicked, the user's answer is checked, and this page is reloaded. If there are more questions to ask, then another, different, randomly selected question is shown. If we've come to the end of the quiz, a results page like that shown in Figure 7-20 is displayed. Actually, the results page and question-asking pages are the same HTML page, but they're created dynamically depending on whether the quiz has ended.
We'll now create that page, which should be saved as AskQuestion.htm.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Ask Questions</title> <script language="JavaScript" type="text/javascript"> var globalFunctions; globalFunctions = window.top.fraTopFrame.fraGlobalFunctions; function getAnswer() { var answer = 0; while (document.QuestionForm.radQuestionChoice[answer].checked != true) { answer++; } return String.fromCharCode(65 + answer); } function buttonCheckQ_onclick() { var questionNumber = globalFunctions.currentQNumber; if (globalFunctions.answerCorrect(questionNumber,getAnswer()) == true) { alert("You got it right"); } else { alert("You got it wrong"); } window.location.reload(); } </script> </head> <body background="bluewashqs.jpg"> <table align=center border="2" width="70%"> <tr> <td bgcolor=RoyalBlue> <form name="QuestionForm"> <script language=JavaScript> document.write(globalFunctions.getQuestion()); </script> </form> </td> </tr> </table> </body> </html>
As you can see, a different background image is needed for this page, namely bluewashqs.jpg. Again, you will need to either create this image or retrieve the one in the code download.
The next frame we're looking at is fraTopFrame whose position in the frames hierarchy is shown in Figure 7-21.
This other frame defined in Topwindow is, in fact, another frameset-defining page. It defines one visible frame, the one containing the page heading, and a second frame, which is not visible and contains our global functions, but no HTML. Let's create that frameset-defining page next.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Top Frame</title> </head> <frameset rows="0,*" border="0"> <frame src="GlobalFunctions.htm" name="fraGlobalFunctions"> <frame src="Menubar.htm" name="fraMenubar"> </frameset> </html>
Save this page as TopFrame.htm.
You can see that it defines the two frames called fraGlobalFunctions and fraMenuBar, which we will look at next.
We'll next create the page for the fraMenubar frame, whose position in the frames hierarchy is shown in Figure 7-22.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <title>Menu Bar</title> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> </head> <body background="multicol_heading.jpg"> <h1 align="center"> <font face="Comic Sans MS" size=+4 color=DarkRed> Online Trivia Quiz </font> </h1> </body> </html>
Save this as menubar.htm.
This page just defines the heading that can be seen throughout the trivia quiz. It also uses a background image called multicol_heading.jpg, which you will need to create or retrieve from the code download.
In Figure 7-23, we can see where fraGlobalFunctions fits into our frames hierarchy.
Now let's turn our attention to the final new page, namely globalfunctions.htm, which serves as a module containing all our general JavaScript functions. It is contained in the frame fraGlobalFunctions. You may recognize a lot of the code from the trivia_quiz.htm page that constituted the trivia quiz we created in Chapter 6.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Global functions</title> <script language="JavaScript" type="text/javascript"> // questions and answers variables will holds questions and answers var questions = new Array(); var answers = new Array(); var questionsAsked; var numberOfQuestionsAsked = 0; var numberOfQuestionsCorrect = 0; var currentQNumber = -1; // define question 1 questions[0] = new Array(); questions[0][0] = "The Beatles were"; questions[0][1] = "A sixties rock group from Liverpool"; questions[0][2] = "Four musically gifted insects"; questions[0][3] = "I don't know - can I have the questions on Baseball please"; // assign answer for question 1 answers[0] = "A"; // define question 2 questions[1] = new Array(); questions[1][0] = "Homer Simpson's favorite food is"; questions[1][1] = "Fresh salad"; questions[1][2] = "Doughnuts"; questions[1][3] = "Bread and water"; questions[1][4] = "Apples"; // assign answer for question 2 answers[1] = "B"; // define question 3 questions[2] = new Array(); questions[2][0] = "Lisa Simpson plays which musical instrument"; questions[2][1] = "Clarinet"; questions[2][2] = "Oboe"; questions[2][3] = "Saxophone"; questions[2][4] = "Tubular Bells"; // assign answer for question 3 answers[2] = "C"; function resetQuiz() { var indexCounter; currentQNumber = -1; questionsAsked = new Array(); for (indexCounter = 0; indexCounter < questions.length;indexCounter++) { questionsAsked[indexCounter] = false; } numberOfQuestionsAsked = 0; numberOfQuestionsCorrect = 0; } function answerCorrect(questionNumber, answer) { // declare a variable to hold return value var correct = false; // if answer provided is same as answer then correct answer is true if (answer == answers[questionNumber]) { numberOfQuestionsCorrect++; correct = true; } // return whether the answer was correct (true or false) return correct; } function getQuestion() { if (questions.length != numberOfQuestionsAsked) { var questionNumber = Math.floor(Math.random() * questions.length) while (questionsAsked[questionNumber] == true) { questionNumber = Math.floor(Math.random() * questions.length); } var questionLength = questions[questionNumber].length; var questionChoice; numberOfQuestionsAsked++; var questionHTML = "<h4>Question " + numberOfQuestionsAsked + "</h4>"; questionHTML = questionHTML + "<p>" + questions[questionNumber][0]; questionHTML = questionHTML + "</p>"; for (questionChoice = 1;questionChoice < questionLength;questionChoice++) { questionHTML = questionHTML + "<input type=radio " questionHTML = questionHTML + "name=radQuestionChoice" if (questionChoice == 1) { questionHTML = questionHTML + " checked"; } questionHTML = questionHTML + ">" + questions[questionNumber][questionChoice]; questionHTML = questionHTML + "<br>" } questionHTML = questionHTML + "<br><input type='button' " questionHTML = questionHTML + " value='Answer Question'"; questionHTML = questionHTML + "name=buttonNextQ "; questionHTML = questionHTML + "onclick='return buttonCheckQ_onclick()'>"; currentQNumber = questionNumber; questionsAsked[questionNumber] = true; } else { var questionHTML = "<h3>Quiz Complete</h3>"; questionHTML = questionHTML + "You got " + numberOfQuestionsCorrect; questionHTML = questionHTML + " questions correct out of " questionHTML = questionHTML + numberOfQuestionsAsked; questionHTML = questionHTML + "<br><br>Your trivia rating is " switch(Math.round(((numberOfQuestionsCorrect / numberOfQuestionsAsked) * 10))) { case 0: case 1: case 2: case 3: questionHTML = questionHTML + "Beyond embarrasing"; break; case 4: case 5: case 6: case 7: questionHTML = questionHTML + "Average"; break; default: questionHTML = questionHTML + "Excellent" } questionHTML = questionHTML + "<br><br><A " questionHTML = questionHTML + "href='quizpage.htm'><strong>" questionHTML = questionHTML + "Start again</strong></A>" } return questionHTML; } </script> </head> <body> </body> </html>
Save this page as GlobalFunctions.htm. That completes all the pages, so now it's time to load in the new trivia quiz and find out how it works.
Load TriviaQuiz.htm into your browser to start the quiz and try it out.
Although there does appear to be a lot of new code, much of it is identical to that in the previous version of the trivia quiz.
We will take a closer look at the pages QuizPage.htm and AskQuestion.htm, which are loaded into the fraQuizPage frame, and GlobalFunctions.htm, which is loaded into the fraGlobalFunction frame.
Of the other pages, TriviaQuiz.htm and TopFrame.htm are simply frameset-defining pages, and menubar.htm simply defines the heading for the page.
This is a simple page. We define a function cmdStartQuiz_onclick(), which is connected to the onclick event handler of the Start Quiz button further down the page.
function cmdStartQuiz_onclick() { window.top.fraTopFrame.fraGlobalFunctions.resetQuiz(); window.location.href = "AskQuestion.htm"; }
In this function, we reset the quiz by calling the resetQuiz() function, which is in our fraGlobalFunctions frame. We'll be looking at this shortly. To get a reference to the window object of fraGlobalFunctions, we need to get a reference to the fraTopFrame that is under the top window.
On the second line of the function, we navigate the frame to AskQuestion.htm, the page where the questions are asked. Let's look at that next.
In this page, we'll access the functions in the fraGlobalFunctions frame a number of times, so we declare a page-level variable and set it to reference the window object of fraGlobalFunctions. It saves on typing and makes our code more readable.
var globalFunctions; globalFunctions = window.top.fraTopFrame.fraGlobalFunctions;
We then come to getAnswer() function, which retrieves from the form lower in the page the option the user chose as her answer. It does this by looping through each option in the form, incrementing the variable answer until it finds the option that has been checked by the user. Remember that the answers are stored as A, B, C, and so on, so we convert the index number to the correct character using the fromCharCode() method of the String object. This is identical to the first half of the buttonCheckQ_onclick() function that we saw in the previous incarnation of the trivia quiz.
function getAnswer() { var answer = 0; while (document.QuestionForm.radQuestionChoice[answer].checked != true) { answer++; } return String.fromCharCode(65 + answer); }
The second function, butCheckQ_onclick(), will be connected to the Check Answer button's onclick event. It is similar to the second half of the function with the same name in the previous version of the quiz. However, now it refers to the answerCorrect() function in the fraGlobalFunctions frame rather than the current page and uses the getAnswer() function rather than the variable answer.
function buttonCheckQ_onclick() { var questionNumber = globalFunctions.currentQNumber; if (globalFunctions.answerCorrect(questionNumber,getAnswer()) == true) { alert("You got it right"); } else { alert("You got it wrong"); } window.location.reload(); }
As in Chapter 6, the form that displays the question to the user is populated dynamically, using document.write(). However, this time the function, getQuestion(), is located in the GlobalFunctions.htm page.
Much of this page is taken from the trivia_quiz.htm page that we created in Chapter 6. At the top of the page, we have added four more page-level variables.
var questions = new Array(); var answers = new Array(); var questionsAsked; var numberOfQuestionsAsked = 0; var numberOfQuestionsCorrect = 0; var currentQNumber = -1;
We then define the questions and answers arrays exactly as we did previously. The first new function, resetQuiz(), is shown here
function resetQuiz() { var indexCounter; currentQNumber = -1; questionsAsked = new Array(); for (indexCounter = 0; indexCounter < questions.length;indexCounter++) { questionsAsked[indexCounter] = false; } numberOfQuestionsAsked = 0; numberOfQuestionsCorrect = 0; }
When the quiz is started or restarted, this function is called to reset all the global quiz variables back to a default state. For example, the questionsAsked variable is reinitialized to a new array, the length of which will match the length of the questions array, with each element being set to a default value of false indicating that the corresponding question has not yet been asked.
We then have the answerCorrect() function, which is the same as in the previous chapter. The rest of the page is made up of the getQuestion() function, which has undergone major changes since the previous version.
Previously, we asked questions randomly and kept going until the user got bored. Now we're going to keep track of which questions have been asked and how many questions have been asked. The questionsAsked array will store which questions have already been asked, so we can avoid repeating questions. The variable numberOfQuestionsAsked keeps track of how many have been asked so far, so we can stop when we've used up our question database. The variable numberOfQuestionsCorrect will be used to record the number of right answers given. These variables were defined in the head of the page.
Turning to getQuestion(), we can see that the very first thing the function does is use an if statement to see if we have asked as many questions as there are questions in the database. The length property of our questions array tells us how many elements there are in our array, and numberOfQuestionsAsked tells us how many have been asked so far. If we have asked all the questions, then later on we'll see that the function writes out an end page with details of how many the user got correct and rates her trivia knowledge.
function getQuestion() { if (questions.length != numberOfQuestionsAsked) { var questionNumber = Math.floor(Math.random() * questions.length) while (questionsAsked[questionNumber] == true) { questionNumber = Math.floor(Math.random() * questions.length); }
You can see from the preceding code that the selection of the question is random, as it was in Chapter 6's version of the quiz. However, we have added a while loop that makes use of the questionsAsked array we declared earlier. Each time a question is asked, we set the value of the element in the questionsAsked array at the same position as its question number to true. By checking to see if a particular array position is true we can tell if the question has already been asked, in which case the while loop keeps going until it hits a false value, that is, an unasked question.
Now that we know which question we want to ask, we just need to go ahead and ask it, which is the purpose of the next lines of code.
var questionLength = questions[questionNumber].length; var questionChoice; numberOfQuestionsAsked++; var questionHTML = "<h4>Question " + numberOfQuestionsAsked + "</h4>"; questionHTML = questionHTML + "<p>" + questions[questionNumber][0]; questionHTML = questionHTML + "</p>"; for (questionChoice = 1;questionChoice < questionLength;questionChoice++) { questionHTML = questionHTML + "<input type=radio " questionHTML = questionHTML + "name=radQuestionChoice" if (questionChoice == 1) { questionHTML = questionHTML + " checked"; } questionHTML = questionHTML + ">" + questions[questionNumber][questionChoice]; questionHTML = questionHTML + "<br>" } questionHTML = questionHTML + "<br><input type='button' " questionHTML = questionHTML + " value='Answer Question'"; questionHTML = questionHTML + "name=buttonNextQ "; questionHTML = questionHTML + "onclick='return buttonCheckQ_onclick()'>";
This code is almost identical to its previous form in Chapter 6, except that we now create the button dynamically as well as the answer options. Why? At the beginning of the function we saw that an if statement checked whether we had reached the end of the quiz. If not, we created another question as we are doing here. If the quiz has come to an end, we don't want to create an array of answers to answer the question. Instead we want to create an end of quiz form. The only way to avoid having the Answer Question button there is to make it part of the dynamic question creation.
Finally, we see that the questionsAsked array is updated, that is, the question just asked is stored in the array as follows:
currentQNumber = questionNumber; questionsAsked[questionNumber] = true; }
The else part of the if statement from the top of the function is shown next. Its purpose is to create the quiz completed message. We build up the HTML necessary, storing it in the questionHTML variable. We not only specify how many questions the user got right out of how many were asked, but we also rate his knowledge.
else { questionHTML = "<h3>Quiz Complete</h3>"; questionHTML = questionHTML + "You got " + numberOfQuestionsCorrect; questionHTML = questionHTML + " questions correct out of " questionHTML = questionHTML + numberOfQuestionsAsked; questionHTML = questionHTML + "<br><br>Your trivia rating is "
The rating is done using the switch statement, where it's based on questions answered correctly divided by the number of questions asked, which for simplicity we multiply by ten and round to the nearest integer. Then we use the case statements to create the correct rating. Remember that code execution starts at the first case statement that matches and continues until either the end of the switch statement or when a break statement is reached. So if our rating calculation
Math.round(((numberOfQuestionsCorrect / numberOfQuestionsAsked) * 10
were 1, the code would start executing from the case 1: statement and continue until the break statement in case 3. Essentially this means that a rating of 0–3 will be described as "Beyond embarrassing", 4–7 as "Average", and anything else, that is, the default case, will be "Excellent".
switch(Math.round(((numberOfQuestionsCorrect / numberOfQuestionsAsked) * 10))) { case 0: case 1: case 2: case 3: questionHTML = questionHTML + "Beyond embarrasing"; break; case 4: case 5: case 6: case 7: questionHTML = questionHTML + "Average"; break; default: questionHTML = questionHTML + "Excellent" }
Finally, we add a link to allow the user to restart the quiz.
questionHTML = questionHTML + "<br><br><A " questionHTML = questionHTML + "href='quizpage.htm'><strong>" questionHTML = questionHTML + "Start again</strong></A>" }
At the end of the function, we return the HTML to be written into the page; either a new question or the end of quiz results.
return questionHTML; }
That completes the discussion of the trivia quiz for this chapter. In the next chapter we'll use advanced string manipulation to pose questions requiring a text, rather than option-based, answer.