Chapter 14. Working with Data

Although some Flex applications use data more extensively than others, nearly all use data to some extent. The Flex SDK is a robust set of tools for working with data. This chapter examines how to work with data on the client side without an extensive discussion of client/server data communication, which is covered in Chapter 16. Rather, this chapter focuses primarily on the following topics: modeling data and data binding.

When working with data, you generally want to store it in some sort of data repository within memory. These repositories are known as data models. In the first section of this chapter, we’ll look at each of the three basic ways to store data in data models.

You can use ActionScript for all your data management needs, but then you’re not really using the power of the Flex framework. To simplify linking data from a data model to a control or from one control to another control or component you can use a powerful feature called data binding.

Once you know the basics of working with data in a Flex application, you’ll have a foundation for sending and receiving data. That topic is discussed further in Chapter 16.

Using Data Models

You can work with data in many ways in Flex applications, including as low-level ActionScript solutions and high-level MXML solutions; this section looks at both techniques. You can use these data models as a repository for data retrieved from a remote procedure call (RPC) such as a web service method call. You can also use a data model to store user input data before sending it to the server. You can even use a data model simply as a mechanism for populating form input, such as a combo box.

Using the Model Tag

The <mx:Model> tag allows you to create an object that represents a data structure using MXML. To use the tag practically, you must always specify an id attribute:

<mx:Model id="example" />

Once you’ve created a model this way, you can do one of two things:

  • Create a data structure using tags.

  • Specify a source attribute to populate the model from a file.

Creating tag-based model structures

If you want to populate a model object within your MXML document, you can specify the structure using tags. Tags are useful when you want to use the data model for storing user input or data you retrieve from an RPC. The tags are arbitrary XML tags that you select to represent the data structure. The data model must contain only one root node. The following example uses a data model to represent a user:

<mx:Model id="userData">
    <user>
        <email></email>
        <phone></phone>
        <address>
            <city></city>
            <state></state>
        </address>
    </user>
</mx:Model>

Of course, you can populate the data model with real data, as in the next example:

<mx:Model id="userData">
    <user>
        <email>[email protected]</email>
        <phone>123 555-1212</phone>
        <address>
            <city>Exampleville</city>
            <state>CA</state>
        </address>
    </user>
</mx:Model>

However, in most cases when you want to initialize a data model with data, you should use an external file, as described in the next section. When you create the structure of a data model in the MXML document in this fashion, you won’t initialize the object with data. Instead, you’ll use it with data binding (discussed in the Data Binding” section later in this chapter) to store data from user input or data retrieved with RPCs.

Populating a model from a file

If you want to use a data model to store static data, often the best approach is to load that data from a file rather than defining the structure and initializing the object data, all from the MXML document. For example, consider a data model in which you want to store all the U.S. state names. If you define the structure and initialize it within the MXML document, your MXML document becomes cluttered with lots of lines that are not used to define the user interface or the business logic, but rather are used just to populate a data model. It is better to place that data in a separate file and simply reference it in the MXML document. The <mx:Model> tag makes that an easy task.

If you specify a source attribute for an <mx:Model> tag, the object looks to the file you specify, and it loads the data from that file at compile time, not at runtime. Specifying the source attribute achieves exactly the same effect as placing the contents of the source file within the <mx:Model> tag, but it allows you to place the data in a separate file to clean up the MXML. Once the .swf is compiled, you won’t need to distribute the data source file with the .swf file as all the data is compiled into the .swf. This is also a potential downside since it also increases the file size of the .swf file.

Note

Note that while using <mx:Model> tags may seem like a solution for application-wide data sources, you’ll likely find that it is preferable to use ActionScript data model classes for that purpose. You can read about using ActionScript data model classes in the “Using ActionScript Classes” section of this chapter.

Consider the following XML document called states.xml, an XML file that contains the names of all 50 U.S. states (to save space, this example has been shortened, but you can assume the actual file contains all 50 states):

<states>
    <state>Alabama</state>
    <state>Alaska</state>
    <state>Arizona</state>
    <state>Arkansas</state>
    <state>California</state>
    <state>Colorado</state>
    <state>Connecticut</state>
    <state>Delaware</state>
    <state>Florida</state>
    <state>Georgia</state>
    <state>Hawaii</state>
    <state>Idaho</state>
    <state>Illinois</state>
    <!-- additional states... -->
</states>

You can populate a model with that data by adding the source attribute to the <mx:Model> tag, as shown in the following example:

