Chapter 14. XML

IN THIS CHAPTER

Understanding XML Structure

Creating an XML Object

Using Variables in XML

Reading XML

Writing XML

Deleting XML

Loading External XML Documents

Sending to and Loading from a Server

An XML-Based Navigation System

What’s Next?

XML, which stands for Extensible Markup Language, is a structured, text-based file format for storing and exchanging data. If you’ve seen HTML before, XML will look familiar. Like HTML, XML is a tag-based language. However, it was designed to organize data, rather than lay out a web page. Instead of a large collection of tags that define the language (as found in HTML), XML is wide open. It starts with only a handful of preexisting tags that serve very basic purposes. This freedom allows you to structure data in a way that’s most efficient for your needs.

In the past, traversing and working with XML within ActionScript has not been the most pleasant or efficient of experiences. Fortunately, E4X (which stands for ECMAScript for XML), is a part of ActionScript 3.0. E4X is the current standard for reading and writing XML documents and is maintained by the European Computer Manufacturers Association. It greatly reduces the amount of code and hoop-jumping required to communicate with XML. It allows you to treat XML objects like any other object with familiar dot syntax, and provides additional shortcuts for traversing XML data. You can use ActionScript’s E4X implementation to create XML inside a class or the Flash timeline or, more commonly, load an XML file at runtime.

In this chapter you’ll learn the essentials of E4X, and other XML-related concepts. We’ll cover:

  • Understanding XML Structure. The flexibility of XML means you can set up files in a manner that best serves your project’s requirements. Unlike other tag-based languages, there’s no library of tags to memorize—just a few simple rules to follow.

  • Creating an XML Object. To learn how to read and write XML, you must first be able to create an XML object. We’ll show you how to create an object directly from XML nodes and from parsing a string. Later, we’ll show you how to load XML from an external file.

  • Using Variables with XML Nodes. Both when creating an XML object and when writing XML on the fly, you can use variables when building nodes. This gives you the same latitude to manipulate XML on the fly using stored information that you enjoy when working with other data. We’ll also review basic variable practice to build a string, which can then be parsed, or analyzed, as XML.

  • Reading XML. Reading and parsing XML files is significantly easier using E4X than when using prior versions of ActionScript. You can find specific pieces of information, as well as sweep through the entire document, using properties and methods that are consistent with other ActionScript objects.

  • Writing XML. You can also put the same power, clarity, and ease of use to work when creating XML. You can create XML for internal use or build data structures for use with servers or other clients.

  • Deleting XML. Whether eliminating unwanted items during reading to simplify the final XML object or removing errant elements when writing, it is sometimes necessary to delete elements.

  • Loading External XML Documents. Because you determine its structure, XML is highly efficient and often the format of choice for portable data. As a result, external XML documents are very useful for loading data at runtime.

  • Communicating with XML Servers. After learning how to read and write XML, you can then use it in your communications between servers and other clients.

  • An XML Navigation System. We’ll enhance the navigation system created in Chapter 6, reading the menu content from XML instead of an array. We’ll also populate a Loader instance so you can use the menu to load external SWFs and images.

Understanding XML Structure

When working with large data sets, XML is a vast improvement over the name-value pairs that are used in simple web communications, such as HTML forms. An XML document can contain much more data, but can also convey an information hierarchy, detailing relationships among data elements. For example, you can organize a list of users—with names, emails, passwords, and similar information—much the way you would a traditional database. Records might be represented with tags (called element nodes in XML) that define a single user, similar to a database record; nested, or child, tags might serve as the equivalent of database fields, associating data with that user. Element nodes can contain text, which is also considered an XML node (a text node) for easy parsing. Once you establish a structure, you can duplicate a tag set any time a new record (or user, in this case) is added, and the consistent structure can be reliably navigated when retrieving the data.

Here is an example XML document:

<users>
    <user>
        <username>johnuser</username>
        <email>[email protected]</email>
        <password>123456</password>
    </user>
    <user>
        <username>janeuser</username>
        <email>[email protected]</email>
        <password>abcdef</password>
    </user>
</users>

Because you make up the tags as you go along, this document would be just as valid if you replaced the word “user” with “student” throughout. Neither the data nor the data structure would change. The document simply might be more meaningful if you were describing students instead of users.

The easiest way to understand this open format is to remember that XML simply structures your content. While HTML defines the layout of a web page and gives instructions for displaying that page to a browser, XML does nothing more than organize data. It’s up to the application to correctly parse the data. Think of XML as you would any other structuring effort. For example, you might export text from a database or a spreadsheet using XML as a replacement for comma-delimited or tab-delimited formats (records separated by carriage returns, fields separated by commas or tabs, respectively).

There are only a few simple rules to remember when you’re creating an XML document:

  • Every XML document must have a root node that contains all other information. It doesn’t have to have a specific name, but all XML data must be nested within one node.

  • XML is case-sensitive. It doesn’t matter whether you use lowercase or uppercase, but the case used in matching opening and closing tags must be consistent. There are two schools of thought when it comes to choosing a case. The first school advocates uppercase as a means of making it easier to separate tags from content when you glance at the document. The other school pursues lowercase as a de facto standard form used in programming, URLs, and other places where case sensitivity matters.

    Note

    As a personal preference, we opt for lowercase. You’ll learn later in this chapter how you can address XML elements using dot syntax the same way you would create custom properties of objects, as described in the section Custom Objects section of Chapter 2. However, case sensitivity must be preserved. Therefore, a node called username in lowercase would be represented as <username>, while uppercase requires <USERNAME>. We prefer to reserve uppercase in ActionScript as a convention for representing constants.

  • All nodes must be closed—either with a balancing tag or as a self-closing tag. Balancing tags must be written as <one>text</one> versus <one>text. Single tags (such as a line break, <br>, in HTML), must use the self-closing format—preceding the last greater-than symbol with a slash (such as <br />).

  • All tags must be properly nested. The following is incorrect: <one><two>term</one></two>. But this is correct: <one><two>term</two></one>.

  • All attributes must be enclosed within quotation marks. The following would generate an error: <story class=headline>News</story>. But this will not: <span class="headline">News</span>. This is important not only because the XML must be well formed, but because attributes are also XML nodes and can be parsed just like element and text nodes.

A few other items that warrant a bit more discussion are covered in the following sections.

White Space

White space includes all returns, tabs, and spaces between tags, as indicated in the example below:

<users>
    <user>
        <username>johnuser</username>
        <email>[email protected]</email>
        <password>123456</password>
    </user>
</users>

By contrast, the following example has no white space:

<users><user><username>richshupe</username><email>[email protected]
</email><password>123456</password></user></users>

Both are representations of the same document, and they each have their benefits. The file size of the version with no white space is a tiny bit smaller due to the reduced number of characters; however, in all but very large documents, this is usually negligible. The version with white space is much easier to read.

White space is important to understand because this information could be interpreted as text. Return, tab, and space characters are all legal text entities, so the XML parser must be told to ignore them or they will be counted as such when reading the document. This is because tags and text are separate objects when parsed. The tags are called element nodes and the text entries within the tags are called text nodes. Because the white space can be interpreted as text nodes, the previous XML examples would contain a different number of nodes with and without white space.

Readability usually prevails when formatting XML documents and, fortunately, ignoring white space is the default behavior of ActionScript’s E4X implementation. To parse white space, you must add this static property setting to your script before creating your XML object.

XML.ignoreWhitespace = false;

Note

We strongly recommend against this unless you have a pressing need to parse the whitespace. If you choose not to ignore whitespace, every discrete run of space, tab, and return characters will be interpreted as a text node.

Declarations

You will likely see additional tags at the start of XML documents that you should be aware of. The first is the XML declaration tag, and it usually looks something like this:

<?xml version="1.0" encoding="UTF-8"?>

This may differ, depending on the source document, but the purpose of such a tag is usually the same. It tells parsers the version of the XML language specification and the type of encoding used when the file was written. Another example of a declaration tag is the document type declaration (DTD), which is used to identify a set of rules against which a parser will compare the XML when validating. An example can be seen here:

<!DOCTYPE note SYSTEM "note.dtd">

ActionScript does not validate XML using these declaration tags. If you plan to use an XML document with another parser, such as a server-side component of your project with which ActionScript will communicate, you may need to use these tags. However, ActionScript does not require their presence.

Comments and Processing Instructions

XML comments use the same form as HTML comments: <!-- comment -->. In ActionScript, they are ignored by default but they can be parsed using E4X in the rare case that you may want to use them. For example, you may want to track version or date information that wouldn’t otherwise appear in the structure of your data. To parse comments, you must add the following static property assignment to your script before creating your XML object:

XML.ignoreComments = false;

Processing instructions are strings typically used when working with style sheets to display XML, and ActionScript does not use them. They take the form: <?instruction ?>. They are ignored by default but can also be parsed using E4X and converted to strings if you wish to use them, though this, too, is exceedingly rare. To do so, you must add this static property setting to your script before creating your XML object:

XML.ignoreProcessingInstructions = false;

Entities and the CDATA Tag

When writing your XML documents, you must be aware that it is possible to confuse a parser or even break your document by including restricted characters. For example, the following document would cause a problem:

