instruction_banner

by Kevin Schmitt

The Mother of All Flash Quiz Engines, Part 3

Our XML-based quiz document is really going places. We explained it and then loaded it into Flash in the first two installments, so logically it’s time to make some sense of it using Flash’s superheroic XML parsing abilities. I really can’t stress enough how much better XML parsing is under ActionScript 3, because if things hadn’t changed, there certainly would have been a nice padded cell waiting for me at Bellevue right about now. So join me as I share the joy of reading XML in Flash with you, the most loyal reader.

Quick recap

Let’s kick things off by reviewing our trusty ol’ XML document, shall we? Behold, for your viewing pleasure:

<!--mode types: inline, reveal, or submit-->
<quiz mode="inline" timer="0">
	<item>
		<question>The first question is a true/false question.</question>
		<answer correct="true">True</answer>
		<answer>False</answer>
		<explanation>When a question has one answer called "true" and one answer called "false," it is a true/false question.</explanation>
	</item>
	<item>
		<question>Does this question have three possible answers?</question>
		<answer correct="true">Yes</answer>
		<answer>No</answer>
		<answer>Maybe</answer>
		<explanation>Some questions have two answers, some have four, but this one has three.</explanation>
	</item>
	<item>
		<question>How many possible answers does this question have?</question>
		<answer>One</answer>
		<answer>Two</answer>
		<answer>Three</answer>
		<answer correct="true">Four</answer>
		<explanation>Four is an excellent number in general, so we are very fortunate that it is the right answer in this case.</explanation>
		<media>http://url/path/to/mediafile.jpg</media>
	</item>
</quiz>

Still looks as lovely as the day I first wrote it. Now, just to catch up with where we were last time, you need to create a new ActionScript 3 file in Flash CS4, rename the first frame in the Timeline to “actions,” and then open the Actions Panel (Window:Actions). With the Actions Panel at the fore, enter in the following script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var quiz:XML;
var xloader:URLLoader = new URLLoader();
 
xloader.addEventListener(IOErrorEvent.IO_ERROR, onError);
xloader.addEventListener(Event.COMPLETE, onLoaded);
xloader.load(new URLRequest("quiz.xml"));
 
function onError(e:IOErrorEvent):void {
	trace("An error occured when attempting to load the XML:\r" + e.text);
}
 
function onLoaded(e:Event):void {
	quiz = XML(e.target.data);
	parseXML();
}
 
function parseXML():void {
	trace(quiz);
}

We went through what each of these lines does in copious detail in our last installment, so head back there using the Related Links at the end of this page if you’re so inclined. What we’re going to change today is Line 18, turning our trusty trace statement into code that finds the stuff we want in the XML file. But first: an XML parsing primer.

Nodes and attributes and base 0, oh my!

I could get technical here and go back into how ActionScript 3’s XML parsing is based on the ECMAScript for XML specification (E4X for short), but we already went over that last time. Instead, permit me to use the magic word of the day: drilldown. In a nutshell, that’s how AS3 handles XML: a logical drilldown to the content you want. Just as you would “eye parse” XML by following nodes down the tree (e.g., quiz-item-question), that’s how AS3’s parsing works also. So let’s try it out. Head back to the Actions panel and modify the trace(quiz) command to read the following:

trace(quiz.item.question);

What you’ll get back in the Output panel should look something like Figure 1:


Figure 1

So what went on here? In effect, Flash is tracing back a specialized array, known as an XML list, that consists only of the nodes which match the criteria of the drilldown. Similarly, if we wanted to see all the explanation nodes, we’d just have to send the following command:

trace(quiz.item.explanation);

Which would lead to the Output panel tracing out:


Figure 2

… and so on. Hopefully you can begin to see the possibilities. Depending on how you need to parse an individual XML document, you can loop through various nodes, doing things like assigning values to variables and routing node content to individual Movie Clips. However, what if you want to target a specific node, or get content assigned to an XML attribute? Well, let’s try both of those. Say I want to get just the first question:

trace(quiz.item[0].question);

Figure 3 shows the trusty Output panel’s, well, output:


Figure 3

There are a couple things of note here. One, when you target a specific node, Flash will return just the contents, and not the nodes themselves, as is the case when you request an XML list. Second, and more germane to this discussion, you may be wondering why I specified question 0 when I was looking for the first question. The answer is that ActionScript treats XML documents as zero-based, the same way it does with arrays and month numbers. (As a somewhat amusing aside, during one particular stretch when I was doing a lot of date calculations, I carried Flash’s zero-based month numbering into the real world. Any time I needed to write a date, it was always one month off, e.g., October 4th became as 9/4. Took me a while to snap back to normal.) Anyway, the upshot of zero-based numbering is that the first item in a list is item 0, the second is item 1, and so on. In this case, all of our individual questions are packaged into item nodes, and by specifying item[0], I can target any of the content in the first item node.

