Table of contents
ActionScript 3.0 includes a group of classes based on the ECMAScript for XML (E4X) specification (ECMA-357 edition 2). These classes feature powerful and easy-to-use functionality for working with XML data. Using E4X, you can develop code with XML data faster than was possible with previous programming techniques. As an added benefit, the code you produce is easier to read.
Many server-side applications use XML to structure data, so you can use the XML classes in ActionScript to create sophisticated rich Internet applications, such as those that connect to web services. A web service is a means to connect applications—for example, an Adobe Flash Player 9 application and an application on a web server—through a common standard such as the Simple Object Access Protocol (SOAP).
The ECMAScript for XML specification in Adobe Flex defines a set of classes and functionality for working with XML data. These classes and functionality are known collectively as E4X. The two main classes you use in Flex are the XML and XMLList classes.
Note: There was an XML class in ActionScript 2.0. In ActionScript 3.0, it is renamed XMLDocument so that it does not conflict with the new XML class that is part of E4X. In ActionScript 3.0, the legacy classes — XMLDocument, XMLNode, XMLParser, and XMLTag — are included in the flash.xml package primarily for legacy support. The E4X classes are core classes; you need not import a package to use them. This Quick Start does not go into detail about the legacy ActionScript 2.0 XML classes. For details on these, see the flash.xml package in the Flex 3 Language Reference.
In the following example, you create an XML literal called myBooks by using ActionScript. You can create XML literals in ActionScript by writing XML in an ActionScript block and assigning it to a variable, because XML is a native data type in Flex, just like Number or Boolean.
The myBooks XML literal in the following example contains two book elements (also known as nodes). The first book element has four child elements, with the names title, author, amazonUrl, and pageCount.
To access the elements in an XML instance, you use dot notation (.), just like you do when accessing the property of an object. So, for example, to get a reference to the list of book nodes, you write myBooks.book. This returns an XMLList instance that contains the two book nodes in the myBooks XML. To access a specific node in a list of identical nodes, you use array notation. To get a reference to the first book node, for example, you write myBooks.book[0]. You should already be familiar with this use of the dot operator and array-style syntax if you have worked previously with Objects and Arrays in ActionScript. E4X, however, goes further than this and enables you to search the XML for a node with a specific attribute value.
In the following example, you get a reference to the first book by searching for the value of its ISBN attribute. Attributes in E4X are referenced by prefixing them with the at-sign, as in @ISBN. The statement myBooks.book.(@ISBN=="1590595181") translates to "find the book node where the ISBN attribute has a value of 1590595181." The other examples describe more advanced, query techniques.
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="440" height="400" initialize="initializeHandler();" > <mx:Script> <![CDATA[ [Bindable] public var a:XMLList; [Bindable] public var b:XMLList; [Bindable] public var c:XMLList; [Bindable] public var d:XMLList; // Model: XML structure describing // some of the books in my collection. [Bindable] private var myBooks:XML = <books> <book ISBN="1590595181"> <title>Foundation ActionScript Animation: Making Things Move</title> <author>Keith Peters</author> <amazonUrl>http://tinyurl.com/npuxt</amazonUrl> <pageCount>470</pageCount> </book> <book ISBN="1582346194"> <title>Send in the Idiots: Stories from the Other Side of Autism</title> <author>Kamran Nazeer</author> <amazonUrl>http://tinyurl.com/lo5ts</amazonUrl> <pageCount>500</pageCount> </book> </books> private function initializeHandler():void { // An XML list that contains both book nodes. a = myBooks.book; // Keith Peters b = myBooks.book[0].author; // 470 c = myBooks.book.(@ISBN=="1590595181").pageCount; // Delete the first book node. delete myBooks.book[0]; // Send in the Idiots... d = myBooks.book[0].title; } ]]> </mx:Script> <!-- User interface --> <mx:Panel title="XML lookup results" paddingBottom="10" paddingLeft="10" paddingRight="10" paddingTop="10"> <mx:Text text="{'a: ' + a}" width="300"/> <mx:Label text="{'b: ' + b}"/> <mx:Label text="{'c: ' + c}"/> <mx:Label text="{'d: ' + d}"/> </mx:Panel> </mx:Application>
To view the full source, right-click the Flex application and select View Source from the context menu.
You use the @ and dot (.) operators not only to read data values from an XML structure, but also to assign data values to it.
In the following example, you create a master-detail view of an XML structure. The master view contains a DataGrid component that displays a list of books. The detail view has controls for editing the book that is currently selected in the master view.
The master and detail views use data binding and E4X to both read data from the XML and to update data in the XML.
The following examples uses E4X in these ways:
myBooks.book refers to the XMLList of book elementsmyBooks.book[selectedBookIndex] references the currently selected book element indexmyBooks.book[selectedBookIndex].title references the value of the title element of the currently selected book elementTo update the titleInput TextInput control with the title of the currently selected book, you bind the text property of the titleInput TextInput control to myBooks.book[selectedBookIndex].title. Similarly, to update the XML with the user's latest input, you assign the value of the text property of the TextInput control to myBooks.book[selectedBookIndex].title when the text in the titleInput TextInput control changes.
The other components in the detail view work exactly the same way as the titleInput control.
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="500" height="470" creationComplete="myDataGrid.selectedIndex=0;" > <mx:Script> <![CDATA[ // Model: XML structure describing // some of the books in my collection. [Bindable] private var myBooks:XML = <books> <book ISBN="1590595181"> <title>Foundation ActionScript Animation: Making Things Move</title> <author>Keith Peters</author> <amazonUrl>http://tinyurl.com/npuxt</amazonUrl> </book> <book ISBN="1582346194"> <title>Send in the Idiots: Stories from the Other Side of Autism</title> <author>Kamran Nazeer</author> <amazonUrl>http://tinyurl.com/lo5ts</amazonUrl> </book> </books> ]]> </mx:Script> <!-- Keep track of the currently selected book --> <mx:Number id="selectedBookIndex">{myDataGrid.selectedIndex}</mx:Number> <!-- User interface --> <mx:Panel title="Assigning XML data" paddingBottom="10" paddingLeft="10" paddingRight="10" paddingTop="10" > <!-- Master view: Display all books --> <mx:DataGrid id="myDataGrid" dataProvider="{myBooks.book}"> <mx:columns> <mx:DataGridColumn dataField="@ISBN" headerText="ISBN" width="85"/> <mx:DataGridColumn dataField="title" headerText="Title"/> <mx:DataGridColumn dataField="author" headerText="Author"/> <mx:DataGridColumn dataField="amazonUrl" headerText="Web site"> <mx:itemRenderer> <mx:Component> <mx:LinkButton label="Visit" click="navigateToURL(new URLRequest(data.amazonUrl), 'blank');" /> </mx:Component> </mx:itemRenderer> </mx:DataGridColumn> </mx:columns> </mx:DataGrid> <!-- Detail view: Display currently selected book for editing --> <mx:Form width="100%" autoLayout="false"> <mx:FormHeading label="Edit book details"/> <mx:FormItem label="ISBN:" width="100%"> <mx:TextInput id="isbnInput" width="100%" text="{myBooks.book[selectedBookIndex].@ISBN}" change="{myBooks.book[selectedBookIndex].@ISBN = isbnInput.text}" /> </mx:FormItem> <mx:FormItem label="Title:" width=" 100%"> <mx:TextInput id="titleInput" width="100%" text="{myBooks.book[selectedBookIndex].title}" change="{myBooks.book[selectedBookIndex].title = titleInput.text}" /> </mx:FormItem> <mx:FormItem label="Author:" width="100%"> <mx:TextInput id="authorInput" width="100%" text="{myBooks.book[selectedBookIndex].author}" change="{myBooks.book[selectedBookIndex].author = authorInput.text}" /> </mx:FormItem> <mx:FormItem label="Amazon Url" width="100%"> <mx:TextInput id="amazonUrlInput" width="100%" text="{myBooks.book[selectedBookIndex].amazonUrl}" change="{myBooks.book[selectedBookIndex].amazonUrl = amazonUrlInput.text}" /> </mx:FormItem> </mx:Form> </mx:Panel> </mx:Application>
To view the full source, right-click the Flex application and select View Source from the context menu.
The Introduction to XML example demonstrates one method of initializing an XML object by using an XML literal. When creating an XML literal, you can also pass data by reference (from other variables) into an XML object by referencing a variable name enclosed in curly braces.
If the XML structure you are creating is not valid XML, you see a TypeError run-time error.
The following example dynamically creates an XML object based on the values provided by the user for the tag name, attribute name, attribute value, and tag contents. The example checks for the TypeError condition by surrounding the XML initialization code with a try...catch block.
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="500" height="400" initialize="createXML();" > <mx:Script> <![CDATA[ [Bindable] public var xml:XML private function createXML():void { try { // Create the XML object using the values provided // by the user for the tag name, attribute name, // attribute value and the tag's contents. xml = <{tagName.text} {attributeName.text}={attributeValue.text} > {content.text} </{tagName.text}>; } catch (e:TypeError) { // Type error encountered while trying to create the // XML object. The form must not be valid. Inform // the user. xml = <note>Fill the form to see the tag here.</note>; } } ]]> </mx:Script> <!-- User interface --> <mx:Panel title="Passing XML data by reference" layout="horizontal" > <mx:Form> <mx:FormItem label="Tag name:"> <mx:TextInput id="tagName" change="createXML();"/> </mx:FormItem> <mx:FormItem label="Attribute name:"> <mx:TextInput id="attributeName" change="createXML();"/> </mx:FormItem> <mx:FormItem label="Attribute value:"> <mx:TextInput id="attributeValue" change="createXML();"/> </mx:FormItem> <mx:FormItem label="Tag content:"> <mx:TextInput id="content" change="createXML();"/> </mx:FormItem> <mx:HRule width="100%"/> <!-- Display the resulting XML --> <mx:TextArea editable="false" width="300" height="50" text="{xml.toXMLString()}" /> </mx:Form> </mx:Panel> </mx:Application>
To view the full source, right-click the Flex application and select View Source from the context menu.
In addition to assigning values to XML elements and attributes, you can assemble and transform XML objects by using E4X.
In the following example, you use the prependChild() and the appendChild() methods to add a property to the beginning or end of an XML object's list of properties. Similarly, you use the insertChildBefore() method and the insertChildAfter() method to add a property before or after a specified property. To delete an item, use the delete() method to remove a node from the XML.
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" width="500" height="600" creationComplete="myDataGrid.selectedIndex=0; validateForm();" > <mx:Script> <![CDATA[ import mx.controls.Alert; // Constants private const SELECTED_ITEM:uint = 0; private const THE_LIST:uint = 1; private const BEFORE:uint = 0; private const AFTER:uint = 1; // Flag: is the form valid? [Bindable] private var formIsValid:Boolean; // Flag: does a selection exist in the data grid? [Bindable] private var selectionExists:Boolean; // Holds the index of the next item in the // data grid following the deletion of a book item. private var newSelectedIndex:int; // Model: XML structure describing // some of the books in my collection. [Bindable] private var myBooks:XML = <books> <book ISBN="1590595181"> <title>Foundation ActionScript Animation: Making Things Move</title> <author>Keith Peters</author> <amazonUrl>http://tinyurl.com/npuxt</amazonUrl> </book> <book ISBN="1582346194"> <title>Send in the Idiots: Stories from the Other Side of Autism</title> <author>Kamran Nazeer</author> <amazonUrl>http://tinyurl.com/lo5ts</amazonUrl> </book> </books> // Add a new book. private function addBookHandler():void { // Create a new XML Object from the form information var newBook:XML = <book ISBN={isbnInput.text}> <title>{titleInput.text}</title> <author>{authorInput.text}</author> <amazonUrl>{amazonUrlInput.text}</amazonUrl> </book>; // Save the selected book's index so it can be reselected // in the grid once the new book is added. var selectedBookIndex:uint = myDataGrid.selectedIndex; // Does the user want to add the new // book relative to the selected book in the // data grid or relative to the list itself? if (addRelativeTo.selectedIndex == SELECTED_ITEM) { // Add the new item relative to the selected item. var selectedBook:XML = myBooks.book[selectedBookIndex]; // Does the user want to add it before or after // the selected item? if (addPosition.selectedIndex == BEFORE) { // Add new item before selected item myBooks = myBooks.insertChildBefore(selectedBook, newBook); } else { // Add new item after selected item myBooks = myBooks.insertChildAfter(selectedBook, newBook); } } else { // Add the new item relative to the whole list of books. if (addPosition.selectedIndex == BEFORE) { // Add new item at the start of the list. myBooks = myBooks.prependChild(newBook); } else { // Add new item at the end of the list. myBooks = myBooks.appendChild(newBook); } } // Select the previously selected item in the grid // so the user doesn't lose their position. (If a new book was // added before the currently selected item, that item's // new index will be one greater, so select that.) var newSelectedIndex:uint = (addPosition.selectedIndex == BEFORE) ? selectedBookIndex + 1: selectedBookIndex; myDataGrid.selectedIndex = newSelectedIndex; } // Delete selected book private function deleteBookHandler():void { // Save the currently selected index. var selectedBookIndex:int = myDataGrid.selectedIndex; // Delete the currently selected book. delete (myBooks.book[selectedBookIndex]); // Reselect the next logical item in the data grid. newSelectedIndex = (selectedBookIndex==0) ? 0 : selectedBookIndex - 1; // Change the selected index of the data grid // at a later frame. See note on changeDataGridIndex() // method for more details on this workaround. callLater ( changeDataGridIndex ); } // This is a workaround for a known issue with // List-based components where deleting an item // from the control's dataProvider leaves the // selectedIndex at an incorrect value. The workaround // is to reassign the data provider at least a // frame later and to change the index there. private function changeDataGridIndex ():void { // Reassign the data grid's data provider. myDataGrid.dataProvider = myBooks.book; // Set the selected index. myDataGrid.selectedIndex = newSelectedIndex; // Validate the form to make sure that there // is actually a selection (that the grid is // not empty). validateForm(); } // Perform simple form validation. private function validateForm():void { // Is the form valid? formIsValid = isbnInput.text != "" && titleInput.text != "" && authorInput.text != "" && amazonUrlInput.text != ""; // Does a selection exist in the data grid? selectionExists = myDataGrid.selectedIndex != -1; } ]]> </mx:Script> <!-- User interface --> <mx:Panel title="Assigning XML data" paddingBottom="10" paddingLeft="10" paddingRight="10" paddingTop="10" > <!-- List of books --> <mx:DataGrid id="myDataGrid" dataProvider="{myBooks.book}" change="validateForm();" > <mx:columns> <mx:DataGridColumn dataField="@ISBN" headerText="ISBN" width="85"/> <mx:DataGridColumn dataField="title" headerText="Title"/> <mx:DataGridColumn dataField="author" headerText="Author"/> <mx:DataGridColumn dataField="amazonUrl" headerText="Web site"> <mx:itemRenderer> <mx:Component> <mx:LinkButton label="Visit" click="navigateToURL(new URLRequest(data.amazonUrl), 'blank');" /> </mx:Component> </mx:itemRenderer> </mx:DataGridColumn> </mx:columns> </mx:DataGrid> <!-- New book form. Prepopulated with a book for easier testing. --> <mx:Form width="100%" autoLayout="false"> <mx:FormHeading label="New book details"/> <mx:FormItem label="ISBN:" width=" 100%"> <mx:TextInput id="isbnInput" width="100%" text="1590596196" change="validateForm();" /> </mx:FormItem> <mx:FormItem label="Title:" width="100%"> <mx:TextInput id="titleInput" width="100%" text="Object Oriented Actionscript for Flash 8" change="validateForm();" /> </mx:FormItem> <mx:FormItem label="Author:" width="100%"> <mx:TextInput id="authorInput" width="100%" text="Peter Elst" change="validateForm();" /> </mx:FormItem> <mx:FormItem label="Amazon Url" width="100%"> <mx:TextInput id="amazonUrlInput" width="100%" text="http://tinyurl.com/qxon2" change="validateForm();" /> </mx:FormItem> </mx:Form> <mx:TextArea id="deb" width="100%"/> <mx:ControlBar> <mx:Button label="Add book" click="addBookHandler();" enabled="{formIsValid}" /> <mx:ComboBox id="addPosition" enabled="{formIsValid}"> <mx:String>before</mx:String> <mx:String>after</mx:String> </mx:ComboBox> <mx:ComboBox id="addRelativeTo" enabled="{formIsValid}" > <mx:String>selected item.</mx:String> <mx:String>the list.</mx:String> </mx:ComboBox> <mx:VRule height="15"/> <mx:Button label="Delete book" click="deleteBookHandler();" enabled="{selectionExists}" /> </mx:ControlBar> </mx:Panel> </mx:Application>
There are several ways to query an XML data source in E4X. The following example describes the following methods:
When you have the results of a query, you can use the for..in and for each..in loops to iterate through the results.
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" viewSourceURL="src/XMLQuery/index.html" width="450" height="290" initialize="initializeHandler();" > <mx:Script> <![CDATA[ // Model: XML structure describing // some of the books in my collection. [Bindable] private var myBooks:XML = <books> <book ISBN="1590595181"> <title>Foundation ActionScript Animation: Making Things Move</title> <author>Keith Peters</author> <amazonUrl>http://tinyurl.com/npuxt</amazonUrl> <pageCount>470</pageCount> </book> <book ISBN="1582346194"> <title>Send in the Idiots: Stories from the Other Side of Autism</title> <author>Kamran Nazeer</author> <amazonUrl>http://tinyurl.com/lo5ts</amazonUrl> <pageCount>500</pageCount> </book> <book ISBN="1590592212"> <title>Flash 3D Cheats Most Wanted</title> <author>Aral Balkan et. al.</author> <amazonUrl>http://tinyurl.com/gsd7b</amazonUrl> <pageCount>256</pageCount> </book> </books> private function initializeHandler():void { // Line length to truncate strings at when // displaying them var lineLength:uint = 50; // // Find books with more than 300 pages. // var resultA:XMLList; resultA = myBooks.book.(pageCount > 300); // Display the found books using a for each..in // loop. var tempString:String = "<ul>"; for each (var book:XML in resultA) { tempString += "<li>" + truncate(book.title, lineLength) + "</li>"; } tempString += "</ul>"; aText.htmlText = tempString; // // Find book with "Idiots" in the title. // var resultB:XMLList; resultB = myBooks.book.(title.toString().search("Idiots")>-1); // Display the title of the found book. bText.htmlText = "<ul><li>" + truncate(resultB.title, lineLength) + "</li></ul>"; // // Get the titles of all the books. // var resultC:XMLList; resultC = myBooks.book..title; // Display the titles using a for..in loop tempString = "<ul>"; for (var bookTitle:String in resultC) { tempString += "<li>" + truncate(resultC[bookTitle], lineLength) + "</li>"; } tempString += "</ul>"; cText.htmlText = tempString; } // Helper method: Truncate a string at a given character count. Tries // to do this intelligently by truncating at a space if one exists in // the string (so that words are not truncated in the middle). private function truncate ( str:String, numChars:uint, symbol:String = "..." ):String { // Don't do anything if the string is shorter than the maximum value. if (str.length <= numChars) return str; // Search backward for a space in the string, starting with // the character position that was requested. var charPosition:uint = numChars-1; while (str.charAt(charPosition) != " " && charPosition != 0) { charPosition--; } var truncateAt:uint = charPosition == 0 ? numChars : charPosition; // If the space is right before a punctuation mark, crop the // punctuation mark also (or else it looks weird). var charBefore:String = str.charAt(truncateAt-1); if (charBefore == ":" || charBefore == ";" || charBefore == "." || charBefore == ",") { truncateAt--; } // Truncate the string. var newString:String = str.substr(0, truncateAt); newString += symbol; // Return the truncated string. return newString; } ]]> </mx:Script> <!-- User interface --> <mx:Panel title="XML lookup results" width="100%" paddingBottom="10" paddingLeft="10" paddingRight="10" paddingTop="10"> <mx:Label text="Query A - Books found:" fontWeight="bold"/> <mx:Text id="aText" width="100%"/> <mx:Label text="Query B - Books found:" fontWeight="bold"/> <mx:Text id="bText" width="100%"/> <mx:Label text="Query C - Books found:" fontWeight="bold"/> <mx:Text id="cText" width="100%"/> </mx:Panel> </mx:Application>
To view the full source, right-click the Flex application and select View Source from the context menu.
Aral Balkan acts and sings, leads development teams, designs user experiences, architects rich Internet applications, and runs OSFlash.org, the London Macromedia User Group, and his company, Ariaware. He loves talking design patterns and writing for books and magazines. He also authored Arp, the open-source RIA framework for the Flash platform. Aral is generally quite opinionated, animated, and passionate. He loves to smile, and can even chew gum and walk at the same time.