<mx:Model id="statesModel" source="states.xml" />

Note

Note that the preceding assumes that states.xml is in the same directory as the main MXML file for the application.

Referencing model data

Having a data model doesn’t do you much good unless you can reference the data stored within it—either to update it or to retrieve it. In many, if not most cases, you will use something called data binding to reference model data. (The details of data binding are discussed later in this chapter, in the Data Binding” section.) You can also reference a model using ActionScript. You’ll see how the same concepts apply when using data binding later in this chapter.

To understand how to reference data in a data model it’s important to understand how an <mx:Model> tag translates into ActionScript. Unlike many MXML tags, the <mx:Model> tag does not correspond to an ActionScript class by the same name. There is no Model class. Rather, an object created using the <mx:Model> tag is an instance of the ObjectProxy class. An ObjectProxy object is essentially a wrapper for an Object instance. In practical terms, you can treat an ObjectProxy object exactly as you would an Object instance. The primary purpose of ObjectProxy is to enable data binding, which would not be available for a simple Object instance.

The fact that an object created with <mx:Model> is an ObjectProxy object in ActionScript immediately tells you that you can access the data using standard ActionScript dot syntax. The only thing you need to now know about these objects is how the tag-based data structure translates to ActionScript. As we’ve discussed, a model created with <mx:Model> can have only one root node. That root node is always synonymous with the model object itself. The child nodes of the root node become properties of the ObjectProxy object, and the child nodes of the child nodes become properties of the properties of the object.

The following example defines a simple data model and a button. When the user clicks the button, it calls trace() and displays the value of the email property of the data model. Because the root node of the data model (user) is synonymous with the data model object (userData), you do not need to (nor can you) treat the root node as a child of the data model; to retrieve the email value, userData.email is used, not userData.user.email:

<mx:Model id="userData">
    <user>
        <email>[email protected]</email>
        <phone>123 555-1212</phone>
        <address>
            <city>Exampleville</city>
            <state>CA</state>
        </address>
    </user>
</mx:Model>
<mx:Button click="trace(userData.email)" />

You can also assign values to the data model properties using standard ActionScript expressions. The following example uses the same data model as the preceding example, but with two buttons—one that updates the city value by appending a random number, and one that traces the value:

<mx:VBox>
    <mx:Button click="userData.address.city = 'Exampleville' +
                      Math.round(Math.random() * 10)" label="Update City" />
    <mx:Button click="trace(userData.address.city)" label="Trace" />
</mx:VBox>

There is one conversion that takes place that might not be intuitive. When a data model structure consists of two or more sibling nodes with the same name, they are converted into an array. Consider the states example again:

<states>
    <state>Alabama</state>
    <state>Alaska</state>
    <state>Arizona</state>
    <state>Arkansas</state>
    <state>California</state>
    <state>Colorado</state>
    <state>Connecticut</state>
    <state>Delaware</state>
    <state>Florida</state>
    <state>Georgia</state>
    <state>Hawaii</state>
    <state>Idaho</state>
    <state>Illinois</state>
    <!-- additional states... -->
</states>

The states data is loaded into a data model using the source attribute, as follows:

<mx:Model id="statesModel" source="states.xml" />

Here, statesModel.state is an array that contains the name of each state as an element. The following traces true when the user clicks the button because statesModel.state is an array:

<mx:Button click="trace(statesModel.state is Array)" />

The following example uses an ActionScript function to loop through all the elements of the array and display them in a text area:

<mx:Script>
    <![CDATA[

        private function displayStates():void {
            for(var i:uint = 0; i < statesModel.state.length; i++) {
                statesTextArea.text += statesModel.state[i] + "
";
            }
        }

    ]]>
</mx:Script>
<mx:Model id="statesModel" source="states.xml" />
<mx:VBox>
    <mx:Button click="displayStates()" />
    <mx:TextArea id="statesTextArea" height="500" />
</mx:VBox>

Using XML

The <mx:Model> tag is useful when you want to work with data stored in traditional types such as objects, strings, and arrays. If you want to work with XML-formatted data, you can use the <mx:XML> tag to create an XML-based data model (that you can access using E4X).

The <mx:Model> and <mx:XML> tags are structurally very similar. As with <mx:Model>, you should always specify an id attribute when creating an <mx:XML> tag:

<mx:XML id="example" />

Also, as with <mx:Model>, there are two basic ways to create the structure and/or initialize an <mx:XML> data model:

  • Create a data structure using tags.

  • Specify a source attribute to populate the model from a file.

