MXML Component Basics

To understand MXML components it helps to understand that MXML files are just classes behind the scenes. When an MXML file is compiled, the compiler translates the file to ActionScript and then compiles it into native Flash Player bytecode. This means that everything you can build in MXML you can also build in ActionScript. MXML code usually is shorter and easier to read than the equivalent ActionScript code. This makes MXML more convenient to work with than ActionScript in many cases. At the same time, because MXML is ultimately compiled to the same bytecode as ActionScript is, there is no loss in performance or features. This makes MXML ideal for application layout.

Now that you understand the benefits of working with MXML over ActionScript, let’s discuss how to create a class, optimally implement its common features, and decouple application components written in MXML within an application.

Creating and Using a Component

To create a component in MXML, you create a new file with the root tag corresponding to the class you want to extend, and with a filename corresponding to the class name of the component. Typically when segmenting an application, the base class (root tag) will be a container component. In Figure 9-2, our contact details component example, the base class is the Canvas container. Example 9-1 provides the code for the Canvas container.

Example 9-1. ContactDetails.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml">
    <!--  Contact Details Implementation Details -->
</mx:Canvas>

The code in Example 9-1 should look very similar to a basic MXML application file, except that it uses the Canvas component as the root tag rather than the Application component. Also note that as each MXML file is a separate component, you need to reference the MXML namespace as you would in the application’s root MXML file.

Note

Although we do not cover it in this book, MXML components are also ideal for when you want to extend an existing component. By declaring your own MXML file that is based on an existing component and just adding your own additional logic where needed, you can customize the existing Flex framework components. Extending components requires an understanding of the underlying component framework and the component you want to extend. To learn more about the component framework, see Chapter 19, as well as the Flex SDK documentation.

Once the component is created, you can reference it using the filename used for the MXML file, as shown in Example 9-2.

Example 9-2. Main MXML application file

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:pf3="*">
    <pf3:ContactDetails/>
</mx:Application>

In Example 9-2, we declared an instance of the ContactDetails component as we would any other component. Notice the addition of the pf3 namespace prefix. To inform the compiler of available components, you will need to reference the package, which in this case is just the root package, *.

In ActionScript, you would typically use the package keyword to declare the package of a class. In MXML, you declare a package simply via the directory structure. Because we placed the main MXML file and the MXML component in the same directory in Example 9-2, the compiler defaults to the top-level package. Although this is usable, you should specify a package for all your components. For example, to create the package com.oreilly.programmingflex.contactmanager.views you would just create the corresponding directory structure, com/oreilly/programmingflex/contactmanager/views/, and place the component’s MXML file within the views directory. You can find more details on the source paths in Chapter 2.

Now, to update the main application file, we need to update the namespace reference to reflect the new package (see Example 9-3).

Example 9-3. Updating the namespace to reflect the new package

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:pf3="com.oreilly.
programmingflex.contactmanager.views.*">
    <pf3:ContactDetails/>
</mx:Application>

When dividing an application into several components, you will want to place many of the components together within the same folder (package). This allows you to create a single package for all your components. There is nothing wrong with having multiple packages; however, you will want to have a logical reason for doing so. One such reason could be to separate components created for use within the application you are developing versus shared components you develop to be shared across many applications.

Going back to the understanding that an MXML file is a class, you can also reference the newly created component in ActionScript classes as you would any class, as shown in the following code:

package com.oreilly.programmingflex.foo {
    //Import the ContactDetails component
    import com.oreilly.programmingflex.contactmanager.ContactDetails;

    public class SampleClass {
        //Instance variable of the ContactDetails type
        private var _contactDetails:ContactDetails;
        //Class code omitted for brevity
    }
}

As you can see, the basics of creating a new component are straightforward. The ability to quickly create components is one of the biggest benefits of Flex and MXML, and it helps support the component-based development nature of Flex.

Adding and Laying Out Controls

When creating an application component, typically you will base your component on an existing Flex container, and the new component will be composed of existing components. Adding and laying out components in an MXML component requires almost the same techniques we covered in Chapter 6 regarding the main MXML file.