<example topic="<, "">use entities for < and "</example>

In this case, the XML parser assumes the quotation mark within the attribute is closing the attribute prematurely, and it sees the two less than symbols (one within the attribute and one within the text node) as the start of XML tags. The quotation mark within the text node is fine, as it does not conflict with the quotation marks required for attributes. To be considered well formed, the offending characters must be represented by entities:

<example topic="&lt;, &quot;">use entities for &lt; and "</example>

There are only five entities included in the XML specification, as seen in Table 14-1.

Table 14-1. The five entities included in the XML specification

Entity

Correct Form

Notes

<

&lt;

Less than

>

&gt;

Greater than

&

&amp;

Ampersand

'

&apos;

Apostrophe

"

&quot;

Quotation mark

To include other special characters, or preserve special formatting, you can use a CDATA (character data) tag. This tag wraps around the special content and tells the XML parser to consider everything therein as plain text. This is particularly useful when you want to include HTML, white space, or formatted text inside your XML document. The following example might be used to display a sample ActionScript function. The less than and greater than symbols will not cause a problem, and the white space will be preserved.

<stuff>
    <![CDATA[
        function styleBold(txt:String):String {
            return "<b>" + txt + "</b>";
        }
    ]]>
</stuff>

Creating an XML Object

The first step in using XML in ActionScript 3.0 is typically to create an instance of the XML class. There are two ways of creating an XML instance from internal data. (We’ll cover loading external XML files separately.) The first approach is to write the content explicitly, as XML nodes, when creating the object. The following example is found in the xml_from_nodes.fla source file:

1    var las3Data:XML = <authors>
2                         <author>
3                           <firstname>Rich</firstname>
4                           <lastname>Shupe</lastname>
5                         </author>
6                         <author>
7                           <firstname>Zevan</firstname>
8                           <lastname>Rosser</lastname>
9                         </author>
10                      </authors>;
11    trace(las3Data);

There are a couple of wonderful things about of this approach. First, the XML is automatically treated like XML, rather than like plain text. As a result, an instance of the XML class is automatically created, and you don’t need to enclose the XML in quotes. Second, you don’t need to worry about white space or line breaks until the next ActionScript instruction is encountered. In line 11, for example, a trace statement occurs. This makes it easy to format your XML in a nice, readable way.

Note

This is a good example of where the semicolon at the end of a line significantly improves readability. The semicolon at the end of line 10 clearly indicates the end of the XML.

The second approach is to create the XML instance from a string. This is handy for creating XML on the fly from user input, such as when a user types information into a field. In this case, you must use the XML class constructor explicitly, passing to it the string you want to convert to XML. This example is in the xml_from_string.fla source file.

1    var str:String = "<book><publisher>O'Reilly</publisher></book>";
2    var las3Data:XML = new XML(str);
3    trace(las3Data);

Using Variables in XML

It’s even possible to use variables when writing XML nodes by enclosing the variables in braces. This can be seen inside the tags in lines 7 and 8 in the following example, found in the xml_from_nodes_variables.fla source file.

1    var author1First:String = "Rich";
2    var author1Last:String = "Shupe";
3
4    var las3Data:XML = <authors>
5                          <author>
6                            <firstname>{author1First}</firstname>
7                            <lastname>{author1Last}</lastname>
8                          </author>
9                        </authors>;
10    trace(las3Data);

If you choose to create XML from a string, you can also use standard variable syntax to build the string before parsing. Lines 2 and 3 join two strings with a variable before converting to XML. The following code is found in the xml_from_string_variables.fla source file.

1    var publisher:String = "O'Reilly";
2    var str:String = "<book><publisher>" + publisher +
3                     "</publisher></book>";
4    var las3Data:XML = new XML(str);
5    trace(las3Data);

Reading XML

ActionScript 3.0 makes reading XML easier than ever before. You can now use syntax consistent with that of other ActionScript objects. Not only can you use basic properties and methods of an XML instance, you can also work with individual nodes and attributes using familiar dot syntax.

A familial relationship is used to describe nodes. Nested element nodes, text nodes, and comments are children of their parent element nodes. Nodes at the same level—meaning they have the same parent node—are known as siblings. Retrieving a node from an XML object is as easy as drilling down through the family tree of parent and child nodes—just like you would access a nested movie clip from the main timeline.

Before we continue, take care to note that the root node of an XML object is never included in dot syntax that references its child nodes. Consider this example:

var las3Data:XML = <book><publisher>O'Reilly</publisher></book>;
//trace(las3Data.book.publisher);
trace(las3Data.publisher);

The commented line is wrong, and the last line is correct. This is because the root node is synonymous with the XML instance. Every XML document must have a root node, so traversing it is an unnecessary extra step, and it should not be referenced.

Note

Including a root node in your syntax targeting XML nodes will not only produce no useable result, it typically won’t generate an error and you’ll be left scratching your head. Remember to omit it from all node references and use the XML class instance instead.

Element and Text Nodes, and the XMLList Class

As mentioned previously, element nodes are XML tags, and text enclosed in a pair of tags is a text node unto itself. Conveniently, accessing an element node allows you to work with the node as an object—such as when you want to copy or delete a node (both of which we’ll do later in this chapter)—but it also returns useful context-sensitive data for immediate use.

When the queried node contains additional element nodes, they are returned so that you can work with a subset of the larger XML object. This is handy for working only with information you really need, as we’ll see when working with individual menus in our XML-based navigation system project at the end of the chapter. When the queried node contains a text node, the text is returned as a String. This is convenient for populating variables or text fields with node content without first having to convert the data to a String.

In all cases, however, it’s important to understand the difference between the node and what’s returned when accessing the node. This is worth a few minutes of detailed focus, as it will save you time when you have to write code to parse XML for use at runtime. Let’s look at how to work with text nodes first.

Text nodes and strings

The following example is found in the text_nodes_and_strings.fla source file and begins with the explicit creation of an XML instance called las3Data, in lines 1 through 15.

1    var las3Data:XML = <book>
2                          <publisher name="O'Reilly"/>
3                          <title>Learning ActionScript 3.0</title>
4                          <subject>ActionScript</subject>
5                          <authors>
6                             <author>
7                                <firstname>Rich</firstname>
8                                <lastname>Shupe</lastname>
9                          </author>
10                         <author>
11                            <firstname>Zevan</firstname>
12                            <lastname>Rosser</lastname>
13                         </author>
14                         </authors>
15                       </book>;
16
17    trace("- name of title node:", las3Data.title.name());
18    //- name of title node: title
19
20    trace("- data returned from title node:", las3Data.title);
21    //- data returned from title node: Learning ActionScript 3.0
22
23    var txtFld:TextField = new TextField();
24    txtFld.width = 300;
25    txtFld.text = las3Data.title;
26    addChild(txtFld);

Note

Throughout the chapter, ActionScript comments are used to show trace() output to simplify our discussion, and have been included in the file so you can compare your own results.

The trace() statements will often use a comma to separate items output to a single line, but a newline and plus (+) operator for multiple line output. This is purely aesthetic. The comma adds a space between items in a trace and, when combined with a carriage return, it causes the first line of multiline input to be formatted with a leading space. Because white space plays a part in XML, we didn’t want this to be a distraction, so we concatenated multiline items to avoid this cosmetic issue.

Now take a look at line 17. This illustrates a simple example of working with a node object by using the name() method to return the node’s name. The rest of the segment demonstrates working with data returned when querying a node. Line 20 traces the value to the Output panel, and lines 23 through 26 show a text field populated with the String returned.

Line 28 in the following code block further demonstrates the difference between these two concepts by showing that the title node, itself, is still an element node. Like an element node, a text node is also XML and can be accessed using the text() method shown in line 31. This, too, will return a String for your convenience, but line 34 shows that the node itself is a text node.

27    //node kind
28    trace("- kind of title node:", las3Data.title.nodeKind());
29    //- kind of title node: element
30
31    trace("- text node child of title:", las3Data.title.text());
32    //- text node child of title: Learning ActionScript 3.0
33
34    trace("- kind of text node:", las3Data.title.text().nodeKind());
35    //- kind of text node: text

Note

It’s not uncommon for ActionScript to return separate but related data that may be useful to you. For example, we discussed in Chapter 2 that the push() method of the Array class adds an item to an array. However, it also returns the new length of the array. The following snippet shows the most common use of the push() method—simply adding an item (banana) to an array (fruit). However, in the third line of this snippet, you’ll see another push() that’s inside a trace() statement. This displays a 4 in the Output panel, which is the new length of the array.

var fruit:Array = ["apple", "orange"];
fruit.push("banana");
trace(fruit.push("grape"));
//4

You don’t have to use the returned information, as seen in the second line of the example, but it’s there if you want it.

When your goal is to work with text, you are most likely to use the String data returned when querying a text node or an element node that contains text. However, it’s occasionally convenient to work with the text node instead because it’s still XML. For example, you can use XML syntax to collect all occurrences of a particular node in an XML object. This is accomplished with the XMLList class, the real heart of E4X. We’ll demonstrate the power of this class using element nodes.

Element nodes and the power of XMLList