Specifying an XML structure with tags

You can specify an XML structure using tags in the MXML document much as you would for an ObjectProxy-based model created with <mx:Model>. The following defines the structure for an <mx:XML> tag:

<mx:XML id="chaptersXml" xmlns="">
    <chapters label="Chapters">
        <chapter label="Chapter 1">
            <file label="File 1.1" />
        </chapter>
        <chapter label="Chapter 2">
            <file label="File 2.1" />
        </chapter>
    </chapters>
</mx:XML>

Note

As with <mx:Model>, the structure for <mx:XML> must have only one root node.

Note

The MXML parser uses namespaces extensively, and it is important that the XML tag has a unique namespace from the default namespace of the containing MXML document. For this reason, Flex Builder adds xmlns="", as in the preceding example.

Loading XML from a file

You can load the XML data for an <mx:XML> tag using the source attribute just as you would with <mx:Model>. When you use the source attribute, that data is loaded at compile time, and it is compiled into the .swf. That means you do not need to distribute the source file with the .swf, and it means the data is static. The following loads the data from chapters.xml:

<mx:XML id=chaptersXml" source="chapters.xml" />

Referencing XML data

When you use an <mx:XML> tag, it creates an XML object in ActionScript. By default, the XML object is a top-level E4X XML object. However, you can use the format attribute to specify whether to use an E4X XML object or a legacy flash.xml.XMLNode object. The default value for the format attribute is e4x. Setting the value to xml creates an XMLNode object instead. For all the examples in this book, E4X XML data models are used unless otherwise noted.

When you want to reference the data from a model created using <mx:XML>, the root node of the data is synonymous with the data model object. The following uses E4X syntax to trace the label attribute of the first <chapter> child node:

<mx:XML id="chaptersXml">
    <chapters label="Chapters">
        <chapter label="Chapter 1">
            <file label="File 1.1" />
        </chapter>
        <chapter label="Chapter 2">
            <file label="File 2.1" />
        </chapter>
    </chapters>
</mx:XML>
<mx:Button click="trace(chaptersXml.chapter[0].@label)" />

The following example assigns the data model as the data provider for a tree component:

<mx:XML id="chaptersXml">
    <chapters label="Chapters">
        <chapter label="Chapter 1">
            <file label="File 1.1" />
        </chapter>
        <chapter label="Chapter 2">
            <file label="File 2.1" />
        </chapter>
    </chapters>
</mx:XML>
<mx:VBox>
    <mx:Button click="chapters.dataProvider = chaptersXml" />
    <mx:Tree id="chapters" width="200" labelField="@label" />
</mx:VBox>

Using ActionScript Classes

Although the <mx:Model> and <mx:XML> data models provide a simple and convenient way to work with data, they are not the ideal solution in most cases. Even though they work well for simple, static data (such as a list of U.S. state names), they are not well suited for complex data, dynamic data, or data that has rules applied to it. In those cases, it is better to use a custom ActionScript class as the data model. Although there’s nothing wrong with using the <mx:Model> and <mx:XML> tags, it is best to be very sparse in your use of them. Remember that MXML is primarily intended for creating user interfaces and layout. ActionScript, on the other hand, is ideal for managing and working with data. Here are a few of the advantages of using a custom ActionScript class:

Strong typing

With <mx:Model> and <mx:XML> you cannot enforce data types, but with ActionScript classes you can (i.e., a string must be a String, and an int must be an int).

Data testing/consistency

You cannot verify that a value assigned to an object created using <mx:Model> or <mx:XML> is a valid value. For example, if a particular property can have values only in the range from 1 to 10, you can't verify that when assigning values to <mx:Model> or <mx:XML>. However, an ActionScript class setter method can test for valid values. If a value is invalid, the class can discard the new assignment, convert the value to a value in the valid range, or throw an error.

Business logic

When you assign a value to a property of an instance created from <mx:Model> or <mx:XML>, you're simply assigning a value without the ability to run any business logic. However, with an ActionScript class you can run any ActionScript operations you want when getting or setting values.

Design patterns

You cannot employ sophisticated design patterns with <mx:Model> and <mx:XML>, yet you can with an ActionScript class. For example, you may need to have a single, managed instance of a data model such as a user account data model. That way, it is accessible throughout the application. With an ActionScript class you can employ the Singleton design pattern to accomplish this goal.

Note