In the ContactDetails component from Figure 9-2, we created a component based on the Canvas layout component. As we discussed in Chapter 6, the Canvas layout component allows you to place components using absolute positioning or constraint-based layout rules. Because this is the Canvas tag of the component rather than the Application, as you saw in the main application MXML file, we will use the layout rules for Canvas to lay out children that are added to the component.

Note

Although not required, Flex Builder allows you to view the layout of a component in design mode, as well as visually lay out the component’s contents in design mode. This can be helpful when working with application components, as there is no other mechanism to easily view a component’s layout without needing to compile an application with your component.

Reviewing ContactDetails in Figure 9-2, you can see that the component is built from Labels, TextInputs, a TextArea, and a Button. You can add components using the same techniques we used previously. What follows is the earlier component, with the needed components added and positioned.

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%">
    <mx:Label id="heading" styleName="heading" x="10" y="10"/>
    <mx:Button id="edit" bottom="10" left="10" label="Edit" toggle="true"/>
    <mx:Label x="62" y="42" text="phone"/>
    <mx:Label x="53" y="94" text="address"/>
    <mx:Label x="66" y="68" text="email"/>
    <mx:TextArea x="110" y="93" editable="false" enabled="true" width="160" height=
"60" id="address"/>
    <mx:TextInput x="110" y="40" editable="false" id="phone"/>
    <mx:TextInput x="110" y="66" editable="false" id="email"/>
</mx:Canvas>

In the preceding code, we added the components and set their properties. We positioned the components using the properties for positioning (x, y, left, and bottom, in this example), as we would any child within a container. We set the root Canvas tag’s width and height properties to 100%. In the ContactDetails component, we positioned the children using absolute positioning relative to the edges of the parent. By setting the Canvas width and height properties to 100%, we helped to ensure that the container would grow to the maximum space allowed for it. It is important to note that setting the values on the root tag of an MXML component doesn’t disallow a parent from overriding the values. Because of this, the value set on the root MXML component is said to be the default value.

Understanding Interaction

When you create an MXML component, it lives isolated in its own world. When you build an application using one large MXML file, it is easy to reference component instances using the id attribute, data-bind directly to controls, and pretty much access anything within the same MXML file. When an application is split into multiple components, each component will be able to access its members, but it should not access another component’s members, even though component instances in MXML are declared public by default. Although this may seem like a limitation, it helps to promote a key object-oriented programming principle: encapsulation.

Note

Encapsulation is the process of hiding implementation details. With components, you can set properties and call methods, but a component should not access another component’s internal workings.

With that said, when you are working with application components, you will need to communicate to and from each component instance. To communicate with a component, you interact with the interface it has defined—in other words, the methods and properties that are accessible. For example, in the following code, we are setting the toolTip property. We are able to do so because the toolTip property is defined by part of the ContactDetails API (in this case, it is inherited from the UIComponent class, which is part of the Flex framework).

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:pf3="com.oreilly.
programmingflex.contactmanager.views.*">
    <pf3:ContactDetails toolTip="Contact Details"/>
</mx:Application>

This code should look familiar to you in that you can work with the component in the same way you are used to working with the Flex component. In addition, because the ContactDetails component extends Canvas, its public API is inherited, including all methods, properties, events, and styles. This allows you to set the width and height properties even though you did not define them yourself. You also can use all the MXML features available with other components, including data binding.

When deciding on the component’s interface, it is important to step back for a moment and think about the purpose of the component. The interface may initially be used for one specific purpose, but with time you will find that a good interface will improve reusability of your newly created component. A good interface also allows a developer to accomplish his goals without having to access the internals of the component.

Defining component properties

Component properties can aid in communicating to and from a component. You create properties by defining fields or getter/setter functions. You define properties in the same manner you would in ActionScript.

Note

Although it's possible to define properties using <mx:DataType> syntax, it's generally recommended that you declare properties within the ActionScript <mx:Script> block. This will allow you to organize all public methods, properties, and getter and setter functions together. This chapter focuses on real-world usage rather than all possible methods of developing a component. You can review the Flex documentation for details on all possible methods of implementing component properties, methods, and metadata.

