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.
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.
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.
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.
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 Label
s,
TextInput
s, 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.
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.
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:ContactDetailstoolTip="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.
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.
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.
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:ContactDetailsmode="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.
Tight coupling is where components are reliant on each other and cannot be separated without a fair amount of refactoring.
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.
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.