An XMLList instance is a list of all occurrences of a node at the same hierarchical level in the XML object—even if there is only one of those nodes. Let’s start right away by pointing out that all XML nodes are of the XMLList data type. The following example is found in the element_nodes_and_xmllist.fla source file, and lines 1 through 15 again create a basic instance of the XML class. Lines 17 and 20 show that both element and text nodes are typed as XMLList.

1    var las3Data:XML = <book>
2                          <publisher name="O'Reilly"/>
3                          <title>Learning ActionScript 3.0</title>
4                          <subject>ActionScript</subject>
5                          <authors>
6                             <author>
7                                <firstname>Rich</firstname>
8                                <lastname>Shupe</lastname>
9                             </author>
10                            <author>
11                               <firstname>Zevan</firstname>
12                               <lastname>Rosser</lastname>
13                            </author>
14                         </authors>
15                      </book>;
16
17    trace("- XMLList element node:", las3Data.title is XMLList);
18    //- XMLList element node: true
19
20    trace("- XMLList text node:", las3Data.title.text() is XMLList);
21    //- XMLList text node: true

Now let’s take a closer look at how wonderful XMLList can be. First, you can isolate a segment of your XML object to make it easier to parse. Lines 23 and 24 show that you can place a subset of las3Data into an XMLList instance (<authors>, in this case).