It’s worth noting that the main application class is a type of Singleton class in that there is only one instance per application, and that one instance is globally accessible (via mx.core.Application.application). You could, theoretically, add a data model instance to the main class and access it globally through the main class. Although that would accomplish the goal, the architectural soundness of that decision is questionable. It is generally advisable to use an ActionScript class as the data model.

Writing an ActionScript class as a data model is quite simple. You merely need to define a new class with public accessor methods for all the properties. The class in Example 14-1 defines a data model for a user. Note that all the getters and setters are strongly typed and several of the setters use data testing.

Example 14-1. User class

package com.oreilly.programmingflex.data {

    public class User {

        private var _nameFirst:String;
        private var _nameLast:String;
        private var _email:String;
        private var _lastLogin:Date;
        private var _userType:uint;

        public function get nameFirst():String {
            return _nameFirst;
        }

        public function set nameFirst(value:String):void {
            _nameFirst = value;
        }

        public function get nameLast():String {
            return _nameLast;
        }

        public function set nameLast(value:String):void {
            _nameLast = value;
        }

        public function get email():String {
            return _email;
        }

        public function set email(value:String):void {
            var expression:RegExp = /[A-Z0-9._%-]+@[A-Z0-9.-]+.[A-Z]{2,4}/i;
            if(expression.test(value)) {
                _email = value;
            }
            else {
                _email = "invalid email";
            }
        }

        public function get lastLogin():Date {
            return _lastLogin;
        }

        public function set lastLogin(value:Date):void {
            _lastLogin = value;
        }

        public function get userType():uint {
            return _userType;
        }

        public function set userType(value:uint):void {
            if(userType <= 2) {
                _userType = value;
            }
        }

        public function User() {}


    }
}

You can then create an instance of the model class using MXML or ActionScript. With MXML you have to define the namespace, then use <namespace:Class> to create the instance:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:data="com.oreilly.programmingflex.data.*" layout="absolute">

    <data:User id="user" email="[email protected]" lastLogin="{new Date()}"
               nameFirst="Abigail" nameLast="Smith" userType="1" />

</mx:Application>

With ActionScript you need to import the class, and then use the constructor as part of a new statement:

import com.oreilly.programmingflex.data.User;
private var user:User;

private function initializeHandler(event:Event):void {
    user = new User();
    user.email = "[email protected]';
    // etc.
}

In the next section, we’ll look at data binding. If you want to enable the data binding feature for a custom ActionScript-based data model, you must use the [Bindable] metadata tag when declaring the class:

[Bindable]
public class User {

If you create the instance using MXML, the instance is automatically enabled for data binding, assuming the class uses the [Bindable] metadata tag. However, if you create the instance using ActionScript, you must also use the [Bindable] tag when declaring the variable you use to store the reference:

[Bindable]
private var user:User;

We’ll talk more about data binding and the [Bindable] metadata tag in the Data Binding” section later in this chapter.

Working with Collections

Another way that you can work with data is to use a collection. In this case, we’re using the word collection to refer to a class that implements the mx.collections.ICollectionView interface. Two of the primary such collection classes are mx.collections.ArrayCollection and mx.collections.XMLListCollection.

In some cases, collections can be substitutes for the other techniques we described earlier. For example, you can use a collection in place of a Model tag if you intend to use the data as a data provider for a list component. However, when you are using an ActionScript class to model data, a collection isn’t necessarily a substitute, but rather a complement. The primary value of a collection in such a case is that it provides a data-bindable way to store data.

You can use an ArrayCollection instead of an Array when you’d like to use data binding (see the next section for more information about data binding). This is because an Array does not allow for data binding. A change to an element in an array will not trigger changes in other components. However, ArrayCollection instances are data-bindable, and if you add or remove an element from the collection, it triggers binding changes. As with many things in Flex, you can create an ArrayCollection instance using ActionScript or MXML. First we’ll look at an example using MXML. In the following code, we create an ArrayCollection instance using an ArrayCollection tag. We are assigning the collection to the dataProvider property of a combo box component. You’ll notice that nested in the ArrayCollection tag is an Array tag. That’s because an ArrayCollection object is a data-bindable wrapper for an array.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
    <mx:ComboBox>
         <mx:dataProvider>
              <mx:ArrayCollection id="letters">
                   <mx:Array>
                        <mx:String>A</mx:String>
                        <mx:String>B</mx:String>
                        <mx:String>C</mx:String>
                        <mx:String>D</mx:String>
                   </mx:Array>
              </mx:ArrayCollection>
         </mx:dataProvider>
    </mx:ComboBox>
</mx:Application>

Note

You can omit the <mx:Array> tag in the preceding example and it will still work. When you nest values inside an <mx:ArrayCollection> tag, the Flex compiler assumes they're elements of an array. Therefore, you may sometimes see code where the <mx:Array> tag is absent. Either way works. In this case, we are opting for the explicit use of the <mx:Array> tag for clarity.

Once we’ve created an ArrayCollection instance we can update it using various methods. We can use the addItem() method to add elements to the collection. We can use the removeItemAt() method to remove elements at specific indexes. We can use the getItemAt() method to retrieve elements at specific indexes. Adding and removing elements using these methods triggers data binding changes, as you can see in the next example. In this code, we add a button that adds a random letter to the collection when the user clicks it. Because the update triggers data binding, the following code will result in the combo box redrawing itself, and it will display a new item in the drop-down list.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
    <mx:ComboBox>
         <mx:dataProvider>
              <mx:ArrayCollection id="letters">
                   <mx:Array>
                        <mx:String>A</mx:String>
                        <mx:String>B</mx:String>
                        <mx:String>C</mx:String>
                        <mx:String>D</mx:String>
                   </mx:Array>
              </mx:ArrayCollection>
         </mx:dataProvider>
    </mx:ComboBox>
    <mx:Button label="Change"
     click="letters.addItem(String.fromCharCode
(Math.round(Math.random() * 26) + 65))" />
</mx:Application>