That leaves us with attributes, also known as “that which is contained inside the node itself.” We’ve got a few examples of attributes in our XML document, most notably those which indicate the correct answer to a question. As it turns out, getting attributes is pretty easy also — it’s just a matter of adding a simple @ symbol to the path. Let’s start with an easy one. If I wanted to find out the mode type of the quiz, I would simply need to trace out the following:

trace(quiz.@mode);

And testing the movie will permit the Output panel to reveal:


Figure 4

Another way to do the same thing is to explicitly refer to an attribute as an attribute. For example:

trace(quiz.attribute("mode"));

… yields the same result at the previous statement. Why do I mention this? Well, combining that syntax with AS3’s built-in filtering could allow us to trace out all the correct answers as an XML list with a single command. Behold:

trace(quiz.item.answer.(attribute("correct") == "true"));

Test the movie again, and the Output panel reveals:


Figure 5

What we did there is combine our usual drilldown with an integrated search, looking for all answers with an attribute named “correct” which is set to “true.” The bottom line is that the E4X syntax is not only incredibly simple, but also very powerful, and with a few logical statements, we can easily parse all kinds of XML documents. Now, while this little primer is hardly comprehensive, hopefully by now you’re getting the idea and can use these tips as a jumping off point for your own experiments.

The first steps towards a real quiz

Lest you think I’m going to bail on you today without actully making concrete moves towards building the actual quiz engine, rest assured we’re going to do just that before we call it a day. Here are the steps we need to take in Flash to get us to a basic display of a single question:

  1. On the main Timeline, make sure you have separate actions and items layers.
  2. On the main Stage, in the items layer, create two dynamic text fields at the top and bottom of the window.
  3. Name the top one question_txt and the bottom one feedback_txt using the Properties panel.
  4. Create four new symbols in the library:
    • A graphic, named gSquare, consisting of a 25×25 pixel plain black square.
    • A button, named bSquare, which is made “invisible” by using the old trick of placing only an instance of gSquare on the Hit layer.
    • A movie clip, named mcSelector, which consists of several instances of gSquare to make up what is, in effect, a checkbox. The important thing here is that the clip contains at least one symbol named hl_mc, which will serve as the “on” state for the checkbox.
    • Another movie clip, named mcAnswer, made up of one instance of mcSelector (and named select_mc in the Properties panel), one instance of bSquare (named click_btn in the Properties panel), and one dynamic text field named answer_txt in the Properties panel.

Figures 6 and 7 show how the main Stage and mcAnswer are set up in Flash, respectively:


Figure 6


Figure 7

Now, this thing isn’t going to win any design awards, but that’s not what we’re after. In any event, we now need to make mcAnswer available as a class to be called from ActionScript, since we’re going to dynamically create new instances of the symbol depending on how many answer nodes are found in each item in our XML file. To do that, right-click on mcAnswer in the Library and select Properties. In the resulting Symbol Properties dialog box, make sure the settings match those of Figure 8:


Figure 8

Now, if you’ve set your movie up like mine, and (more importantly) named everything correctly, the scripts we’re about to add should just work out of the box. First up, we’ve got some new stuff to add to our original Timeline script on frame 1 of the actions layer. Feel free to just paste this in:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
var current:Number = 0;
var qbox:Sprite = new Sprite;
var quiz:XML;
var xloader:URLLoader = new URLLoader();
 
qbox.x = 10;
qbox.y = 80;
this.addChild(qbox);
 
xloader.addEventListener(IOErrorEvent.IO_ERROR, onError);
xloader.addEventListener(Event.COMPLETE, onLoaded);
xloader.load(new URLRequest("quiz.xml"));
 
function onError(e:IOErrorEvent):void {
	trace("An error occured when attempting to load the XML:\r" + e.text);
}
 
function onLoaded(e:Event):void {
	quiz = XML(e.target.data);
	displayQuestion();
}
 
function displayQuestion():void {
	while (qbox.numChildren > 0) {
		qbox.removeChildAt(0);
	}
	question_txt.text = quiz.item[current].question;
	var starty:Number = 0;
	for each (var answeritem:XML in quiz.item[current].answer) {
		var ansobj:Sprite = new Answer();
		ansobj.y = starty;
		ansobj.answer_txt.text = answeritem;
		if (answeritem.@correct == "true") {
			ansobj.correct = true;
		}
		qbox.addChild(ansobj);
		starty += 55;
	}
	feedback_txt.text = "";
}
 
