You are on page 1of 24

Create a Quiz Application Using AS3 Classes

Download Source Files


http://theflashconnection.com/book/export/html/23

1. The Initial Concept


This article will demonstrate how to build a simple Flash quiz application using Classes. Each step, from concept to finished product, will be explained in detail. The finished application will run the user through a series of multiple choice questions. It will provide forward and back buttons to navigate to the previous or next question. When all of the questions have been answered, the application will compute the user's score. To begin, here's a description of the concept. The application will consist of a fla file, a Document Class called QuizApp, and another class called QuizQuestion. And that's it! Two classes and a fla file! Each file will have it's own responsibilities, which we will define up front: Quiz.fla: The fla file is not going to be responsible for very much at all. It will contain no code, and will serve simply as a container for the Document Class, and also its library will contain the components we will need. Since it's a multiple choice quiz, the RadioButton component will make an excellent choice for displaying the possible answers to each question. Also, rather than making customized buttons, we'll just use instances of the Button component. QuizApp.as, which we will make the Document Class of the fla file, will be responsible for posing the questions one at a time, by creating instances of QuizQuestion (and storing them in an Array). It will also create and position the previous, next, and finish buttons, and keep track of the current question. At the end it will compute and display the score. There will only be one instance of QuizApp. It will be the main application and perform most of the work. QuizQuestion.as, of which there will be many instances, conceptually will represent one question and series of possible answers. Each instance of QuizQuestion will store the question as a string, the multiple possible answers as an array of strings, the correct answer to each question as an integer, and the answer the user supplied as an integer. I'll show you how to build the QuizQuestion class, and how to make instances of it store all this information. I hope you'll follow along and actually build this application. Or at least read the article and build the application using copy and paste. If you do, you'll learn many new things about classes and using objects made from those classes. The application is fairly simple (on purpose), but I think it does a good job of illustrating many important concepts. As always, I hope the result will be that you are inspired to take this knowledge and use it to build applications of your own. In these tutorials, I always strive to make sure that the end result is the creation of something that's actually useful. This Quiz application will be functional, but it's really just a starting point,

as it (admittedly) could really use quite a bit more polish. Mostly, it's a teaching tool to further demonstrate classes and objects, and how they might be used to build a simple application. The idea is that even if you don't care to build a Quiz application, you can still use the concepts presented here as a springboard for an idea of your own. Here's the application that we'll build:

2. Create the files and the file structure


The first step is to create the files and save them to a folder. Since it's probably unlikely that you'll ever want to use the QuizQuestion or QuizApp classes again in any other application, we won't worry about saving them into packages. We'll just save everything into the same folder. Open flash, and create a new Actionscript file. We're going to first create QuizApp.as, which will be our Document Class. So type these lines into the script pane:
package { import flash.display.Sprite; public class QuizApp extends Sprite { public function QuizApp() { trace("this is the document class"); } } }

Go to File, Save. You can use this dialog box to also create your folder. I'll assume that you know how to browse your system to get to the place where you keep your flash files. Create a new folder there to save this file into (I named my folder "Quiz"), then save it there as QuizApp.as. Notice that every class file must include the package keyword. In this case, we're not saving into a package, so the package keyword appears without anything following it. This is called using the unnamed package. Our class files will still be able to find each other without importing, because we're saving them all into the same folder. Flash's classpath always includes a reference to the current folder. Any class file that you want to use as a Document Class must extend either MovieClip or Sprite. The difference is whether you intend to use the main timeline or not. If so, use MovieClip, and if not, use Sprite. In this case, we will just choose to extend Sprite, because everything we create will be generated with Actionscript. We won't be physically placing any objects on the timeline at authoring time. Next, create a new flash file. Immediately save it to the same folder where you saved QuizApp.as. I named mine "Quiz.fla." We're not done working with this file yet, but we need to save it right away, so that when we designate the Document Class, flash will find our QuizApp class file. So make sure you save the file before proceeding.

Next, here's how to designate the Document Class: Click somewhere on or around the stage, so that the Properties Panel gives you the properties of the Document. It should say "Document" in the upper left corner of the Properties Panel. Look for the field labelled "Document Class," and type "QuizApp" into that field (minus the quotes, of course, and make sure you don't include the .as file extension):