It is a good practice to not declare fields as public, but rather to declare getter/setter functions, as shown in the following code. This helps you to guarantee that if the implementation of the property changes in the future, the public interface will not change.

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

        private var _mode:String;

        [Bindable]
        public function set mode(value:String):void
        {
            _mode = value;
        }

        public function get mode():String
        {
            return _mode;
        }
        ]]>
    </mx:Script>
    <!-- contents omitted for brevity-->
</mx:Canvas>

In the preceding code, we declared a public property mode. You can use this property to set the mode property of the component from the parent. In the code, we declared the property as bindable using the [Bindable] metadata tag. It is a good idea to use [Bindable] whenever you declare a public property because it is likely that a developer will want to use the data-binding features of Flex with your new component. Finally, we declared a private _mode variable. It is a good practice to declare private properties with a preceding single underscore. This allows you to distinguish a public property from a private one, especially if you are exposing a private property via a public getter/setter function.

Note

As you learned when adding ActionScript within MXML files in earlier chapters, you can add properties and methods using ActionScript within MXML as well, as within <mx:Script/>. This chapter covers inline ActionScript within a Script tag, but it is important to note that you can also use the other methods to add ActionScript to an MXML component just as you would reference an external ActionScript file via the Script tag’s source property.

Once you’ve declared a property, you can access the property from the parent container as you would any other property. In the following code, we have set the property value from the parent container:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" initialize="init()"
xmlns:pf3="com.oreilly.programmingflex.contactmanager.views.*">
    <pf3:ContactDetails mode="view" />
    <!-- contents omitted for brevity -->
</mx:Application>

Most of the time, you will want a property to accept any value of a data type, and in such cases, this method works well. Sometimes, though, you will want to restrict the possible values. In the code we’ve been building on throughout this chapter, the mode value can accept only an enumeration of values. However, ActionScript does not support enumerations. It is a good practice, therefore, to provide the user with static variables. This reduces the possibility of user error and improves ease of use because static variables are a form of self-documentation for possible values. In addition to using static variables, we use the Inspectable metadata tag, which provides Flex Builder with hints on what data is accepted by the property. Adding both helps the developer and allows Flex Builder to provide code hinting for the possible values, but does not provide any sort of compile time or runtime type checking at this time. Example 9-4 is updated to make use of enumerations for the component, and Example 9-5 is the updated Main.mxml.