function setClick(correct:Boolean):void {
	if (correct) {
		feedback_txt.text = "Right! " + quiz.item[current].explanation;
	} else {
		feedback_txt.text = "Sorry, that's wrong. " + quiz.item[current].explanation;
	}
}

We’re up to 48 lines of code goodness now, some new, some not. While we’re all intimately familiar with the workings of the stuff that came before, let’s go through what’s been added:

Lines 1-2. Declares two new variables, current, which will always hold the numeric value of the particular question we’re displaying, and set to an initial value of 0. We’ve also declared qbox, a Sprite which will serve as a container for however many answers our question needs to generate.

Lines 6-8. Sets the x and y coordinates for qbox and adds it to the Stage.

Line 20. Replaces the parseXML function from our first pass with a call to a new function, displayQuestion().

As for the just-now-mentioned displayQuestion() function…

Lines 24-26. Since this is the function we’re going to call whenever a new question needs to be displayed, we need to preemptively clear out the contents of qbox. This while loop does just that.

Line 27. Fills in one of our two dynamic text fields on the Stage with the appropriate question, using the value of current to target the correct item in the XML file.

Line 28. Declares a new variable, starty, which will serve as the y coordinate each new answer will be placed at.

Lines 29-38. Starts the for...each loop which will populate our answer symbols and add them to the qbox container. First, we create a variable, answeritem, for each item in the set of answers corresponding to the XML item that matches the value of current. For each of those, we’re creating a new Sprite from the Answer class we set up earlier in the Library, giving it a variable name of ansobj. We’re then setting the y coordinate of ansobj to the value of starty, filling in the text field with the value of answeritem, and then determining if the correct answer is true through a simple if statement. If the answer is the correct one, we’re “baking” a Boolean value of true into the ansobj itself. Finally, we’re adding ansobj to the qbox container, and then incrementing the value of starty by 55 so each answer isn’t sitting on top of the last one.

Line 39. Clears out any text from our second dynamic text field on the Stage.

There’s yet another new function here, setClick, which does the following:

Line 42. setClick is designed to take a single Boolean argument, which will be passed up from the Answer item in response to a click.

Lines 43-47. Establishes some conditional logic dependent on the value of the correct variable passed as an argument to the setClick function. If correct is true, the dynamic text field will fill with the phrase “Right!” plus the correct explanation set forth in the XML document. If correct is false, the prepended text will be a little less positive, but still show the same explanation.

Still with me? Good. The second script we need to add has to go into the actions layer of the mcAnswer symbol, so head over there and paste the following into the Actions panel:

1
2
3
4
5
6
7
8
9
10
11
12
13
var correct:Boolean;
 
select_mc.hl_mc.visible = false;
 
click_btn.addEventListener(MouseEvent.CLICK, onClick);
 
function onClick(e:MouseEvent):void {
	for (var i:Number = 0; i < this.parent.numChildren; i++) {
		this.parent.getChildAt(i).select_mc.hl_mc.visible = false;
	}
	select_mc.hl_mc.visible = true;
	this.parent.parent.setClick(this.correct);
}

That’s better… only 13 lines in this one, which should make for a much shorter — but no less important — explanation:

Line 1. Declares a placeholder Boolean variable, correct, which by default is false. The for..each loop in the displayQuestion() function on the main Timeline will set this to true as events warrant.

Line 3. Sets the highlight in the mcSelector instance to false, so that it’s not selected by default.

Line 5. Adds a click listener to our invisible click_btn button, mapped to the onClick function.

Lines 7-13. The main function of our Answer symbol, onClick first loops through all the children contained in the qbox container and turns off their highlighted states, then turns on the highlighted state for itself, and finally, sends the value of the correct Boolean up two levels to the main Timeline’s setClick function.

OK, with all that set, we’re ready to test the movie. If all has gone as planned, the movie should look something like this:


Figure 9

If I click on the “True” answer, I’ll get this:


Figure 10

Clicking on “False” gives me this:


Figure 11

And finally, if I change line 1 of the actions layer on the main Timeline to have a current value of 1, I’ll get the second question; testing the movie and clicking on the “Yes” answer yields the following:


Figure 12

After all that, we’re well on our way. We’ve learned how to parse XML in ActionScript 3, and then created a very simple question display engine that gives us instant feedback based on our selections. If you’d like to download the pre-made Flash CS3 (or later) file and study it for yourself, you can do so at the link at the end of the page. Now, there are still a lot of bells and whistles to install in order to make this the truly full-featured quiz engine we set out to make lo those many months ago, and we’ll get to those in subsequent installments. But for now, enjoy the fruits of your labor!