Press the Enter key. You shouldn't get any dialog box warning you that the class file couldn't be found. You would have gotten such a warning if you had not already saved this fla file somewhere. If you do get the warning dialog, something is wrong, and you should check to make sure you really saved both files to the same folder. Also make sure that all spelling and capitalization is correct everywhere, as flash is case sensitive. Press CTRL-Enter to test the movie. You should get this message in the output window: "this is the document class." This means our Document Class is working. It's being automatically instantiated when the fla is compiled. It's also being added to the stage with an automatic "addChild" command that happens behind the scenes. This isn't apparent at the moment, because it doesn't contain any graphical content yet. But if it did, you'd see it appear. So, anything we add to this Document Class with addChild will also appear on the stage of our movie. There's a little more we have to do with our fla file before we leave it. Go to the Components panel (if it's not around anywhere, go to Window, Components, or just press CTRL-F7). Drag the Button component out to the stage area, then press the Delete key. Next, drag the RadioButton component out to the stage, and again press the Delete key. This has the effect of adding these items to your fla file's library. The idea here is that we can create instances of these components with Actionscript code, but not unless their master templates are also in our library, as they contain all the graphics. Now the fla file is done. All of our coding is going to be done inside the two class files. Let's create the other class file, QuizQuestion: Go to File, New, and create a new Actionscript file. Type the following code into the script pane:
package { import flash.display.Sprite; public class QuizQuestion extends Sprite { public function QuizQuestion() {

} } }

Save the file as QuizQuestion.as, in the same folder where you've saved the other two files. Now all the files are created, and they all reside in the same folder. Our Document Class is working, and we can now proceed to flesh out our class files some more.

3. The QuizQuestion Class


At this point, all three files have been created and saved to the same folder. You should also have all three files open in Flash, on separate tabs. Let's work on QuizQuestion.as first: QuizQuestion will need to have many properties. Instances of QuizQuestion will have a TextField to display the question, RadioButtons that lay out the possible answers, an integer variable to store the correct answer to the question, another integer variable to store the user's answer to the question, plus several others. Once again, here's the file as we last saved it:
package { import flash.display.Sprite; public class QuizQuestion extends Sprite { public function QuizQuestion() { } } }

This file extends the Sprite class, so the Sprite class has to be imported. In case you haven't tried my earlier tutorials, I pointed out in those that whenever you add to your class files, you'll start to realize when you've added something that requires another import statement. Let's add a "question" property, a String that will store the text of the question. Next, a TextField property that will be used to display the question. Also, since we don't know the length of the question in advance, we won't know how wide to make the TextField to accomodate the length of the question. This is solved for us by setting the TextField's autoSize property. There just happens to be a TextFieldAutoSize class that contains the auto sizing constants we need, so we'll import that too:
package { import flash.display.Sprite; import flash.text.TextField; import flash.text.TextFieldAutoSize; public class QuizQuestion extends Sprite { private var question:String; private var questionField:TextField; public function QuizQuestion() {

} } }

Next, we need to make provision for somehow getting the required infomation into our class. We're going to need to display a question, display the list of possible answers, and also somehow store what we know to be the correct answer. All of this information will come into this class in the constructor's parameter list. I'm now going to tell you about a new feature in AS3. It's called the ...rest parameter, and you may have heard of it and wondered how it works. Basically, it's a parameter that can store any number of arguments in itself. For this reason, if you use ...rest, you need to make sure it's the last parameter in the list, because it's a catch-all for all the arguments that are too numerous for the parameter list. It saves all the extra arguments as an array, and the array's name is whatever name you supply in place of the word "rest." In our case, we're making a multiple choice quiz, and using the ...rest parameter as the last parameter will allow us flexibility in the number of possible choices that can be supplied with each question. In other words, we can make multiple choice questions that can have three possible answers, four possible answers, five--or however many we want, actually. Within reason, of course, because we want them to all fit on the screen without the need for scrolling. We'll use the name "answers" in place of the word "rest." I've also gone ahead and created some private variables to store the values that are passed in to the parameter list, so that they can be stored for future reference. Without doing this, the constructor would run, and anything you might want to do with this parameter list had better be done in the constructor, because their values are lost after the constructor function ends.
package { import flash.display.Sprite; import flash.text.TextField; import flash.text.TextFieldAutoSize; public class QuizQuestion extends Sprite { private var question:String; private var questionField:TextField; private var theCorrectAnswer:int; private var choices:Array; public function QuizQuestion(theQuestion:String, theAnswer:int, ...answers) { //store the supplied arguments in the private variables: question = theQuestion; theCorrectAnswer = theAnswer; choices = answers; } } }

Anyway, what will be supplied to the parameter list is first the question, then the correct answer, and finally, any number of possible multiple choice answers. All the arguments from the third one on will be caught and stored in the ...answers parameter, which is really an array. Inside the constructor, the value of each parameter is assigned to a class member (variable), including the ...answers one. There are a few more things remaining to accomplish in the constructor. The next thing is to create the TextField and position it. I've called the TextField that holds the question "questionField." "questionField" is instantiated as a new TextField, then given some positioning values. At this point, consider that it would be nice to have some positioning variables at the top of the code, in the variable list. There would be variables for the x and the y of the question, variables for the x and y of the first answer, and one other variable representing how far apart to vertically space the answers. These variables are named questionX, questionY, answerX, answerY, and spacing, all of them integers. If it ever becomes necessary to change the layout, this will give one handy place to do it. Of course, there's nothing wrong with temporarily physically laying out some things on flash's stage (in the fla file) to help you come up with these numbers. With all these additions, here's the code so far:
package { import flash.display.Sprite; import flash.text.TextField; import flash.text.TextFieldAutoSize; public class QuizQuestion extends Sprite { private var question:String; private var questionField:TextField; private var theCorrectAnswer:int; private var choices:Array; //variables private var private var private var private var private var for positioning: questionX:int = 25; questionY:int = 25; answerX:int = 60; answerY:int = 55; spacing:int = 25;

public function QuizQuestion(theQuestion:String, theAnswer:int, ...answers) { //store the supplied arguments in the private variables: question = theQuestion; theCorrectAnswer = theAnswer; choices = answers; //create and position the textfield (question): questionField = new TextField(); questionField.text = question; questionField.autoSize = TextFieldAutoSize.LEFT; questionField.x = questionX; questionField.y = questionY; addChild(questionField); }

} }

The next thing to accomplish, still in the constructor, is to create and position the radio buttons. Also, there needs to be a RadioButtonGroup, so that each radio button can be assigned to the same group. This requires importing both the RadioButton class and the RadioButtonGroup class. We'll be adding an event listener to the RadioButtonGroup instance. This is the Event.CHANGE event. This requires importing the Event class. Note the three additions to the import block, and the additional code in the constructor:
package { import import import import import import flash.display.Sprite; flash.text.TextField; flash.text.TextFieldAutoSize; flash.events.Event; fl.controls.RadioButton; fl.controls.RadioButtonGroup;

public class QuizQuestion extends Sprite { private var question:String; private var questionField:TextField; private var theCorrectAnswer:int; private var choices:Array; //variables private var private var private var private var private var for positioning: questionX:int = 25; questionY:int = 25; answerX:int = 60; answerY:int = 55; spacing:int = 25;

public function QuizQuestion(theQuestion:String, theAnswer:int, ...answers) { //store the supplied arguments in the private variables: question = theQuestion; theCorrectAnswer = theAnswer; choices = answers; //create and position the textfield (question): questionField = new TextField(); questionField.text = question; questionField.autoSize = TextFieldAutoSize.LEFT; questionField.x = questionX; questionField.y = questionY; addChild(questionField); //create and position the radio buttons (answers): var myGroup:RadioButtonGroup = new RadioButtonGroup("group1"); myGroup.addEventListener(Event.CHANGE, changeHandler); for(var i:int = 0; i < choices.length; i++) { var rb:RadioButton = new RadioButton(); rb.textField.autoSize = TextFieldAutoSize.LEFT; rb.label = choices[i]; rb.group = myGroup; rb.value = i + 1; rb.x = answerX; rb.y = answerY + (i * spacing);

addChild(rb); } } } }

The help files are really indispensible when you come to this point. You have to look up the RadioButton and RadioButtonGroup classes, and read up on whatever properties and methods of these classes you think you might need to use to accomplish your goal. First, we create a RadioButtonGroup instance, and its constructor requires that we pass it a string of text to use for its name property. We won't be using this name, but we must supply one nonetheless. In the help files, the example there uses the name "group1," so I just kept that. The important name is the variable name, "myGroup," and that's the one we add the Event listener to, in order to listen for the CHANGE event. Remember, what we're constructing is a question and set of possible answers. Although there will be many of these instances made, when we write the class, it can be helpful to think of it in terms of just one instance, at least temporarily. That's why we need only construct one radio button group. Next, we set up a for loop, and loop through the "choices" array. For each choice, we create a new radio button. The "rb" variable gets reused each time through the loop. This is just a temporary name, in other words, and each time through the loop it refers to a new radio button. Each radio button has its textField property autoSized to the left. This is similar to what we did with the questionField textfield; it allows for expansion to the right. Since "choices" is an array of strings, we can use choices[i] to set the label property of the radio button to the appropriate string of text. Each radio button is assigned to the same radio button group ("myGroup"). Each radio button has its "value" property set to the loop counter plus one. So in a four answer multiple choice question, each possible answer will have a value from 1 through 4, respectively. This value will be important later on. When the user clicks on a radio button, the event listener for the radio button group will be triggered, and this value property is what will be returned in the radio button group's "selectedData" property. Again, you don't find this stuff out without going through the help files quite a bit, and experimenting. Each radio button is then positioned using the positioning variables created earlier (with a little help from the loop counter as a multiplier), and then added to the display list for this Sprite using addChild(). None of this stuff will show up on flash's stage, yet, though, until some external code does an addChild() of its own on the whole thing. Later on, QuizApp will do exactly that. Next, we'll want to write the function "changeHandler," which will run whenever the user clicks on one of the radio buttons. When this happens, Flash broadcasts an Event.CHANGE event, and our changeHandler function is called. Inside this function is where we want to set a private variable to the value of the button that was clicked on. We haven't created such a variable yet, so at the top of the code, add the variable "theUserAnswer" to the variables list and give it an integer data type. Inside the changeHandler function, "event.target" will refer to the radio button group as a whole, and the radio button group's "selectedData" property will hold the "value" of the button that was clicked on.

The final thing to put in the code is a couple of getter functions. One of these will be used by external code to retrieve the correct answer to the question. The other will retrieve the answer that the user supplied. This will be important later on for calculating the score. Here's the final code for QuizQuestion, with these two getter functions added at the end:
package { import import import import import import flash.display.Sprite; flash.text.TextField; flash.text.TextFieldAutoSize; flash.events.Event; fl.controls.RadioButton; fl.controls.RadioButtonGroup;

public class QuizQuestion extends Sprite { private var question:String; private var questionField:TextField; private var choices:Array; private var theCorrectAnswer:int; private var theUserAnswer:int; //variables private var private var private var private var private var for positioning: questionX:int = 25; questionY:int = 25; answerX:int = 60; answerY:int = 55; spacing:int = 25;

public function QuizQuestion(theQuestion:String, theAnswer:int, ...answers) { //store the supplied arguments in the private variables: question = theQuestion; theCorrectAnswer = theAnswer; choices = answers; //create and position the textfield (question): questionField = new TextField(); questionField.text = question; questionField.autoSize = TextFieldAutoSize.LEFT; questionField.x = questionX; questionField.y = questionY; addChild(questionField); //create and position the radio buttons (answers): var myGroup:RadioButtonGroup = new RadioButtonGroup("group1"); myGroup.addEventListener(Event.CHANGE, changeHandler); for(var i:int = 0; i < choices.length; i++) { var rb:RadioButton = new RadioButton(); rb.textField.autoSize = TextFieldAutoSize.LEFT; rb.label = choices[i]; rb.group = myGroup; rb.value = i + 1; rb.x = answerX; rb.y = answerY + (i * spacing); addChild(rb); } } private function changeHandler(event:Event) {

theUserAnswer = event.target.selectedData; } public function get correctAnswer():int { return theCorrectAnswer; } public function get userAnswer():int { return theUserAnswer; } } }

4. A short break...testing out QuizQuestion


Before we turn our attention to the QuizApp class, let's test the QuizQuestion class. Let's go to the fla file and write some code on the first frame to make an instance of QuizQuestion and display it. Here's how:
var quizQuestion:QuizQuestion = new QuizQuestion( "Who is buried in Grant's Tomb?", 3, "Lee", "Washington", "Grant", "Lincoln"); addChild(quizQuestion);

Before you run this, though, temporarily take out the reference to the Document Class in the property inspector, otherwise you'll get an error. This is because our Document Class extends Sprite, and if a Sprite is your Document Class, you're not allowed to have any code on your timeline. You might not have realized that you're allowed to write code on multiple lines like this. Writing it this way is not necessary, it's just that to me it makes it easier to read. The question is on the first line, the correct answer is on the second line, and the possible answers are on the lines following. Naturally, if you supply a correct answer of "3," you'll want to make sure that the answer you intend to be the correct one is the third one listed, so that it corresponds. Press CTRL-Enter to test the movie. You should get a nicely laid out arrangement of the question and possible answers, complete with clickable radio buttons. Cool, eh? The QuizQuestion class is completely self-contained, and is a good example of encapsulation. After you build it, you really need never visit the code again or worry about how it works internally. Just give it a question, a number representing the correct answer, and a list of possible answers. It takes care of all the other details of laying it all out on the screen, and storing the user's answer whenever one of the radio buttons is clicked on. When you're satisfied that it all worked out okay, go ahead and close the running movie. Remove all of the frame code, and reinstate QuizApp as the Document Class in the properties panel.

5. The QuizApp Class (the Document Class) Part One


Now we'll get busy working on QuizApp, which currently looks like this:
package { import flash.display.Sprite; public class QuizApp extends Sprite { public function QuizApp() { trace("this is the document class"); } } }

The first order of business will be to create an array, and this array will be an array of--guess what? QuizQuestion instances! Yep! You may not have realized it yet, but an array can hold any type of data. Right here I should clarify something. We often talk about storing data in arrays, or we might say we stored an object or a variable in an array. Actually the array doesn't physically contain the data, but rather references to the data. An array is just a handy way to group things, like a list. An analogy might be putting my house and your house in an array. The array wouldn't be a huge container that enveloped our houses. The array would be more like a list that simply had my address and your address on it. This class file will rely heavily on a kind of division of labor. The tasks that need to be carried out will be listed in the constructor as various function calls. We know that we need to instantiate a group of QuizQuestions, and store them in our array. This task we will call "createQuestions." We know that we need to create buttons (like "Previous" and "Next" buttons, and maybe something like "Finish"?) for navigation. This task we will call "createButtons." It's maybe still a little bit vague at this point, but we know that flash is event driven, so the final thing the constructor should do is take us to the first question. We'll call this task "firstQuestion." From there, clicking on the "next" button could take us to the second question, and so on. Like I said, it's still kind of vague conceptually, but you do know that you need a variable of some kind that will keep track of the identity or index of the current question. So, we'll create a variable that's an integer, and call it currentIndex. This variable will keep track of the index number of whatever the current question is. We can use this index in combination with the array of QuizQuestions to move to the previous or next question. And at this point, I'd like to introduce you to an interesting technique. It involves creating a variable. Nothing new about that, except one thing: the variable we'll create will be of the QuizQuestion type. That's right! When you create a custom class, you're also creating a custom data type at the same time. This took me a long time to understand, but I'm giving you a crash course. We can declare a variable, and datatype it to QuizQuestion! We'll call it "currentQuestion" and add it to the variable list just like any other variable:

var currentQuestion:QuizQuestion;

We will treat this variable a little differently than other variables we might make. Having declared its type, we won't set it equal to a new instance of QuizQuestion. Instead, we're going to be setting it equal to one of the QuizQuestions that are stored in the quizQuestions array, as needed. For example, writing a statement like:
currentQuestion = quizQuestions[0];

...accomplishes a lot! It basically gives us another name by which we can refer to the first element of the quizQuestions array, but there's more to it than that. "currentQuestion" becomes like a re-usable container (or if you prefer, pointer) that we can use to refer to any of the quizQuestions in the array. However, instead of giving it a hard-coded number for an index, we'll use it like this:
currentQuestion = quizQuestions[currentIndex];

That way, the code is much more dynamic. To move to another question, it's as easy as changing the value of currentIndex, then executing the above line. The questions in the array can thus take turns being the current question. Here's the updated class file with this new variable listed:
package { import flash.display.Sprite; public class QuizApp extends Sprite { private var quizQuestions:Array; private var currentQuestion:QuizQuestion; public function QuizApp() { quizQuestions = new Array(); createQuestions(); createButtons(); firstQuestion(); } } }

The functions listed in the constructor haven't been written yet, and that's what we'll get to next, starting with createQuestions. Now, I know that we could create a bunch of questions and store them in an XML file, but I just want to keep this tutorial simple. So, the createQuestions function is going to contain all the data to create all the questions. To add more questions later, you'll have to go back and edit your QuizApp file and type in more questions. But QuizApp will be flexible enough to allow you to add any number of questions you want. And you can add XML capabilities on your own later on if you want. Here's the createQuestions function:
private function createQuestions() { quizQuestions.push(new QuizQuestion("What color is an orange?", 1,

"Orange", "Blue", "Purple", "Brown")); quizQuestions.push(new QuizQuestion("What is the shape of planet earth?", 3, "Flat", "Cube", "Round", "Shabby")); quizQuestions.push(new QuizQuestion("Who created SpiderMan?", 2, "Jack Kirby", "Stan Lee and Steve Ditko", "Stan Lee", "Steve Ditko", "none of the above")); quizQuestions.push(new QuizQuestion("Who created Mad?", 2, "Al Feldstein", "Harvey Kurtzman", "William M. Gaines", "Jack Davis", "none of the above")); }

These are a combination of some nonsensical questions I made up, and some tidbits of trivia from comic book history. Feel free to change these around and/or use your own questions if you want. You're not limited to 4 questions by any means, either. Put in 10, 20, 50, or however many you want. Notice that each new instance of QuizQuestion is not assigned to a variable name, but is instead immediately pushed into the quizQuestions array. It could be done either way, but there is really no need to name a variable. The array's name and the index number in brackets become our way of referring to individual quiz questions. Next, let's go on to writing the createButtons function:
private function createButtons() { var yPosition:Number = stage.stageHeight - 40; prevButton = new Button(); prevButton.label = "Previous"; prevButton.x = 30; prevButton.y = yPosition; prevButton.addEventListener(MouseEvent.CLICK, prevHandler); addChild(prevButton); nextButton = new Button(); nextButton.label = "Next"; nextButton.x = prevButton.x + prevButton.width + 40; nextButton.y = yPosition; nextButton.addEventListener(MouseEvent.CLICK, nextHandler); addChild(nextButton); finishButton = new Button();

finishButton.label = "Finish"; finishButton.x = nextButton.x + nextButton.width + 40; finishButton.y = yPosition; finishButton.addEventListener(MouseEvent.CLICK, finishHandler); addChild(finishButton); }

It's going to be necessary that we import the Button class, now, too, and also the MouseEvent class, so add these items to the import block:
import fl.controls.Button; import flash.events.MouseEvent;

Also, add the variables for the buttons to the variable list:
private var prevButton:Button; private var nextButton:Button; private var finishButton:Button;

The temporary variable "yPosition" is obviously for setting the y position of all the buttons. They are going to be in a row across the bottom of the screen, so their y positions will all be the same. stage.stageHeight was used as a base, so that if you later change the size of the flash application's stage area, this code will not need to be changed. Similarly, hard coded numbers were mostly avoided, and the buttons' x properties are all expressed in terms of the previous one. Each button is added to the display list, and each also has an event listener assigned to it. Let's go ahead and write some empty handler functions, one for each button:
private function prevHandler(event:MouseEvent) { trace("previous"); } private function nextHandler(event:MouseEvent) { trace("next"); } private function finishHandler(event:MouseEvent) { trace("finish"); }

The dummy actions inside these handler functions just give us some feedback that our buttons are working, and also allow us to test the movie without getting errors. Later, we will fill in the real actions, which will be whatever we want to have happen whenever one of those buttons is clicked. Next, we'll write the remaining function referred to in the constructor, firstQuestion:
private function firstQuestion() { currentQuestion = quizQuestions[0]; addChild(currentQuestion); }

If you've added all these things, here's the entire file so far:

package { import flash.display.Sprite; import fl.controls.Button; import flash.events.MouseEvent; public class QuizApp extends Sprite { private var quizQuestions:Array; private var currentQuestion:QuizQuestion; private var prevButton:Button; private var nextButton:Button; private var finishButton:Button; public function QuizApp() { quizQuestions = new Array(); createQuestions(); createButtons(); firstQuestion(); } private function createQuestions() { quizQuestions.push(new QuizQuestion("What color is an orange?", 1, "Orange", "Blue", "Purple", "Brown")); quizQuestions.push(new QuizQuestion("What is the shape of planet earth?", 3, "Flat", "Cube", "Round", "Shabby")); quizQuestions.push(new QuizQuestion("Who created SpiderMan?", 2, "Jack Kirby", "Stan Lee and Steve Ditko", "Stan Lee", "Steve Ditko", "none of the above")); quizQuestions.push(new QuizQuestion("Who created Mad?", 2, "Al Feldstein", "Harvey Kurtzman", "William M. Gaines", "Jack Davis", "none of the above")); } private function createButtons() { var yPosition:Number = stage.stageHeight - 40; prevButton = new Button();

prevButton.label = "Previous"; prevButton.x = 30; prevButton.y = yPosition; prevButton.addEventListener(MouseEvent.CLICK, prevHandler); addChild(prevButton); nextButton = new Button(); nextButton.label = "Next"; nextButton.x = prevButton.x + prevButton.width + 40; nextButton.y = yPosition; nextButton.addEventListener(MouseEvent.CLICK, nextHandler); addChild(nextButton); finishButton = new Button(); finishButton.label = "Finish"; finishButton.x = nextButton.x + nextButton.width + 40; finishButton.y = yPosition; finishButton.addEventListener(MouseEvent.CLICK, finishHandler); addChild(finishButton); } private function firstQuestion() { currentQuestion = quizQuestions[0]; addChild(currentQuestion); } private function prevHandler(event:MouseEvent) { trace("previous"); } private function nextHandler(event:MouseEvent) { trace("next"); } private function finishHandler(event:MouseEvent) { trace("finish"); } } }

Press CTRL-Enter to test your movie. You should get the display of the first quiz question, and when you click the buttons, you get the trace output for each one. Naturally, we want the next button to take us to the next question, the previous button to take us to the previous question, and the finish button to compute the score when all the questions have been answered. We'll take all that on next.

6. The QuizApp Class (the Document Class) Part Two


Next, we'll add all the remaning functionality to the QuizApp class. There is a certain logic to the next, previous, and finish buttons that we need to work into the code. For example, when the user clicks the "next" button, we want the program to advance to the next question in the array, unless the current question is the last question. Also, we probably

want to disallow moving to the next question until the user has supplied an answer to the current one. Similarly, when the "previous" button is clicked, we want to display the previous question in the array, unless the current question is the very first one in the array. In this case, we won't put any check to make sure the user has answered the current question, as they will most likely move forward again and return to it. Finally, when the "finish" button is clicked, which can be done at any time, we want the program to go ahead and compute the score, but only if all of the questions have been answered. If that's the case, then it will go ahead and compute their score, and display the results. All this logic will be programmed into the buttons; we just have to translate the above into Actionscript. But first, it would be nice if we had something like a text box where we could display messages to the user. If the "next" button is clicked, and the user hasn't answered the current question, we could use this message box to tell them so. Similarly, any other messages we need to get across could be diplayed in this box. We could also use it to display the final results. So, once again, we will import the TextField and TextFieldAutoSize classes:
import flash.text.TextField; import flash.text.TextFieldAutoSize;

Add the following command to the constructor, just after createButtons but before firstQuestion:
createStatusBox();

Add the following to the variables list:


private var status:TextField;

Now we're all set to write the createStatusBox function. Add in this function just after the createQuestions and createButtons functions, but before the firstQuestion one:
private function createStatusBox() { status = new TextField(); status.autoSize = TextFieldAutoSize.LEFT; status.y = stage.stageHeight - 80; addChild(status); }

This just creates a new TextField instance named "status," autosizes it to the left (so it'll expand to the right), and places it 80 pixels above the bottom of the stage, which will be just above the row of buttons. We're going to be calling on this textbox to display messages so often that it will be handy to make a function for that purpose, which we'll call showMessage. So add this function right after the createStatusBox one:

private function showMessage(theMessage:String) { status.text = theMessage; status.x = (stage.stageWidth / 2) - (status.width / 2); }

This function accepts a String as a parameter, which will be whatever message we want to display. It sets it as the text of the status textbox, and then the next line centers it on the screen. Now, we've got our array of quizQuestions, we've got our buttons, and we've got a way to display messages on the screen. The next obvious step is to program the buttons. But first, let's create a function that will put all of the questions on the display list. Then, we'll create another function that will make them all invisible. After that, when we want to hide or show a question, we'll just toggle its visibility using its "visible" property. Here are the functions to add:
private function addAllQuestions() { for(var i:int = 0; i < quizQuestions.length; i++) { addChild(quizQuestions[i]); } } private function hideAllQuestions() { for(var i:int = 0; i < quizQuestions.length; i++) { quizQuestions[i].visible = false; } }

Also, add these function calls to the constructor, after everything else but (once again) before firstQuestion:
addAllQuestions(); hideAllQuestions();

Your constructor function should now look like this:


public function QuizApp() { quizQuestions = new Array(); createQuestions(); createButtons(); createStatusBox(); addAllQuestions(); hideAllQuestions(); firstQuestion(); }

Next, change the firstQuestion function to the following:


private function firstQuestion() { currentQuestion = quizQuestions[0]; currentQuestion.visible = true; }

Now, we can program the buttons. We'll replace those trace messages with the real actions we want to perform. Earlier, I described the logic. Here's the code for the "previous" button:

private function prevHandler(event:MouseEvent) { showMessage(""); if(currentIndex > 0) { currentQuestion.visible = false; currentIndex--; currentQuestion = quizQuestions[currentIndex]; currentQuestion.visible = true; } else { showMessage("This is the first question, there are no previous ones"); } }

The first action here is to clear the status text box of anything that might already be in it. We'll include this as the first action of all the buttons. The if statement checks to see if the currentIndex is greater than 0. If it's not, it's 0, and we're at the first question, and there is no previous question to go to, in which case the else block takes over, and our custom "showMessage" function is employed to tell the user that. But if currentIndex is greater than 0, we first hide currentQuestion, take away one from the currentIndex, then set currentQuestion equal to quizQuestions[currentIndex]. Finally, we make currentQuestion visible again. But now it's a different question than it was three lines ago; it's now the previous one in the array of questions. Here's the code for the "next" button:
private function nextHandler(event:MouseEvent) { showMessage(""); if (currentQuestion.userAnswer == 0) { showMessage("Please answer the current question before continuing"); return; } if (currentIndex < (quizQuestions.length - 1)) { currentQuestion.visible = false; currentIndex++; currentQuestion = quizQuestions[currentIndex]; currentQuestion.visible = true; } else { showMessage("That's all the questions! Click Finish to Score, or Previous to go back"); } }

The logic here is similar to the last one, with a few differences. This time, the first check is to see if the user has answered the question in front of them. Remember that "userAnswer" is one of the "getter" functions built into QuizQuestion. Whenever the user answers the question, inside of QuizQuestion the variable "theUserAnswer" gets set to the value of whatever multiple choice button they clicked, and the "userAnswer" getter function returns that. Long story short, if it's still 0, they haven't answered the question yet. The return statement there causes the function to stop executing.

The next if statement checks to make sure the user is not at the last question. If that's the case, the following statements proceed to the next question, and if so, the showMessage function is called to display a message telling you so. Here's the code for the "finish" button:
private function finishHandler(event:MouseEvent) { showMessage(""); var finished:Boolean = true; for (var i:int = 0; i < quizQuestions.length; i++) { if (quizQuestions[i].userAnswer == 0) { finished = false; break; } } if (finished) { prevButton.visible = false; nextButton.visible = false; finishButton.visible = false; hideAllQuestions(); computeScore(); } else { showMessage("You haven't answered all of the questions"); } }

This function first sets up a local Boolean variable called "finished," which is set to true. Next, all the questions are run through in a loop and each one tested to see if any of them haven't been answered yet. The first question that passes this if test will cause the "finished" variable to be set to false. The loop is then terminated. The next if statement uses the result of that test to see if the user really answered all the questions. If so, it hides all the buttons, hides all the questions, and makes a call to a function (which we'll write next) called computeScore. If not, it displays an appropriate message. Now, the final thing to write is the computeScore function. First, though, we need a variable named "score" in our variables list, so add this line there:
private var score:int = 0;

Here's the code for computeScore:


private function computeScore() { for (var i:int = 0; i < quizQuestions.length; i++) { if (quizQuestions[i].userAnswer == quizQuestions[i].correctAnswer) { score++; } } showMessage("You answered " + score + " correct out of " + quizQuestions.length + " questions."); }

Once again, the array of quizQuestions is run through, and each one's user answer is compared to the correct answer. If they match, the score is incremented by 1. Finally, after the loop completes, showMessage is once again called upon to display the results. Here's the entire completed QuizApp file:
package { import import import import import flash.display.Sprite; fl.controls.Button; flash.events.MouseEvent; flash.text.TextField; flash.text.TextFieldAutoSize;

public class QuizApp extends Sprite { //for managing questions: private var quizQuestions:Array; private var currentQuestion:QuizQuestion; private var currentIndex:int = 0; //the buttons: private var prevButton:Button; private var nextButton:Button; private var finishButton:Button; //scoring and messages: private var score:int = 0; private var status:TextField; public function QuizApp() { quizQuestions = new Array(); createQuestions(); createButtons(); createStatusBox(); addAllQuestions(); hideAllQuestions(); firstQuestion(); } private function createQuestions() { quizQuestions.push(new QuizQuestion("What color is an orange?", 1, "Orange", "Blue", "Purple", "Brown")); quizQuestions.push(new QuizQuestion("What is the shape of planet earth?", 3, "Flat", "Cube", "Round", "Shabby")); quizQuestions.push(new QuizQuestion("Who created SpiderMan?", 2, "Jack Kirby", "Stan Lee and Steve Ditko",

"Stan Lee", "Steve Ditko", "none of the above")); quizQuestions.push(new QuizQuestion("Who created Mad?", 2, "Al Feldstein", "Harvey Kurtzman", "William M. Gaines", "Jack Davis", "none of the above")); } private function createButtons() { var yPosition:Number = stage.stageHeight - 40; prevButton = new Button(); prevButton.label = "Previous"; prevButton.x = 30; prevButton.y = yPosition; prevButton.addEventListener(MouseEvent.CLICK, prevHandler); addChild(prevButton); nextButton = new Button(); nextButton.label = "Next"; nextButton.x = prevButton.x + prevButton.width + 40; nextButton.y = yPosition; nextButton.addEventListener(MouseEvent.CLICK, nextHandler); addChild(nextButton); finishButton = new Button(); finishButton.label = "Finish"; finishButton.x = nextButton.x + nextButton.width + 40; finishButton.y = yPosition; finishButton.addEventListener(MouseEvent.CLICK, finishHandler); addChild(finishButton); } private function createStatusBox() { status = new TextField(); status.autoSize = TextFieldAutoSize.LEFT; status.y = stage.stageHeight - 80; addChild(status); } private function showMessage(theMessage:String) { status.text = theMessage; status.x = (stage.stageWidth / 2) - (status.width / 2); } private function addAllQuestions() { for(var i:int = 0; i < quizQuestions.length; i++) { addChild(quizQuestions[i]); } } private function hideAllQuestions() { for(var i:int = 0; i < quizQuestions.length; i++) { quizQuestions[i].visible = false;

} } private function firstQuestion() { currentQuestion = quizQuestions[0]; currentQuestion.visible = true; } private function prevHandler(event:MouseEvent) { showMessage(""); if(currentIndex > 0) { currentQuestion.visible = false; currentIndex--; currentQuestion = quizQuestions[currentIndex]; currentQuestion.visible = true; } else { showMessage("This is the first question, there are no previous ones"); } } private function nextHandler(event:MouseEvent) { showMessage(""); if(currentQuestion.userAnswer == 0) { showMessage("Please answer the current question before continuing"); return; } if(currentIndex < (quizQuestions.length - 1)) { currentQuestion.visible = false; currentIndex++; currentQuestion = quizQuestions[currentIndex]; currentQuestion.visible = true; } else { showMessage("That's all the questions! Click Finish to Score, or Previous to go back"); } } private function finishHandler(event:MouseEvent) { showMessage(""); var finished:Boolean = true; for(var i:int = 0; i < quizQuestions.length; i++) { if(quizQuestions[i].userAnswer == 0) { finished = false; break; } } if(finished) { prevButton.visible = false; nextButton.visible = false; finishButton.visible = false; hideAllQuestions(); computeScore(); } else { showMessage("You haven't answered all of the questions"); } } private function computeScore() { for(var i:int = 0; i < quizQuestions.length; i++) {

if(quizQuestions[i].userAnswer == quizQuestions[i].correctAnswer) { score++; } } showMessage("You answered " + score + " correct out of " + quizQuestions.length + " questions."); } } }

Closing Comments:
That concludes this tutorial. There are a lot of ways this application could be jazzed up, including: Styling the text using a different font. Making a fancier transition when changing questions (maybe fly-in, fly-out?) Give feedback at the end as to the correct answer to each question. Get the question and answer data from an XML file. This application is "bare bones" and needs a lot more work. In actuality, though, it's not that the quiz itself is any big deal. It just served as a simple example application. The concept of classes and objects is the thing to take away from this. Maybe you don't want to make a quiz, but rather a slideshow, or an MP3 player, or a menu. The details may differ in what you want to make, but the underlying concepts of objects and classes are going to remain the same. I hope you enjoyed this article. I enjoy writing these, and I've been getting a lot of good feedback from them. I'll try to keep them coming as I myself learn more. PS. See the introduction to this article (click the "up" link below) to download a ZIP file of this application.