As we said earlier, we can also create ArrayCollection instances using ActionScript. When we create ArrayCollection instances in this way, we can create the empty instance and add elements programmatically using the addItem() method, or we can initialize the collection by passing an array to the constructor, as in the following example.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
    <mx:Script>
          <![CDATA[
              import mx.collections.ArrayCollection;

              [Bindable]
              private var letters:ArrayCollection =
new ArrayCollection(["A", "B", "C", "D"]);

         ]]>
    </mx:Script>
    <mx:ComboBox dataProvider="{letters}" />
    <mx:Button label="Change"
     click="letters.addItem(String.fromCharCode
(Math.round(Math.random() * 26) + 65))" />
</mx:Application>

The XMLListCollection class is to XMLList objects what the ArrayCollection is to arrays. XMLListCollection instances wrap XMLList objects. XMLListCollection provides the same API for adding, retrieving, and removing elements as the ArrayCollection class does. However, when you call these methods, you use XML elements instead of nonhierarchical values, as you would with ArrayCollection. The following code illustrates how you can create an XMLListCollection using MXML:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
    <mx:Script>
         <![CDATA[

              private function addNewItem():void {
                   var item:XML = <element label="Numbers">
                                                <element label="1" />
                                                <element label="2" />
                                        </element>
                   collection.addItem(item);
              }

         ]]>
    </mx:Script>
    <mx:Tree labelField="@label" width="200">
         <mx:dataProvider>
              <mx:XMLListCollection id="collection">
                   <mx:XMLList xmlns="">
                        <element label="Letters">
                             <element label="A" />
                             <element label="B" />
                        </element>
                   </mx:XMLList>
              </mx:XMLListCollection>
         </mx:dataProvider>
    </mx:Tree>
    <mx:Button label="Change" click="addNewItem();" />
</mx:Application>

In this example, we create an XMLListCollection with an ID of collection, and we assign it as the dataProvider for a tree component. The XMLListCollection instance wraps an XMLList object with one element called Letters. When the user clicks the button we use the addItem() method to add a second element to the XMLListCollection. Because XMLListCollection updates data binding, the changes are reflected in the UI immediately.

You can also create an XMLListCollection object using ActionScript. The following example illustrates how to do this. This code results in the same behavior as the preceding example, but it uses ActionScript to create the XMLListCollection instance.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">
    <mx:Script>
         <![CDATA[
              import mx.collections.XMLListCollection;

              private var xmlList:XMLList = new XMLList(<element label="Letters">
                                                              <element label="A" />
                                                              <element label="B" />
                                                       </element>);

              [Bindable]
              private var collection:XMLListCollection =
new XMLListCollection(xmlList);

              private function addNewItem():void {
                   var item:XML = <element label='Numbers'>
                                                <element label="1" />
                                                <element label="2" />
                                        </element>
                   collection.addItem(item);
              }

         ]]>
    </mx:Script>
    <mx:Tree labelField="@label" width="200" dataProvider="{collection}" />
    <mx:Button label="Change" click="addNewItem();" />
</mx:Application>
..................Content has been hidden....................

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