22    //isolation of XML subset
23    var authors:XMLList = las3Data.authors;
24    trace("- authors:
" + authors);
25    /*- authors:
26    <authors>
27      <author>
28        <firstname>Rich</firstname>
29        <lastname>Shupe</lastname>
30    </author>
31    <author>
32        <firstname>Zevan</firstname>
33        <lastname>Rosser</lastname>
34        </author>
35    </authors>
36    */

But that’s just the beginning. What XMLList excels at is pulling together all occurrences of a node at the same hierarchical level. We’ll first show this at work by collecting both <author> nodes within the <authors> node.

37    //collecting siblings into an XMLList instance
38    trace("- author:
" + las3Data.authors.author);
39    /*- author:
40    <author>
41       <firstname>Rich</firstname>
42       <lastname>Shupe</lastname>
43    </author>
44    <author>
45       <firstname>Zevan</firstname>
46       <lastname>Rosser</lastname>
47    </author>
48    */

Note that line 38 references simply <author>, but two of these nodes are returned, evidenced by the trace() output. This is XMLList collecting the relevant nodes for you. If an additional <author> node appeared on another level, perhaps as a parent, child, or grandchild, it would not be included.

Note

Using the same name for nodes that are not siblings with the same purpose is bad XML design because of the possible confusion this structure may cause. If something akin to this is required (such as listing primary authors at one level and contributing authors at another, to follow our example), it’s best to use separate names for each node purpose (such as <primary> and <contributor>).

Collecting siblings for you is great, because you don’t have to loop through the siblings and build an array yourself. Using XMLList, for example, you could automatically generate a list of all sibling news items from an XML news feed. What’s really great, however, is that XMLList will traverse nodes for you to collect all nodes at the same hierarchical level. Continuing the news feed example, you could collect all headline nodes from each parent news node.

Using our book example, line 50 of the following script collects both <firstname> nodes, even though they are in separate <author> parent nodes. Furthermore, you can use bracket syntax to retrieve specific data from the list. For example, line 56 retrieves only the first <firstname> node.

Note

Although you can use bracket syntax, an XMLList instance is not an array. One of the most common mistakes developers make when working with XMLList results is using the array length property to see how many items are in the list. This will not work, either failing silently or returning a null object reference error depending on usage. The XMLList equivalent to this property exists as a method: length().

49    //collecting nodes at the same level into an XMLList instance
50    trace("- firstname:
" + las3Data.authors.author.firstname);
51    /*- firstname:
52    <firstname>Rich</firstname>
53    <firstname>Zevan</firstname
54    */
55
56    trace("- firstname[0]:
", las3Data.authors.author.firstname[0]);
57    //- firstname[0]: Rich

Using the descendant accessor operator and wildcards

Two more powerful tools make traversing XML and XMLList instances easier: the descendant accessor operator and the wildcard. The descendant accessor operator is a pair of dots (..) and allows you to query a node or nodes in any hierarchical level at or below the specified node, without using a complete path to that element. This is convenient for retrieving deeply nested nodes, as long as no other nodes bear the same name. (Again, this would probably be bad XML design, and all nodes of the same name would be collected.) The following is an alternate way to retrieve only the <firstname> nodes that reside within separate parent nodes, anywhere in the las3Data instance.

58    //descendant accessor operator
59    trace("- ..firstname:
" + las3Data..firstname);
60    /*- ..firstname:
61    <firstname>Rich</firstname>
62    <firstname>Zevan</firstname>
63    */

The wildcard is an asterisk (*) that allows you to include every node at one hierarchical level. The following will retrieve both <firstname> and <lastname> nodes, even traversing multiple parent nodes.

64    //wildcard operator
65    trace("- author.*:
" + las3Data.authors.author.*);
66    /*- author.*:
67    <firstname>Rich</firstname>
68    <lastname>Shupe</lastname>
69    <firstname>Zevan</firstname>
70    <lastname>Rosser</lastname>
71    */

Using Attributes

XML element nodes can include attributes the same way HTML nodes can. For example, an HTML image tag might contain a width attribute, and the <publisher> node of our las3Data XML object contains an attribute called name with “O’Reilly” as its content. To access an attribute by name, you first treat it like a child of the node in which it resides, and then precede its name with an at symbol (@). The following code is found in the xml_attributes.fla source file and contains a simplified adaptation of our las3Data example.

1    var las3Data:XML = <book>
2                          <publisher name="O'Reilly" state="CA"/>
3                       </book>;
4
5    trace("- dot syntax:", las3Data.publisher.@name);
6    //- dot syntax: O'Reilly;

Because an element node can contain multiple attributes, you can also access all attributes as an XMLList. You can create the list using the attributes() method (line 8) or a wildcard (line 11). And, as the result of both queries is an XMLList, you can again use array syntax to select one attribute by index number. (This syntax is shown in line 11, though only one attribute exists in this simple example).

7    //collecting attributes using XMLList
8    trace("- attributes():", las3Data.publisher.attributes());
9    //- attribute(): O'ReillyCA
10
11    trace("- @*:", las3Data.publisher.@*[0]);
12    //- @*: O'Reilly

Note

The output from the trace() statement in line 8 reads “O’ReillyCA” but the data is returned as an XMLList. You can still work with a single item, as shown in line 11.

Collecting attributes using one of these methods is particularly important when you have to work with XML that uses node names that aren’t legal in ActionScript. The most common example is a node name that contains a dash. The following example creates a simple XML instance in lines 14 through 16 and then repeats two ways to retrieve an attribute: by name and by the attributes() method. The first approach (line 18) would generate an error if uncommented. The second (line 21) will work correctly.

13    //querying attribute names illegal in AS3
14    var example:XML = <file creation-date="20071101">
15                         <modified-date>20100829</modified-date>
16                      </file>;
17
18    //trace("- bad attribute name", example.@creation-date);
19    //causes an error
20
21    trace("- attribute(name):", example.attribute("creation-date"));
22    //- attribute(name): 20071101

Coping with element node names that are incompatible with ActionScript

Finally, on a related note, using a method to retrieve all nodes of a specified type can also be used to retrieve element nodes with illegal names. This is seen in line 24 of the following code, which has been appended to the xml_attributes.fla source file for side-by-side comparison.

Note, however, that there is an inconsistency here. The attributes() (plural) method collects all attributes in a given scope, while the attribute(), (singular) method is used to query a single attribute. The elements() (plural) method, however, is used for both purposes.

1    //querying node names illegal in AS3
2    trace("- elements(name):", example.elements("modified-date"));
3    //- elements(name): 20100829

Finding Elements by Content

Another convenient feature of E4X is the ability to use conditionals when querying a node. For example, instead of walking through the contents of an XML document with a loop and a formal if structure, you can simply start with the conditional directly inside the dot-syntax address, and create an XMLList automatically. Consider the following information, which can be seen in find_by_content.fla:

1    var phones:XML = <phones>
2                        <model stock="no">
3                           <name>T2</name>
4                           <price>89.00</price>
5                        </model>
6                        <model stock="no">
7                           <name>T1000</name>
8                           <price>99.00</price>
9                        </model>
10                        <model stock="yes">
11                           <name>T3</name>
12                           <price>199.00</price>
13                        </model>
14                     </phones>;

Line 15 checks to see if any phone model has a price that is below $100. Only the first two models are listed because they are the only models with a price less than 100.

15    trace("< 100:
" + phones.model.(price < 100));
16    /*
17    <model stock="no">
18        <name>T2</name>
19        <price>89.00</price>
20    </model>
21    <model stock="no">
22    <name>T1000</name>
23        <price>99.00</price>
24        </model>
25    */

Line 26 looks for any element one level down that has an attribute named stock with a value of “yes.” Both implicit and explicit casting are also represented here, with the same results of both instructions listed only once.

26    trace("in stock:
" + phones.*.(@stock == "yes"));
27    /*
28    <model stock="yes">
29        <name>T3</name>
30        <price>199.00</price>
31    </model>
32    */

A limitation when filtering by attribute

Another important thing to know about the aforementioned @ versus attribute() choice is that filtering content using @ works only if all of the queried elements have the attribute. Note, in the following example, found in the xml_attributes_filtering_issue.fla source file, that one of the element nodes is missing the price attribute. Matching nodes using @price will generate an error, but using attribute("price") will not.

1    var catalog:XML = <stock>
2                         <product name="one" price="100" />
3                         <product name="two" price="200" />
4                         <product name="three" />
5                         <product name="four" price="100" />
6                      </stock>;
7
8    //trace(catalog.product.(@price == 100));
9    //error
10
11    trace(catalog.product.(attribute("price") == 100));

Writing XML

You’ve already seen how to write XML when creating an instance of the XML class, but you may also have to write to the instance over time. For example, you may need to add to XML based on user input or as data changes. The majority of techniques for adding content to XML mirror the process of reading the data, except this time you’re assigning information to a node rather than retrieving it.

In this section, you’ll re-create the data used throughout the Reading XML section of this chapter. For simplicity, we’ll create element nodes, text nodes, and attributes in one example, and build an XML instance as if we were writing it over time. In a real scenario you would not assemble XML in multiple steps in the same script. However, assuming the premise that we’re writing the object in stages will allow us to demonstrate the most common XML writing methods.

Because we’re intentionally writing the XML out of order to demonstrate methods like insertChildBefore() that will alter the order of nodes, we’ll show the progress of the XML as we go. The code that follows can be found in the write_xml.fla source file. For clarity, only the last trace() statement is used in the source file, to show the final XML content, but you can uncomment any trace along the way to see interim results.

To begin, we must have an XML instance and a root node, so line 1 creates both. Note that, when adding element nodes without content, you must specify a self-closing tag so the XML remains well formed. As soon as you add content to a self-closing node, ActionScript will replace it with balanced open and closing tags. For example, we’re initially adding <book /> as the root node but, after adding the next element node, the root node will become <book></book>.

Line 2 demonstrates the simplest technique for creating both an element node and a text node. When assigning a value to a node, if the node does not already exist, it will be created. If you assign another element node to the new node, a nested element will be created. If you assign a String to the new node, a text node will be created. Line 2, therefore, creates an element node called <title> and a text node that contains “Learning ActionScript 3.0.” The result appears in lines 5 through 7.

1    var las3Data:XML = <book />
2    las3Data.title = "Learning ActionScript 3.0";
3    trace(las3Data);
4    /*
5    <book>
6        <title>Learning ActionScript 3.0</title>
7    </book>
8    */

We started with the <title> node intentionally to demonstrate the next method. The prependChild() method will add a node to the beginning of the XML object specified. In this case, line 9 creates the <publisher> element node and positions it before the <title> node so it’s the first node of las3Data. Line 10 demonstrates the creation of an attribute node adding the name attribute to the publisher element node just created. The cumulative result appears in lines 13 through 16.

9    las3Data.prependChild(<publisher />);
10    las3Data.publisher.@name = "O'Reilly";
11    trace(las3Data);
12    /*
13    <book>
14        <publisher name="O'Reilly"/>
15        <title>Learning ActionScript 3.0</title>
16    </book>
17    */

The opposite of prependChild(), appendChild() adds a node to the end of an XML object. Therefore, line 18 adds the <authors> node after the <title> node. Up to this point, we’ve only added objects to the XML instance, which is equivalent to the root node. However, you can also use appendChild() to add a child to another node. Line 19 adds the first <author> node as a child of <authors>. Lines 20 and 21 again demonstrate the simultaneous creation of element and text nodes, adding <firstname> and <lastname> nodes and assign a String to each. The cumulative result appears in lines 24 through 33.

18    las3Data.appendChild(<authors />);
19    las3Data.authors.appendChild(<author />);
20    las3Data.authors.author.firstname = "Zevan";
21    las3Data.authors.author.lastname = "Rosser";
22    trace(las3Data);
23    /*
24    <book>
25        <publisher name="O'Reilly"/>
26        <title>Learning ActionScript 3.0</title>
27        <authors>
28            <author>
29                <firstname>Zevan</firstname>
30                <lastname>Rosser</lastname>
31            </author>
32        </authors>
33    </book>
34    */

Note

It’s not possible, using this method, to add more than one node with the same name to an XMLList. For example, we have to add another author to the <authors> node, but if we repeat lines 19 through 21, we’ll receive this error:

Error #1089: Assignment to lists with more than one item is not supported.

Instead, we must copy the first author node and change its contents. We’ll show you how to do that in just a moment.

Line 35 demonstrates the insertChildAfter() method, the first of a pair that allows you to provide an existing node as a reference point for where the new node should be added. The first argument of the method is the existing node, and the second argument is the node you want to create. In this case, the <subject> node is being inserted after the <title> node. Line 36 adds a text node to <subject>, and the cumulative result is seen in lines 39 through 49.

35    las3Data.insertChildAfter(las3Data.title, <subject />);
36    las3Data.subject = "ActionScript";
37    trace(las3Data);
38    /*
39    <book>
40        <publisher name="O'Reilly"/>
41        <title>Learning ActionScript 3.0</title>
42        <subject>ActionScript</subject>
43        <authors>
44            <author>
45                <firstname>Zevan</firstname>
46                <lastname>Rosser</lastname>
47            </author>
48        </authors>
49    </book>
50    */

The next block of code demonstrates insertChildBefore(), the other method that uses an existing node as a reference. However, it also demonstrates how to copy a node and change its values. Line 51 uses the copy() method to copy the previously created <author> node, and lines 52 and 53 change the text nodes in <firstname> and <lastname>. After the edits, the new node is inserted before the existing <author> node to match the order of the original data we’re trying to recreate. Using the copy() method is a real timesaver when tags with many children must be reproduced over and over again with few changes.

The final result is shown in lines 57 through 71, and matches the original las3Data object to achieve our goal.

51    var firstAuthor:XMLList = las3Data.authors.author.copy();
52    firstAuthor[0].firstname = "Rich";
53    firstAuthor[0].lastname = "Shupe";
54    las3Data.authors.insertChildBefore(las3Data.authors.author, firstAuthor);
55    trace(las3Data);
56    /*
57    <book>
58        <publisher name="O'Reilly"/>
59        <title>Learning ActionScript 3.0</title>
60        <subject>ActionScript</subject>
61        <authors>
62            <author>
63                <firstname>Rich</firstname>
64                <lastname>Shupe</lastname>
65            </author>
66            <author>
67                <firstname>Zevan</firstname>
68                <lastname>Rosser</lastname>
69            </author>
70        </authors>
71    </book>
72    */

Deleting XML

We’ve placed deleting XML elements in a separate section because you may delete elements when reading or writing XML. When parsing XML, you’re likely to ignore small sections of unwanted content, but deleting large segments of unneeded material can sometimes simplify your task. When writing XML, you may find the need to delete an element added in error or that is no longer needed.

To delete something, simply use the delete operator on the desired item. Here are a few examples showing how to delete attributes, element nodes, and text nodes from a simplified version of our ongoing las3Data example. This code can be seen in the delete_xml.fla source file. Line 7 deletes an attribute, line 8 deletes an element node and all its children (which deletes the text node therein), and line 9 deletes only a text node, leaving the parent element node intact. The final result is seen in lines 12 through 15.

1    var las3Data:XML = <book>
2                          <publisher name="O'Reilly" />
3                          <title>Learning ActionScript 3.0</title>
4                          <subject>ActionScript</subject>
5                       </book>;
6
7    delete las3Data.publisher.@name;
8    delete las3Data.subject;
9    delete las3Data.title.text()[0];
10    trace(las3Data);
11    /*
12    <book>
13        <publisher/>
14        <title/>
15    </book>
16    */

Note that the delete operator won’t work with text(), elements(), attributes(), children(), or descendents() to delete all of the nodes returned by these methods. Rather, you must specify which item within the XMLList returned that you want to delete. This can be counterintuitive if you just want to delete a single text node, as in line 9.

Loading External XML Documents

Often when you work with XML, you’re using data that’s being retrieved from an external source. Even when you need only a local data source, however, it’s almost always easier to work with an external XML document because it’s easier to edit the XML as you project evolves.

We’ll use the LoadURL class developed in Chapter 13 to load data for our XML navigation system at the end of this chapter, but right now we’d like to stress the basic syntax of loading XML. The code in this section can be found in the load_xml.fla source file.

Before we get started with the syntax, let’s create a very simple XML document called toLoad.xml. It contains the mandatory root node, one element node called <stuff>, and one text node with the string, “XML loaded!”

<main>
    <stuff>XML loaded!</stuff>
</main>

With that in hand, let’s start our script by creating a text field to display our results (lines 1 through 5). This is a handy alternative to tracing loaded content because it’s easier to test in a browser. Next, create a URLRequest instance for the XML file (line 7), and a URLLoader instance to load the document (line 9). Then create two event listeners to react to a possible I/O error (lines 10 and 11), and the completion of the loading process (lines 12 and 13). The onIOError() function in lines 16 through 18 places any error text (if an I/O error occurs) into a text field, and we’ll discuss the onComplete() function after the first code block. Next, the XML document is loaded in line 14.

1    var txtFld:TextField = new TextField();
2    txtFld.width = 500;
3    txtFld.height = 350;
4    txtFld.multiline = txtFld.wordWrap = true;
5    addChild(txtFld);
6
7    var req:URLRequest = new URLRequest("toLoad.xml");
8
9    var urlLoader:URLLoader = new URLLoader();
10    urlLoader.addEventListener(IOErrorEvent.IO_ERROR,
11                               onIOError, false, 0, true);
12    urlLoader.addEventListener(Event.COMPLETE,
13                               onComplete, false, 0, true);
14    urlLoader.load(req);
15
16    function onIOError(evt:IOErrorEvent):void {
17        txtFld.text = "XML load error.
" + evt.text;
18    }

The onComplete() function (lines 19 through 28) is triggered when the XML file has completely loaded. The function first removes the listeners because the XML document was both found and loaded. It then uses a try..catch block to create an XML instance from the loaded data, and places the <stuff> node into a text field. If unsuccessful, an error message is placed into the same field, often allowing you to locate something that may cause the XML to be malformed.

19    function onComplete(evt:Event):void {
20        urlLoader.removeEventListener(IOErrorEvent.IO_ERROR, onIOError);
21        urlLoader.removeEventListener(Event.COMPLETE, onComplete);
22        try {
23            var loadedXML:XML = new XML(evt.target.data);
24            txtFld.text = loadedXML.stuff;
25        } catch (err:Error) {
26            txtFld.text = "XML parse error:
" + err.message;
27        }
28    }

Before we demonstrate populating menus with XML in our navigation system project, let’s look at sending data to a server and loading the result.

Sending to and Loading from a Server

Another frequent use of XML data is for transmission to and from a server. XML is often the data format used by news feeds (RSS, ATOM), Web services, and database output. While some of these uses require only loading information, other tasks, including application logins, game high score submission, and so on, also require sending data. In this chapter, we’ll cover the basic send and load method of communicating with a server.

Send and Load

The send-and-load approach is a form of traditional server communication, be it a browser retrieving an HTML file or a user submitting data via a form. Essentially, the client sends data to the server and waits for a response. The server processes the incoming information, formulates a reply, and sends information back to the client.

For simplicity, this example sends a short XML object to a PHP script, which then writes data to a text file on the server and sends back a short reply. Writing a text file on the server may not be the most common use of XML submissions, but it’s basic enough to illustrate in this context. This example uses two files: send_load_xml.fla and save_xml.php. Let’s look at the FLA file first.

The client source

This example is nearly identical to the load_xml.fla source file discussed in the preceding Loading External XML Documents section. All we need to do is substitute the following eight lines of code for line 7 of that example. These lines both create XML to send, and customize the URLRequest instance to send data, as well as load it.

Lines 7 through 9 create the XML object to send to the server. Like ActionScript, our PHP script does not require the XML declaration tag in line 7 but, for maximum flexibility, it’s not a bad idea to prepend this to any XML you send to a server. You may find that it’s required in a future configuration of your project and, since it makes no difference in ActionScript, there’s no good reason not to include it.

Lines 11 through 14 create a URLRequest instance for submitting the data. Note that you’ll need to put the correct path to your server in line 11. As discussed in Chapter 13, line 12 assigns the outgoing XML to the data property of the request, and lines 13 and 14 specify “text/xml” as the contentType, and POST as the method, of the request object, respectively.

7    var str:String = "<?xml version='1.0' encoding='utf-8'?>";
8    str += "<value>Sent from ActionScript</value>";
9    var xmlToSend:XML = new XML(str);
10
11    var req:URLRequest = new URLRequest("save_xml.php");
12    req.data = xmlToSend;
13    req.contentType = "text/xml";
14    req.method = URLRequestMethod.POST;

The server source

The next code block is the server-side PHP script. This is the server destination of your simple XML data and, as specified in line 11 of the ActionScript code, should be called save_xml.php. The script first checks to be sure POST data has been received (line 3), and then populates the $data variable with that data (line 4). In lines 6 through 8, it creates and opens for writing a file called data.txt, writes the data to the file, and then closes the open file instance. Lastly, it checks to make sure the file was written successfully and sends a simple XML object back to ActionScript.

If successful, you’ll see the message “File saved.” in the text field. If not, you’ll see “Server unable to create file.” There may be a permissions issue on the server preventing write access to the directory in which the PHP script has been placed, for example, or another error preventing file creation.

1    <?php
2
3    if (isset($GLOBALS["HTTP_RAW_POST_DATA"])){
4        $data = $GLOBALS["HTTP_RAW_POST_DATA"];
5
6        $file = fopen("data.txt", "wb");
7        fwrite($file, $data);
8        fclose($file);
9
10        if (!$file) {
11            echo("<stuff>Server unable to create file.</stuff>");
12        } else {
13            echo("<stuff>File saved.</stuff>");
14        }
15    }
16
17    ?>

An XML-Based Navigation System

If you haven’t done so already, you may want to read the last exercise in Chapter 6, before continuing with this project. Chapter 6 discusses object-oriented programming and uses a simplified version of this exercise without XML, populating the menus with an array. By comparing this exercise with the more basic version in Chapter 6, you can see how incorporating XML changes the system. The result of this exercise will be a five-button navigation bar with submenus, the labels and partial functionality of which are populated through XML.

The Directory Structure and Source Files

Before looking at the ActionScript for this exercise, we need to explain a couple of quick things about the project’s directory structure and source files. We’ve tried to use several of the key topics that we’ve learned throughout the book to approximate an average use of an XML-driven menu system. Highlights include: object-oriented design, embedded fonts, text formatting, TweenLite animations, loading external assets, drawing with vectors, filter effects, and, of course, parsing XML.

The project directory, nav_bar_xml, includes the primary FLA file, LAS3Lab.fla, and document class, LAS3Main.as. The exercise also uses classes from the learningactionscript3 package (in the com directory) that we’ve been building throughout the book:

com.learningactionscript3.loading.LoadURL

Created in Chapter 13, this class can load text from a URL, and includes error checking, making it convenient for loading XML.

com.learningactionscript3.ui.NavigationBarXML

This class is the backbone of the system and is a new version of the NavigationBar class used in Chapter 6. It replaces that chapter’s array data with XML, adds submenus, and puts the whole system to work by adding a loader that the menu items can target for loading SWFs and images.

import com.learningactionscript3.ui.MenuButtonMain

This simple class essentially provides a text field and text formatting for the MenuButtonMain movie clip in the LAS3Main.fla library.

import com.learningactionscript3.ui.MenuButtonSub

This class dynamically creates submenu buttons that will be clicked to load visual content at runtime.

Note

The navigation menu system in this chapter is an enhancement of the version in Chapter 6. We elected, however, not to replace the older classes to allow you to compare the classes and see the evolution of the code. Instead, we placed the new and revised classes in the com.learningactionscript3.ui package.

In addition, the exercise uses classes that are developed by others and are not part of any default ActionScript 3.0 distribution, including a few classes from TweenLite (the ActionScript tweening platform discussed in Chapter 7), and a modified version of Adobe’s SafeLoader class (the class designed to load SWFs that contain TLF instances without error, discussed in Chapter 13).

The last parts of the exercise directory are the XML data and content for loading. The XML file that populates the menus is called nav.xml and is found inside a directory called data, and the content ready for loading, and specified in paths in the XML data, is found inside the projects directory.

The Library of the FLA

The main FLA file requires three symbols in its library:

ArialBold
ArialRegular

These are embedded font symbols containing bold and plain versions of the Arial font with linkage class names of ArialBold and ArialRegular, respectively. Although embedded fonts in a SWF ensure that viewers don’t need to have the fonts installed in their computer, this is not true of editing an FLA. If you prefer, you can substitute your own fonts and then either use the same linkage class names or update the code accordingly.

MenuButtonMain

This is a movie clip that looks like a tab-shaped button. The class of the same name provides the text field and text formatting needed to display the tab’s text label.

The XML and Classes

Now we’ll being looking at the code behind the project. We’ll start by showing you an excerpt of the XML data file, and then discuss each of the classes that you’ll write. We’ll also explain the usage of related, third-party classes.

XML

You can look at the XML document any time, but the following excerpt is representative of the data:

<nav>
    <menus>
        <button label="MOTION">
            <project label="flocking" path="projects/motion/worm.swf">
              Flocking with Zeno's paradox
            </project>
            <project label="point at" path="projects/motion/point.swf">
              Pointing at the mouse position
            </project>
        </button>
    </menus>
</nav>

The root node, <nav>, has a child node, <menus>, which contains the data for all menus. Each menu is delineated by a <button> node, which corresponds to the main menu button. This node has a label attribute, which holds the text used for the main menu button text label.

Within each <button> node are multiple child nodes called <project> (two are shown in the excerpt for brevity). Collectively, these make up the submenu for each menu. Each <project> node corresponds with one button in the submenu, and has an attribute called label. This is used to populate the text label of the submenu button. It also has a path attribute, used to load the corresponding content, and a text node, which we’ll use in Chapter 15 to display a brief blurb about the loaded asset.

LAS3Main (document class)

The document class is pretty straightforward. It loads the XML data, creates a SafeLoader for loading content and a mask for masking the content to an area below the menus, and initializes the menus.

Lines 1 through 10 declare the package and import required classes, including SafeLoader, and two custom classes, CustomURLLoader and NavigationBarXML. Line 12 declares the class, including extending MovieClip so it can easily be used as a document class. Finally, lines 14 through 16 create three private properties for the CustomURLLoder, XML, and SafeLoader instances, respectively.

1    package {
2
3        import flash.display.Graphics;
4        import flash.display.MovieClip;
5        import flash.events.Event;
6        import flash.filters.DropShadowFilter;
7        import flash.net.URLRequest;
8        import fl.display.SafeLoader;
9        import com.learningactionscript3.loading.CustomURLLoader;
10        import com.learningactionscript3.ui.NavigationBarXML;
11
12        public class LAS3Main extends MovieClip {
13
14            private var _menuLdr:CustomURLLoader;
15            private var _xml:XML;
16            private var _loader:SafeLoader;

The class constructor occupies lines 18 through 22 and creates an instance of the CustomURLLoader class to load the external data. Discussed in Chapter 13, this class does all the loading work for us and dispatches an Event.COMPLETE event when the loading is finished. Line 20 adds an event listener to trap this event and call onLoadXML().

Once the XML is loaded, the onLoadXML() method uses a try..catch block to attempt to parse the XML. It retrieves the data from the data property of the CustomURLLoader instance and tries to instantiate an XML object using that data (line 26). If successful, it calls the initLoader() and initMenus() methods (lines 27 and 28, respectively). If the data can’t be parsed as XML, it traces an error (lines 30 and 31).

17            //constructor and xml load
18            public function LAS3Main() {
19                _menuLdr = new LoadURL("data/nav.xml");
20                _menuLdr.addEventListener(Event.COMPLETE, onLoadXML,
21                                          false, 0, true);
22            }
23
24            private function onLoadXML(evt:Event):void {
25                try {
26                _xml = new XML(_menuLdr.data);
27                initLoader();
28                initMenus();
29            } catch (err:TypeError) {
30                trace("Can't parse loaded content as XML:",
31                    err.message);
32            }
33        }

The initLoader() method creates an instance of the SafeLoader class (line 36), positions it below the future location of the menus (line 37), and adds it to the display list (line 38). It also draws a 750 × 450 pixel movie clip (lines 40 through 44), adjusts its y position to 100, the same location as the SafeLoader instance (line 45), and uses it to mask the loaded content (line 46).

Note

Using a mask prevents loaded content from appearing outside the area dedicated for its display—something that frequently happens when assets follow the mouse, for example. If you want to see what the project looks like without a mask at any point, simply comment out line 46 when testing.

34        //loader and mask
35        private function initLoader():void {
36            _loader = new SafeLoader();
37            _loader.y = 100;
38            this.addChild(_loader);
39
40            var loaderMask:MovieClip = new MovieClip();
41            var g:Graphics = loaderMask.graphics;
42            g.beginFill(0x000000, 1);
43            g.drawRect(0, 0, 750, 450);
44            g.endFill();
45            loaderMask.y = 100;
46            _loader.mask = loaderMask;
47        }

Note

See Chapter 13 for information about a modification made to Adobe’s SafeLoader class.

The initMenus() method creates an instance of the NavigationBarXML class (lines 50 and 51) and adds it to the display list (line 52). In doing so, it passes the scope of the document class into the constructor, as well as the <menus> XMLList. This makes all of the menu XML data available to the class so it can create the necessary buttons. The method also creates a DropShadowFilter instance (line 54), sets its alpha to 25 percent, and adds it to the _navBar instance. This will give the entire menu system, including the submenus that appear interactively, a drop shadow.

48            //navigation menu bar
49            private function initMenus():void {
50                var _navBar:NavigationBarXML =
51                    new NavigationBarXML(this, _xml.menus);
52                this.addChild(_navBar);
53
54                var ds:DropShadowFilter = new DropShadowFilter();
55                ds.alpha = 0.25;
56                _navBar.filters = [ds];
57            }
58
59            public function get assetLoader():SafeLoader {
60                return _loader;
61            }
62        }
63    }

Finally, a getter is provided to return the SafeLoader instance when required. Despite not processing the information returned, a getter is used here instead of a public property because the value should be read-only. By contrast, we’ll use public properties later on in the MenuButtonSub class, to give you more experience with both approaches to controlling information access in classes. For more information, see the Encapsulation section in Chapter 6.

The NavigationBarXML class is the longest class in the project, and the real workhorse. Although its functionality isn’t particularly complex, a little more detail is warranted to cover some of its inner workings.

Lines 1 through 15 declare the package and import the necessary classes. In this case, note that three TweenLite classes that we haven’t discussed before are imported: TweenPlugin, ColorTransformPlugin, and VisiblePlugin. We’ll discuss those when we go over the constructor. Also, note that MenuButtonMain and MenuButtonSub are imported. It’s in this class that we’ll be using both button types.

Line 17 declares the class and extends MovieClip so the navigation bar instance inherits the accessible properties and methods of the MovieClip class. Lines 19 through 21 create three private properties, preventing access from outside the class. The first property (line 19) will hold an instance of the document class (remember that it extended MovieClip, as well) passed into the constructor during instantiation. This will allow us to get the SafeLoader instance when one of the menu buttons needs to load content.

The second property (line 20) will hold an XMLList of all the button data loaded in the document class—also passed into the constructor during instantiation. The last property (line 21) will contain the currently active submenu when the user rolls his or her mouse over a menu button. This will allow us to reference the same menu in another method when the user rolls the mouse away and we must hide the menu.

1    package com.learningactionscript3.ui {
2
3        import flash.display.Graphics;
4        import flash.display.MovieClip;
5        import flash.events.Event;
6        import flash.events.MouseEvent;
7        import flash.geom.Matrix;
8        import flash.net.URLRequest;
9        import flash.text.TextField;
10        import com.greensock.TweenLite;
11        import com.greensock.plugins.TweenPlugin;
12        import com.greensock.plugins.VisiblePlugin;
13        import com.greensock.plugins.ColorTransformPlugin;
14        import com.learningactionscript3.ui.MenuButtonMain;
15        import com.learningactionscript3.ui.MenuButtonSub;
16
17        public class NavigationBarXML extends MovieClip {
18
19        private var _app:MovieClip;
20        private var _navData:XMLList;
21        private var _subMenu:MovieClip;

The class constructor occupies lines 23 through 41. It accepts two arguments: the movie clip in which the navigation bar was instantiated (to allow us to get a reference to the SafeLoader instance), and the button data loaded from XML. This information is immediately stored in the aforementioned private properties, in lines 25 and 26, so we can use the references in multiple methods within the class.

Line 28 calls the addMenus() function, which builds the menu system and which we’ll discuss in just a moment. Lines 30 through 37 dynamically draw the thick black line that serves as the lower bound of the main menu tabs. This improves upon the original version of the menu system in Chapter 6, which used a symbol for this purpose, because the code can easily be altered without having to create new artwork in the FLA.

The last two lines in the constructor activate the TweenLite plugins. The TweenLite tweening package is kept staggeringly small by only integrating the bare animation essentials. It doesn’t skimp on bells and whistles, however, because it allows you to add individual features as needed by activating plugins. This project uses two TweenLite plugins: VisiblePlugin, which turns an asset invisible after a tween, and ColorTransformPlugin, which allows us to tween color values. The uses of both plugins will be explained in context. This is a one-time process. Once the plugins are activated, their features will be available throughout your SWF, and any SWFs loaded from the same domain.

22        //constructor
23        public function NavigationBarXML(app:MovieClip,
24                                     navData:XMLList) {
25            _app = app;
26            _navData = navData;
27
28            addMenus();
29
30            var line:MovieClip = new MovieClip();
31            var g:Graphics = line.graphics;
32            g.beginFill(0x000000);
33            g.drawRect(0, 0, _app.stage.stageWidth, 4);
34            g.endFill();
35            line.y = 100;
36            line.mouseEnabled = false;
37            this.addChild(line);
38
39            TweenPlugin.activate([VisiblePlugin]);
40            TweenPlugin.activate([ColorTransformPlugin]);
41        }

The addMenus() method is the workhorse of this class, and it’s responsible for parsing the XML data, instantiating each main menu button, creating their submenus, and instantiating all submenu buttons. Line 44 uses the XML length() method to determine how many main menu buttons are included in the XML. (The project source XML contains five menus.)

Note

As mentioned previously, length() is a method, in contrast to the Array property length that serves the same purpose. This can take a little getting used to.

The remainder of the function is inside a loop that iterates five times, once for every menu. Line 46 starts the process by excerpting only the XML relevant to the current menu. Lines 49 through 59 then initialize the main menu button. Lines 49 and 50 create a MenuButtonMain instance, passing the label attribute from the XML into the class constructor to create the button’s text label.

Line 51 positions the button horizontally, beginning at 20 pixels and then offsetting a distance equivalent to the buttons width and a 2-pixel space, for each button in the loop. As the button is 120 pixels wide, this means the first button is placed at an x position of 20 pixels (20 + 0 * (120 + 2)), the second at 142 (20 + 1 * (120 + 2)), and so on. Line 52 positions each button at a y coordinate of 75.

Lines 53 through 58 create two event listeners, one for the MOUSE_OVER event and another for the MOUSE_OUT event. When these events occur, they call the methods starting at lines 83 and 90, respectively. We’ll discuss these methods in a few minutes. Finally, line 59 adds each main menu button to the display list.

42        //building the menus
43        private function addMenus():void {
44            var mainButtonLength:uint = _navData.button.length();
45            for (var i:int; i < mainButtonLength; i++) {
46                var buttonXML:XML = _navData.button[i];
47
48                //main button
49                var mainBtn:MovieClip =
50                    new MenuButtonMain(buttonXML.@label);
51                mainBtn.x = 20 + i * (mainBtn.width + 2);
52                mainBtn.y = 75;
53                mainBtn.addEventListener(MouseEvent.MOUSE_OVER,
54                                         onMainBtnOver,
55                                         false, 0, true);
56                mainBtn.addEventListener(MouseEvent.MOUSE_OUT,
57                                         onMainBtnOut,
58                                         false, 0, true);
59                this.addChild(mainBtn);

Still within the primary loop that iterates once for each main menu button, lines 61 through 64 create the subMenu MovieClip instance to hold all submenu buttons. The submenu is added as a child to the main button (line 64), so their locations start out at the same point. In line 62, the submenu’s y coordinate is set to the bottom of the main button (using the button’s height plus a 2-pixel margin to account for the black line that will lay on top of the navigation bar).

Line 67 next determines the number of project nodes in the current menu. This will determine how many submenu buttons are required. Lines 68 through 79 make up a loop that iterates once for every submenu button. Line 69 parses the current project node from the parent menu XML data.

Note

Note the nested loop that results from the loop that begins at line 68 residing within the loop that begins at line 45. The outer loop increments through the five main menus, using i as its increment variable. The inner loop increments through the number of projects in each menu (which can vary) and uses j as its increment variable.

Each time through the outer loop, line 46 creates a subset of XML that only pertains to the current menu, beginning with the main menu button:

var buttonXML:XML = _navData.button[i];

Then each time through the inner loop, each project node from this subset is parsed in line 69, resulting in each submenu button:

var projectNode:XML = buttonXML.project[j];

Thereafter, any XML information can be parsed from the projectNode variable.

Lines 70 and 71 create an instance of the MenuButtonSub class, passing the label attribute of the <project> node into the constructor to serve as the button’s text label. Line 72 sets the button’s public property projectPath to the path attribute of the project, and Line 73 sets the button’s public property projectDescription to the text within the <project> node.

Lines 75 through 77 add an event listener for the CLICK event so the submenu button will load the asset at the path associated with the button. Note that no other mouse event listeners are created. The ROLL_OVER and ROLL_OUT behavior of the button is cosmetic and is not customizable, so we’ll build it in the MenuButtonSub class.

The last line of the loop, line 78, adds the button to the submenu.

60                //sub menu
61                var subMenu:MovieClip = new MovieClip();
62                subMenu.y = mainBtn.height + 2;
63                subMenu.visible = false;
64                mainBtn.addChild(subMenu);
65
66                //sub buttons
67                var subButtonLength:uint = buttonXML.project.length();
68                for (var j:int = 0; j < subButtonLength; j++) {
69                    var projectNode:XML = buttonXML.project[j];
70                    var subButton:MovieClip =
71                        new MenuButtonSub(projectNode.@label);
72                    subButton.projectPath = projectNode.@path;
73                    subButton.projectDescription = projectNode;
74                    subButton.y = ((subButton.height) * j);
75                    subButton.addEventListener(MouseEvent.CLICK,
76                                               onSubBtnClick,
77                                               false, 0, true);
78                    subMenu.addChild(subButton);
79                }
80            }
81        }

Called each time the main menu button is rolled over, the onMainBtnOver() method shows the submenu. Line 84 first determines which button was rolled over by checking the currentTarget property of the event. Next, the button must show its submenu. Because the main menu button contains both a sprite background and a text field (explained in the next section), line 85 references the submenu by querying the third child of the button. Line 86 sets the visibility of the menu to true, and then line 87 uses TweenLite to fade from an alpha of 0 (transparent) to 1 (opaque), in one-quarter of a second.

Note

If the contents of the button were less uniform, you might prefer to set the name property of the submenu in the creation process (perhaps after line 64 in this class) and then us the getChildByName() method to retrieve a reference to the submenu.

Rolling off the main menu button calls the onMainBtnOut() method so we can again hide the menu. The first line of the method uses the relatedObject property of mouse events to determine if the mouse has rolled onto anything but a submenu button. We don’t want to hide the menu if one of its buttons is in use. If that’s not the case, the submenu’s visible property is set to true so you can see the submenu when it fades in. TweenLite is used to fade the submenu up to an alpha of 1 over one-quarter second.

Also, the visibility of the menu is set to false using TweenLite’s VisiblePlugin. When the visible property of the TweenLite object is set to false, this plugin automatically sets the visibility of the target to false after the tween is complete. In other words, the menu fades out and then becomes invisible. This is vital to the success of the menu because we can’t rely solely on alpha to hide our menus.

Despite being transparent, display objects with an alpha value of 0 can still receive mouse events. If we just faded the submenu out, rolling the mouse over its prior location would cause it to fade back into view without ever going near its main menu button. Instead, the menus must be invisible because invisible assets won’t respond to mouse events. So, the menus must start as invisible, become visible but tween their alpha property from 0 to 1 when rolled over, tween their alpha back to 0 and become invisible again upon mouse out.

Note

Hiding and revealing the submenus could be simplified by just popping the menus in and out when the value of their visible property changes. But TweenLite makes this so easy that only three extra lines of code are required—line 87 does the work and lines 12 and 39 enable the VisiblePlugin. (Line 11 is still required to support the ColorTransformPlugin, and line 92 will still be required in another form.)

If you prefer that the submenus pop in and out (perhaps because the quarter-second tween up and down makes the system feel less responsive), that’s easy to change. First comment out lines 87 and 92 and then add the following after line 92 to hide the menu:

_subMenu.visible = false;

(You may also comment out line 12 and 39, but their effect on the project will be negligible. To remove all alpha tweening of the submenus entirely, you can remove lines 12, 39, 87, and the original line 92, but they will not hinder performance if the appropriate lines remain commented, and this allows you restore the functionality later if you change your mind.)

82        //menu button mouse roll behavior: appearance
83        private function onMainBtnOver(evt:MouseEvent):void {
84            var mainBtn:MovieClip = MovieClip(evt.currentTarget);
85            _subMenu = mainBtn.getChildAt(2);
86            _subMenu.visible = true;
87            TweenLite.to(_subMenu, 0.25, {alpha:1});
88        }
89
90        private function onMainBtnOut(evt:MouseEvent):void {
91            if (!(evt.relatedObject is MenuButtonSub)) {
92                TweenLite.to(_subMenu, 0.25, {alpha:0, visible:false});
93            }
94        }

The last method in the class is called when a submenu button is clicked. Line 97 determines which button was clicked and retrieves the values from its public properties projectPath and projectDescription. The path is used to load the content associated with that button, and the description (included here only as an example) might be used to show information about the asset in a caption field, if you thought it necessary.

Line 100 or 101 will unload any content from the SafeLoader instance (retrieved from the document class assetLoader getter), depending on which platform you’re targeting. As discussed in Chapter 13, unloadAndStop() is preferable because it closes all sound and video streams, stops all timers, and removes all relevant listeners, so the asset can be properly unloaded. This method, however, requires Flash Player 10 or later. If you must target Flash Player 9, you must use the unload() method and take all necessary steps yourself to close open streams, stop timers, and remove listeners within the loaded asset. After any content is unloaded, line 102 then loads the content at the path contained in the local path variable.

95            //menu button click behavior: loading
96            private function onSubBtnClick(evt:MouseEvent):void {
97                var subBtn:MovieClip = MovieClip(evt.target);
98                var path:String = subBtn.projectPath;
99                var description:String = subBtn.projectDescription;
100                //_app.assetLoader.unload(); //FP 9
101                _app.assetLoader.unloadAndStop(); //FP 10
102                _app.assetLoader.load(new URLRequest(path));
103            }
104        }
105    }

This actually concludes the main functionality of the navigation bar, including the XML loading and parsing, and the content loading triggered by using the system. However, we must still discuss the classes that create the main menu buttons and submenu buttons.

The MenuButtonMain class works hand in hand with the corresponding movie clip symbol in the main FLA’s Library. The movie clip contains artwork resembling a tab, and a linkage class of com.learningactionscript3.ui.MenuButtonMain. This class resides in that location and creates the button’s text field and text format.

Lines 1 through 8 declare the package and import the required classes. Line 10 declares the class and extends MovieClip to work with the aforementioned symbol. Line 12 opens the class constructor, and receives a String as its only argument to serve as the text label for the button. It has a default value of an empty String, so an instance of the class can still be created even without passing in any text.

Line 13 sets the buttonMode property of the button to true, so the cursor will change from a pointer to a finger when rolling over the button.

Lines 15 through 22 initialize the button’s text field, and lines 24 through 31 initialize and apply a text format. Much of this is self-explanatory, but a few things are worthy of note. Lines 17 and 18 set the width and height of the text field to fill the button. This exposes the possibility that the button will cease working because the text field will trap incoming mouse events. To prevent this, line 21 disables mouse interaction with the field.

Also of note, line 19 sets the field to use embedded fonts, allowing us to use the font symbols in the library of the FLA. Line 24 instantiates the ArialBold symbol, line 26 ensures that we’re using the embedded font, and, finally, line 31 applies the text format using the setTextFormat() method, after the text has been added to the field.

1    package com.learningactionscript3.ui {
2
3        import flash.display.MovieClip;
4        import flash.text.Font;
5        import flash.text.TextField;
6        import flash.text.TextFieldAutoSize;
7        import flash.text.TextFormat;
8        import flash.text.TextFormatAlign;
9
10        public class MenuButtonMain extends MovieClip {
11
12            public function MenuButtonMain(labl:String="") {
13                this.buttonMode = true;
14
15                var btnLabel:TextField = new TextField();
16                btnLabel.y = 5;
17                btnLabel.width = this.width;
18                btnLabel.height = 20;
19                btnLabel.embedFonts = true;
20                btnLabel.text = labl;
21                btnLabel.mouseEnabled = false;
22                this.addChild(btnLabel);
23
24                var btnFont:Font = new ArialBold();
25                var labelFormat:TextFormat = new TextFormat();
26                labelFormat.font = btnFont.fontName;
27                labelFormat.size = 12;
28                labelFormat.bold = true;
29                labelFormat.color = 0xFFFFFF;
30                labelFormat.align = TextFormatAlign.CENTER;
31                btnLabel.setTextFormat(labelFormat);
32            }
33        }
34    }

The MenuButtonSub class is responsible for creating a submenu button and controlling only its appearance when the mouse rolls over or out of the button. The click behavior is controlled from the NavigationBarXML class. This makes the button more flexible and reusable. It also uses two public properties to store the path and description of the asset the button will load.

Lines 1 through 13 declare the class package and import the required classes, including TweenLite. Line 15 declares the class and extends MovieClip to inherit its accessible properties and methods. Lines 17 through 19 declare the class properties. Note that both projectPath and projectDescription are public, meaning they can be both set and retrieved from outside the class. As a result, no getter or setter is used.

1    package com.learningactionscript3.ui {
2
3        import flash.display.GradientType;
4        import flash.display.Graphics;
5        import flash.display.MovieClip;
6        import flash.display.Sprite;
7        import flash.events.MouseEvent;
8        import flash.geom.Matrix;
9        import flash.text.Font;
10        import flash.text.TextField;
11        import flash.text.TextFormat;
12        import flash.text.TextFormatAlign;
13        import com.greensock.TweenLite;
14
15        public class MenuButtonSub extends MovieClip {
16
17            private var _background:Sprite;
18            public var projectPath:String;
19            public var projectDescription:String;

Lines 21 through 30 contain the class constructor. The String that will serve as the button label is passed into the constructor, and the argument uses an empty String as a default value so the button can still be instantiated even without text input. Lines 22 and 23 call functions to draw the button’s background and create its text label, both of which we’ll look at in a moment. (Note that the String passed into the constructor is passed on to the addTextLabel() method.)

Line 24 disables mouse interaction with the button’s children. This is not only an alternative to disabling mouse interaction directly on the text field (as seen in MenuButtonMain class); it also disables interaction with the background sprite. Finally, lines 26 through 29 add event listeners for mouse roll over and roll out events.

20            //constructor
21            public function MenuButtonSub(labl:String="") {
22                addBackground();
23                addTextLabel(labl);
24                this.mouseChildren = false;
25
26                this.addEventListener(MouseEvent.ROLL_OVER,
27                                      onOver, false, 0, true);
28                this.addEventListener(MouseEvent.ROLL_OUT,
29                                      onOut, false, 0, true);
30                }

The addBackgound() method in lines 32 through 43, create the button’s background sprite using the gradient fill technique discussed in Chapter 8. The colors used in the gradient both have alpha values of 80 percent, making the buttons translucent. The addTextLabel() method receives the button’s label String from the constructor, and uses the same technique seen in the MenuButtonMain class to create and format a text field.

31            //background and text label
32            private function addBackground():void {
33                _background = new Sprite();
34                var g:Graphics = _background.graphics;
35                var matrix:Matrix = new Matrix();
36                matrix.createGradientBox(120, 25, deg2rad(90));
37                g.beginGradientFill(GradientType.LINEAR,
38                                    [0x64788C, 0x2C4054],
39                                    [0.8, 0.8], [0, 255], matrix);
40                g.drawRect(0, 0, 120, 25);
41                g.endFill();
42                addChild(_background);
43            }
44
45            private function addTextLabel(btnLabelText:String):void {
46                var btnLabel:TextField = new TextField();
47                btnLabel.x = btnLabel.y = 2;
48                btnLabel.width = this.width;
49                btnLabel.height = 20;
50                btnLabel.embedFonts = true;
51                btnLabel.text = btnLabelText;
52                this.addChild(btnLabel);
53
54                var btnFont:Font = new ArialRegular();
55                var labelFormat:TextFormat = new TextFormat();
56                labelFormat.font = btnFont.fontName;
57                labelFormat.size = 12;
58                labelFormat.color = 0xDDDDEE;
59                labelFormat.align = TextFormatAlign.LEFT;
60                btnLabel.setTextFormat(labelFormat);
61            }

Although the actions invoked by a button click are invoked from the NavigationBarXML class, each MenuButtonSub instance updates its own appearance based on mouse interaction. Specifically, TweenLite is used to tint the button a slate blue using TweenLite’s ColorTransformPlugin (activated earlier in the NavigationBarXML class). When rolling over the button with the mouse, TweenLite changes the tint from 0 to 100 percent, tinting it blue. When rolling off the button, the tint changes from 100 to 0 percent.

62            //submenu button mouse behavior
63            private function onOver(evt:MouseEvent):void {
64                TweenLite.to(_background, 0.3, {colorTransform:
65                             {tint:0x223355, tintAmount:1}});
66            }
67
68            private function onOut(evt:MouseEvent):void {
69                TweenLite.to(_background, 0.3, {colorTransform:
70                             {tint:0x334466, tintAmount:0}});
71            }
72
73            private function deg2rad(deg:Number):Number {
74                return deg * (Math.PI/180);
75            }
76        }
77    }

Finally, the deg2rad() method in lines 73 through 75 supports the createGradientBox() method in line 36, allowing us to convert degrees to radians so we can rotate the gradient.

Tying it all together

When you tie it all together, you end up with Figure 14-1. The document class creates the loader, loader mask, and navigation bar, and loads the XML. The NavigationBarXML class instantiates each MenuButtonMain instance, submenu, and MenuButtonSub instance based on the XML data. It also sets the click behavior of the submenu buttons to load content into the SafeLoader instance in the document class. The result is that new content is loaded every time the user clicks a submenu button.

A simple navigation system that loads button properties from an external XML file
Figure 14-1. A simple navigation system that loads button properties from an external XML file

This simple navigation system brings a lot of power to a project because it allows you to quickly and easily change the main and submenu button names, modify project descriptions, and update what’s loaded from each button—all by configuring an external XML file. In other words, you don’t have to edit and republish the SWF every time you want to make a change.

Note

The companion website contains additional examples that use XML for a variety of tasks, including driving an image gallery, and populating text fields.

What’s Next?

A nice rest and a beverage, that’s what. Reward yourself for all the progress you’ve made learning a new language. You’ve made your way through 14 chapters of new material; you’ve earned a break. Then, after putting your feet up for a while, get back to work. Spend 15 minutes a day experimenting. Adapt every exercise in the book to accomplish something new. Create mini-projects that combine topics, using the skills you’ve learned in each area to create something new. For example, create a particle system of text fields, or use the microphone activity level to start playing a video.

Write a class for every timeline example. Start just by converting each timeline script to document classes and compare your efforts to the supplemental source files provided. Then start writing classes that you can reuse.

Don’t forget the companion website! Start reading its posts and explore the additional resources. Visit those and other ActionScript-related websites regularly, and subscribe to their news feeds, if available.

Finally, look for a bonus chapter on the companion website. In it, we’ll examine the basic 3D capabilities that are integrated into ActionScript 3.0 as of Flash Player 10 and later. In the bonus chapter, we’ll discuss rotating and translating (moving) objects in 3D space, perspective angle, vanishing point, and more.

We hope you enjoyed this book, and that you continue your learning. Please visit the companion website at your leisure and use the contact form to let us know how you’re doing!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.181.144