Example 9-4. Adding enumerations to a component

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" width="100%"
backgroundColor="#f8f8f8" height="100%">
    <mx:Script>
        <![CDATA[

            public static const VIEW_MODE:String = "view";
            public static const EDIT_MODE:String = "edit";

            private var _mode:String;

            [Bindable]          [Inspectable(enumeration="{ContactDetails.VIEW_MODE},
{ContactDetails.EDIT_MODE}")]

            public function get mode():String
            {
                return _mode;
            }

            public function set mode(value:String):void
            {
                _mode = value;
            }
            ]]>
    </mx:Script>
    <!-- contents omitted for brevity -->
</mx:Canvas>

Example 9-5. Updated Main.mxml file referencing the enumeration value

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" initialize="init()"
xmlns:pf3="com.oreilly.programmingflex.contactmanager.views.*">
    <pf3:ContactDetails mode="{ContactDetails.VIEW_MODE}"/>
</mx:Application>

So far in this section, we covered using properties to pass and retrieve values with a component. You also can use a property to pass an instance of an object with which you would like the component to communicate. For example, you could pass an instance of the root application to a component and have the component call methods directly on the root application. At first, this may seem like an appropriate method of communicating from child to parent, but it actually results in a tight coupling between the child and the passed object. This is not an absolute rule, but later in this chapter we will discuss how to use events to communicate with other objects. Although this requires more work, it will typically be the more appropriate method of communicating from a component as it helps promote loose coupling and reusability.

Note

Tight coupling is where components are reliant on each other and cannot be separated without a fair amount of refactoring.

Defining component methods

In the same way you declare a property, you can declare a component method within part of a component. Although you do not have to add any methods with the ContactDetails component, Example 9-6 shows how you would add a clear() method.

Example 9-6. Adding a clear() method to ContactDetails

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Script>
        <![CDATA[
           public function clear():void
           {
               address.text = null;
               phone.text = null;
               email.text = null;
           }
        ]]>
    </mx:Script>
</mx:Canvas>

This method clears the text within the input components. Once you’ve defined a method with a proper accessor keyword, you can call the new method from the parent, as shown in Example 9-7.

Example 9-7. Calling the new method from the parent

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" initialize=
"initializeHandler()" xmlns:pf3="com.oreilly.programmingflex.contactmanager.
views.*">
    <mx:Script>
        <![CDATA[
           private function initializeHandler():void
           {
               //In this example, calling clear is redundant
               contactDetails.clear();
           }
        ]]>
    </mx:Script>
    <pf3:ContactDetails id="contactDetails"/>
</mx:Application>

In Example 9-7, the composing object creates an instance of the component and calls the clear() method. The parent has no knowledge of the internal workings of the component; it only knows of the publicly accessible interface, and it trusts that the component knows how to do what it needs to do. This further helps to decouple the parent and child relationship between components built in an application.

Defining component events

When creating an MXML component, you will typically be composing other objects within the component, as you did in the ContactDetails component. These child components dispatch their own events, and the application will be interested in some of the events or a custom event that is specific to ContactDetails. Because the children reside within the component, their events won’t be seen by the world outside the component. Actually, you wouldn’t even want them to be seen. Doing so would allow any internal component to dispatch any event, and you would have no control over what events your component dispatches.

For this reason, and to be able to define custom events that do not depend on existing events, you will need to define your own events for your component. In the ContactDetails component, one event that would be useful to define is when a user clicks on the edit button. Although a lot can happen within the component, you are only interested in knowing when a user has changed the data or maybe when the user is about to change the data (in edit mode). For this reason, it would be ideal for this component to have an EditChange event, with a description of “begin” and “end,” depending on whether the user has begun or finished editing the component.

All components in the Flex framework inherit from EventDispatcher, a class that implements the capability of an object to subscribe to and receive event notification. To add an event to a custom component, you need to define and dispatch the event.

To define an event, you first need to declare the event. You declare events using a metadata tag, and in MXML you do that using the <mx:Metadata/> tag, as shown in Example 9-8.

Example 9-8. ContactDetails with event metadata tag added

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" backgroundColor=
"#f8f8f8" height="100%">
    <mx:Metadata>
       [Event(name="editChange", type="com.oreilly.programmingflex.contactmanager.
events.EditChangeEvent")]
    </mx:Metadata>
    <!-- contents omitted for brevity -->
</mx:Canvas>

In Example 9-8, we added an event called editChange and the type EditChangeEvent. EditChangeEvent is the object type passed to the handler function when an event occurs. You could have used a generic event class here, but it is a good idea to create your own events for components. Doing so will allow you to provide added functionality that is specific to your component.

Let’s create the EditChangeEvent class in the com.oreilly.programmingflex.contactmanager.events package. It's a good practice as usual to specify a package for a class and for events, and to place all events in an events package. This is consistent with the Flex framework and allows users to import a single package that will contain all events. Here is the definition of the EditChangeEvent class:

package com.oreilly.programmingflex.contactmanager.events
{
    import flash.events.Event;

    public class EditChangeEvent extends Event
    {
        public static const EDIT_CHANGE:String = "editChange";
        public var edit:Boolean;

        public function EditChangeEvent(edit:Boolean=false)
        {
            super(EDIT_CHANGE);
            this.edit = edit;
        }

        override public function clone():Event
        {
            return new EditChangeEvent(this.edit);
        }
    }
}

With the custom event type created, now you can dispatch the new event (see Example 9-9). We will incorporate the new event into the ContactDetails component later in this chapter.

Example 9-9. Dispatching EditChangeEvent

var eventEditing:EditChangeEvent = new EditChangeEvent(true);
dispatchEvent(eventEditing);
..................Content has been hidden....................

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