Create a Flex component

Master the Adobe Flex rendering engine

Rich Internet Applications (RIAs) strive to bring the interactivity, responsiveness, and robustness of traditional desktop applications to Web-based applications. RIAs are especially important for developers who are hoping to leverage business intelligence (BI) and Web 2.0 approaches to content and delivery. Adobe® Flex® is an application at the forefront of RIA-based solutions. A relatively new but fast-growing technology, Flex leverages the capabilities of Adobe's Flash Player to provide first-rate graphical presentations that feature highly responsive UIs. Flex ships with many useful and robust components, but things get more difficult when you need to step outside the narrow bounds of what Flex provides for you and create domain-specific functionality. This introductory article provides an in-depth look at the architecture of the Flex-rendering engine, walking you through the process of incorporating Flex components into your RIAs and explaining what you need to know to create new Flex functionality from scratch.

Share:

Sandeep Malik (sandeep.malik@in.ibm.com), Tech Lead, IBM

Sandeep Malik photoSandeep Malik is a Tech Lead for IBM Cognos NOW! and works out of India Software Labs, Pune. He has been involved in the design and architecture phase of new generation UI for the Cognos NOW!, and in memory and real-time streaming OBI (Operation Business Intelligence) engine. Sandeep has extensive experience in heavy duty graphics, charting libraries, client-side streaming, non-blocking I/Os, and in general, asynchronous systems. Prior to IBM, Sandeep worked in a network security domain, analyzing the patterns that change the network traffic distribution (for example, botnets, worm scans, and so on). He has also worked on implementing Servlets 2.4 spec in one of his previous companies. During his free time, he enjoys watching cricket and wishes that in his next life he could become a cricketer, too!



28 July 2009

Also available in Chinese Japanese

Introduction to Flex and RIAs

The advent of new client-server models and the demand for more complex UIs have made it imperative for even Web-based products to feature richer and more responsive application designs. These new kinds of applications, often referred to as Rich Internet Applications (RIAs), aim to bring many of the features and benefits of traditional desktop applications to their Web-based counterparts. Adobe Flex is at the forefront of those tools that help you create RIAs, whose features often include rich charting, drill-down animations, 3D effects, and a new level of responsive user interaction. Such features are especially important in the burgeoning areas of business intelligence (BI) and Web 2.0 content creation and delivery. Core Web 2.0 requirements typically include asynchronous processing, doing away with the request-response paradigm, content aggregation, and so on—things that applications like Flex can help you do with less effort and in less time.

Flex ships with many useful and robust components, but things get more difficult when you need to step outside the narrow bounds of what Flex provides for you and create domain-specific functionality. This introductory article provides an in-depth look at the architecture of Flex's rendering engine, detailing Flex's core benefits, walking you through the process of incorporating Flex components into your RIAs and explaining what you need to know to create new functionality from scratch. Specifically, I'll give you a guided tour of Flex and its capabilities. I'll walk you through the similarities between Flex and the Java™ programming language and then discuss the life cycle of a Flex component from its rendering engine's perspective. Along the way, I'll drill down on the details required to extend and build Flex components, including a careful look at a Flex component's life cycle and the subtle details related to a Flex component's commit, measure, and layout phases. There are many things that Flex components take care of for you automatically, but stepping outside the pre-built components can quickly leave you feeling a bit lost. I'll explain what some of these Flex components are doing for you behind the scenes, so you can know what you need to do when you're responsible for providing these behaviors.

Web development standards changing

Flex and competing technologies such as JavaFX, Ajax, and Silverlight are pioneering today's changing approaches to Web development and have played a significant role in changing how users like to visualize their data. Flex and its rivals are relatively nascent technologies and are still in the process of overcoming issues such as portability across browsers, security, limits on client-side caching, and so on.

One of the most significant competing technologies to Flex is Ajax. Their capabilities overlap some, but they also fill substantially different niches. Flex excels at targeting rich and complicated graphical structures, while Ajax is used primarily for text-based content. Ajax works closely with HTML, and its data is transferred in XML (or JSON) format, while Flex can support XML, JSON, and even a binary format of data transfer. The fact that Flex supports binary data transfer probably puts it far ahead of Ajax in terms of data transfer rates and compactness.

Advantages of Flex

Flex delivers many significant benefits that are worth noting. Many (but by no means all) of Flex's most significant benefits derive from the fact that it is built on top of and extends Adobe's ubiquitous Flash Player. Key benefits of Flex include:

  1. Complete browser portability. Any browser that supports Flash Player—and that includes almost every browser—also supports Flex and its scripting language, ActionScript (AS). This is in distinct contrast to Ajax, which is affected by the incompatible implementations of JavaScript implemented in different browsers.
  2. Consistent look-and-feel. Flash Player is well known for delivering the same look-and-feel to all operating systems and browsers. Flex utilizes the Flash Player engine, so it benefits from that same consistent look-and-feel.
  3. Robust security. Flex leverages the highly tested Flash Player security model.
  4. Rich UI. Flex benefits from Halo Skins, gradient fills, vector graphics, and other Flash Player features.
  5. Scalable vector graphics (SVGs). Flex stands out from most other RIA-based technologies because it supports vector-based drawing and direct embedding of SVG mark-up files. SVG-based images look equally good at any resolution a given browser supports. This is in distinct contrast to bitmap-based images, which can degrade significantly as you scale up a given image's size.
  6. Asynchronous request/response model. Flex offers complete support for asynchronous processing of user requests. Asynchronous processing enables a Web site to break away from the page-centric model, where every user request spawns a time-consuming page refresh.
  7. Binary data communication. Flex offers complete support for binary data transfer between a Flex client and any back-end server. You can send this data using Abode's proprietary Action Message Format (AMF) (now with an open source specification) or any other format you might want to leverage or develop in-house. Flex even supports opening binary sockets from client-to-server, which enables you to achieve "real" data push. Note that this feature might not be able to use a browser's encryption facilities such as Secure Socket Layer (SSL).
  8. Runtime shared libraries (RSLs) and modularization. Flex's support for these features ensures that you can load modules dynamically, which means you can add new features to an already running application and even make use of RSLs loaded by other Flex applications running on the same client machine. This can help you reduce the start-up time of your application's features because it shrinks the size of the initial binary.
  9. Client-side caching. Flex offers superb support for client-side caching. With the user's permission, a Flex application can cache unlimited data on the client side, which reduces network round trips if the same data is requested again in the same or future sessions. You can cache any kind of data, including full object graphs, custom classes, maps, and arrays. This support is significantly more advanced than HTML cookies that allow an application to store only String-name value pairs and limit even those to a general restriction of four KBs per Web site.
  10. Cross-browser communication. Flex supports communication between applications running in the same type of browser, between different tabs of the same browser, and even between applications running in different browsers on a user's machine. This feature means many applications can share data among themselves, enabling an extremely rich end-user experience.
  11. Streaming. Flex offers excellent support for streaming binary data. Streaming can be a life saver for data-heavy applications that need to transfer large amounts of data to the end user because this approach lets an application display data in parts to the end user as soon as the data arrives.
  12. Strong back-end connectivity. From its inception, Flex has featured excellent support for popular back-end technologies such as the Java Platform Enterprise Edition, the Microsoft .NET platform, Cold Fusion, and PHP. This connectivity support has helped fuel the rate of Flex adoption for the client-side layer.
  13. Rich Framework. Flex offers a robust framework for component development and contains many off-the-shelf components already built-in for the developer's convenience. This facilitates rapid development of projects and timely deliveries.
  14. Debugging and Editor Support. Adobe has made an extremely smart decision by developing a robust Eclipse-based editor called Flex Builder, which greatly simplifies developing and debugging Flex applications.

This combination of features makes Flex a compelling choice for creating UIs now and for the foreseeable future. Another big plus for developers using Flex: You can create some interesting solutions aggregating and reusing already built components, which fits well within the spirit of Web 2.0. However, things get more difficult if you want to build new custom components from scratch. The power that Flex provides might inspire you to want to create rich, beautiful components that have thus far been solely the province of desktop applications you build with libraries such as openGL. Think about the possibilities of writing a gaming engine on the Web that builds on the concepts of the Electronic Arts racing game, Need for Speed. Or you might want to build an ISP topology with live views of data flows and link usages. I think you will see applications like these and many more in Flex's future, but the market and toolset aren't quite there yet. In any case, enterprise businesses have a lot riding on their Web-based UIs, and they can benefit tremendously from the power of Flex.


Similarities to the Java programming language

Adobe built Flex on top of its Flash Player, which is a proven technology for rendering rich UIs. Flash Player as it exists now is well suited for graphics designers, simplifying how you create MovieClips, time lines, banner ads, and so on. However, Flash Player applications aren't so appealing for enterprise developers who are accustomed to developing applications with object-oriented design and components that extend other components. On the other hand, Flash Player's native language is ActionScript, an ECMA-compliant scripting language that resembles JavaScript, and designers tend to be more attached to XML-tag-based languages. Adobe noticed this gap quickly and bridged it by introducing MXML. Adobe's approach with Flex resembles the approach Sun™ took with Servlets and JSP technologies. Code written in MXML gets translated to ActionScript code, which in turn gets compiled into something called Action Byte Code (ABC)—not unlike what happens with the Java Byte Code process. ABC is packed into the binary SWF format, which is delivered when users request it over a Web browser. These SWF-based files can run locally as well as in a stand-alone Flash Player plug-in. However, the Flash Security model doesn't allow local files to access network resources and vice versa.

Robust security model

This security precaution means an application delivered over a network cannot access a local client's file system—a capability that garnered Java Applets a significant amount of notoriety. Just as a JVM requires a main method, a Flex application requires a component that extends mx.core.Application as the entry point. Like a Java application running in a JVM, the SWF file eventually runs in a virtual machine called ActionScript Virtual Machine (AVM). Table 1 displays a short comparison of the similarities of the virtual machines for the Java programming language and Flex.

Table 1. Java versus Flex VMs
FlexJava
MXML tags and components JSP tags and components
Translated as Action Script Files Translated as Servlets Classes
Compiling to ABC (ActionScript Byte Code) Compiled to JBC (Java Byte Code)
End result is a .swf file End result is a .class file
Runs in ActionScript Virtual Machine Runs in Java Virtual Machine

Class hierarchy in Flex

Flex includes too many classes to try to cover them all in the space of a single article. However, it is instructive to take a closer look at a few of them for the insight they offer into the Flex object model. Figure 1 shows the core class hierarchy:

Figure 1. Flex's core class hierarchy
A diagram shows the flow of the Flex core class heirarchy: DisplayObject -> InteractveObject -> DisplayObjectContainer -> Sprite -> FlexSprite -> UIComponent

The top-level class is DisplayObject, which Flex adds to the Stage object, or the universal display list, of Flash Player. InteractiveObject handles user interaction, including keyboard and mouse events. DisplayObjectContainer lets you add and move around child components to any desired location within bounds. Sprite and FlexSprite are the base classes that don't require a time line, and you can extend these to write your own custom classes. However, the real hero in this set of classes is the UIComponent class, which provides the basic framework you need to write custom components. It is this class that you typically extend to write custom components, and this article's sample project extends only from this class.

Flex adds all components you create as children (and grandchildren) of a single universal display list that is ultimately handled by Flash Player. Flex's architecture hides these elaborate details from you, but it does give you hooks into event listeners that let you process events at an advanced stage. Flash Player's event dispatch mechanism is robust, and it can handle tens of thousands of events.


Building a simple Flex component

So far, I've covered the main benefits Flex brings to Web development, its basic architecture, and some of its core classes. But all of that is only a prelude to what truly matters as a developer: writing code and building solutions. Creating a simple component in Flex is remarkably easy. Assume you want to create a pair of text-input boxes, where typing text into either input box displays the text the user enters in both text boxes. You can create such a component without writing a single line of event-handling code. Your simple UI looks like Figure 2.

Figure 2. Two interactive text boxes
Two blank text boxes are stacked vertically.

Listing 1 displays the handful of lines of code that create this simple application:

Listing 1. Create a simple Flex UI
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">

<mx:TextInput id="box1" text="{box2.text"}>

<mx:TextInput id="box2" text={"box1.text}

</mx:Application>

This code is as simple (and clean) as code gets, but don't be fooled: Quite a bit happens in these few lines. Feel free to compile this code with the compiler flag keep-generated-actionscript=true if you want to see all the things that Flex does on your behalf. The Application container (look at the mx:Application tag) and the TextInput control hide a fair amount of complex code from you. This is fine as long as your boss is happy with you extending, reusing, and aggregating already existing Flex components. However, things get more complicated quickly once you start writing your own components from scratch. Building custom components highlights just how much Flex does for you automatically; many of the Flex features you've come to appreciate don't exist in your bare-bones Flex component.

Reasons to create custom components

As Flex matures, I believe you'll see more developers begin writing their own custom Flex components for two reasons:

  1. Most Flex components appeal to a broad, general audience, and existing controls might not meet your specific requirements. Skinning and extending existing components can help you fill in some gaps, but it becomes imperative that you write your own component if you want to affect a fundamental change in a given component's behavior.
  2. Many new visualization paradigms require that you present data in novel formats that are either under-supported or don't exist yet. You'll need to supply that functionality yourself if you hope to take advantage of these new formats in your applications.

A Flex component's life cycle

Understanding the life cycle of Flex components is essential if you want to build your own custom components. The work horse that drives the Flex life cycle is LayoutManager. Consider this excerpt from the Flex APIs:

"LayoutManager is the engine behind Flex's measurement and layout strategy. Layout is performed in three phases: commit, measurement, and layout."


Inside the commit phase

The first phase, commit, occurs when Flex adds a component to the main display list (hereafter referred to as "stage") using addChild() or one of its variants in the DisplayObject class. Note that Flex doesn't add a component directly to this list; several intermediate steps occur before that happens. First, Flex validates all the properties of the component. LayoutManager calls validateProperties() on the parent object and then each of the child objects iteratively. This is called top-down ordering. Should there be "dirty" properties, LayoutManager allows you to commit these before validation occurs. You do this by calling the commitProperties() protected method in UIComponent. A custom component must override this method to commit properties before validation happens. Examining the Label control can help you appreciate better what's going on in this case. A label is a text field that renders one line of text in an application. You're able to change several values in a label, including its font, font size, font color, font family, its text, and so on. Making any font-related changes in a label requires that you provide a new font context and a new UITextField that correspond to your new font; this requirement helps ensure that your new font is rendered correctly.

How Flex handles commit changes

Now consider what happens when an application changes a label's font. Keep in mind that Flex's UI relies on asynchronous processing, so any changes made might not take effect immediately. When you change a font, you can create a new UITextField at the same time. Or, you can store the new font context and a flag indicating that fonts have changed. The next time the component is rendered (typically on next screen refresh), you can apply the font changes then. Simply check whether the flag indicating a change in font status has changed and then create the corresponding UITextField at that time. It's possible for these properties to change multiple times between two screen refreshes, which makes it inefficient to process such changes immediately. A better strategy is to wait until the time comes for the next screen refresh, and apply the changes then. Note that the end user doesn't notice anything because the screen doesn't refresh any more often than that. This is a typical use case for any asynchronous, event-driven UI. The application stores the changes that need to be applied, queues them up, and, when the appropriate time comes, applies those changes. You might wonder how you know when the appropriate time is at hand. That is precisely what LayoutManager handles. When you receive a callback to a component's commitProperties(), you can be sure that this is the FINAL call before the component is rendered again; all other changes to properties after this call won't appear in the imminent screen refresh. Those changes must wait for another screen refresh and another call to commitProperties().

All that's fine, but you might wonder how you tell LayoutManager to perform validation against a component. Assume someone changed the font of the label, that you stored the new font and the related flag, and now you want LayoutManager to invoke the validation phase again. You do this by invoking a method called invalidateProperties() for the component in question. Now let's look at the code required to change the text of a label. Listing 2 shows you how to handle the validation phase (with some code omitted for clarity).

Listing 2. Handle the validation phase
public function set
    text(value:String): void {

    _text = value;

    textChanged = true

    invalidateProperties();

}

Note that _text is the field where you temporarily store the new text value, while textChanged is the flag that you set to mark this change. Also note the call you make to invalidateProperties() to inform the LayoutManager to begin validation phase. Listing 3 shows the corresponding code snippet in the commitProperties() of the Label class (note that I've omitted some code for the sake of clarity):

Listing 3. Use commitProperties
override protected function
    commitProperties():void{

if (textChanged) {

    textField.text = _text;

    textChanged = false

    }

This overridden method illustrates how, if the flag is marked true, the real UITextField (textField) is updated, and the flag is reset to false for future markings. This is a critical point to understand if you want to use Flex to create your own controls. The validation phase covered in Listing 2 and Listing 3 represents one of the three fundamental pillars of the Flex rendering engine. It is also a widely ignored phase, and it's possible you will see many advanced components written that never touch on the validation phase. This neglect might be partly an efficiency consideration. It is likely that nothing will go wrong if you "apply" the changes right at the point when they are made, but it's important to understand that your changes could be unintentionally and unnecessarily applied multiple times, causing a small (or possibly large) performance issue in your Flex applications.

Developers typically write components in two contexts. In the first context, you write a component that is tightly bound to your application's use case. Often, this kind of app isn't general enough for reuse, and it's okay to suffer a little efficiency loss here and there instead of making your component overly complicated. In the second context, you write a component for use at the framework level or by other developers. In the second context, it pays to make your component as efficient as possible. Taking care of validation phase probably belongs to the second context.


Step through the measurement phase

The measurement phase is the another important Flex phase, one that is more commonly used than the commit phase. Once a component has been validated, LayoutManager proceeds to measure that component so it can display it in a particular context. For example, LayoutManager might display it as the child of some container. You need the measurement phase because it helps LayoutManager decide whether there should be scroll bars on the parent container. Some components can resize themselves depending on how much space their children require to be properly visible. Consider the example of a VBox, which is a container that lays out its children vertically, one below the other. If you don't assign height or width values to your VBox, then it resizes every time you add a child to it. Its height is sufficient to contain the sizes of all the vertically laid children, unless you specify some constraints that make this impossible. Flex includes a protected method called measure() that you can use to measure your components. LayoutManager calls this method at the appropriate time (generally before next screen refresh), at which point you need to measure your component and set the height and width values using the measuredWidth and measuredHeight. The parent container then uses these properties to measure itself.

Measure the children first

In contrast to the validation phase, the measurement phase must happen in bottom-up fashion. This is because the children must be measured before the parent can be measured. This ensures that when the parent container receives a call for a measurement, its children will have been measured already. So far, so good. But now assume you specify a component's height and width explicitly. Flex assigns these values stored as the explicitWidth or explicitHeight properties. Flex respects the width and height values that you specify in such a case. However, sometimes setting explicit dimensions might not be wise for a container because you might not be able to foresee the combined size of all a container's children. In such a case, Flex adds scroll bars to a container for the children that go beyond the specified boundaries. Note that these scroll bars appear only for those containers that extend the Container class. For other controls, scroll bars might appear or Flex might chop the content area (a process called content clipping) to maintain the dimensions you specify.

Set properties values explicitly

Setting width and height values explicitly leads to an interesting issue. If you're writing a component that's going to contain other components and you need to measure the container's size, it would help to know the width and height of each child. You might wonder how you can know whether a particular component's dimensions have been specified explicitly or have been measured by overriding the measured method. Recall that measured dimensions are contained in properties like measuredWidth and measuredHeight, while explicitly defined dimensions are in explicitWidth and explicitHeight. Only one pair of measurements will contain real values, while the other pair will contain Not-a-Numbers (NaNs). Flex solves this riddle by providing two methods: getExplicitOrMeasuredWidth() and getExplicitOrMeasuredHeight(). You need to call these methods only, and you don't need to worry about whether the height and width values have been measured or set explicitly. Once a component has been measured, you need to call the setActualSize() method, which sets the width and height properties. These properties get set anyway if the height and width values were set explicitly. Listing 4 illustrates how straightforward it is to create a text-input component with explicit width and height values. Listing 4 accepts four properties:

Listing 4. Input width and height
<mx:TextInput width="200" height="45"/>

Four properties are set in this case: width, height, explicitWidth, and explicitHeight. Note that measuredWidth and measuredHeight remain as NaNs.

Now consider Listing 5, where you don't set any explicit height or width values:

Listing 5. Let the component measure
<mx:TextInput />

In this example, the component itself provides the measurement. Here, as in other areas of the Flex framework, "default" width and height values get assigned as measuredWidth and measuredHeight. The explicit values remain as NaNs. In any case, getExplicitOrMeasuredWidth() and getExplicitOrMeasuredHeight() return the correct values. In a similar vein, Flex sets minimum dimensions for explicitMinWidth or measuredMaxWidth, which provide min and max bounds for a component. Scroll bars appear to show the content that's not visible with the current view area once the size of a component grows bigger than maxWidth. Note that LayoutManager makes no calls to measure() when setting explicit dimensions. That makes sense because components don't need to be measured again once you define the measurements explicitly.

The call to invalidateSize() tells LayoutManager to initiate the measurement phase. LayoutManager keeps three separate queues: invalidatePropertiesQueue, invalidateSizeQueue, and invalidateDisplayListQueue. The queues correspond to components that call invalidateProperties(), invalidateSize(), and invalidateDisplayList() at some point of time in their life cycles. LayoutManager then processes each component from each of these queues and calls the validateProperties(), validateSize(), and validateDisplayList() methods on each of the components. The default implementation of these methods in UIComponent then calls commitProperties(), measure(), and updateDisplayList(). You can override each of these methods to write custom logic.

Measuring a component can also require measuring text. However, text behaves differently than other controls. Measuring text requires that you consider things such as ascent, descent, leading, gutter clearance, and so on. Fortunately, Flex provides a utility to simplify this process. UIComponent exposes methods called measureText() and measureHtmlText() to help you measure text controls. Listing 6 shows you how to measure a text field:

Listing 6. Measure a text field
package components {
    import flash.text.TextLineMetrics;
    import mx.core.UIComponent;

    public class MyLabel extends UIComponent {
        private var _text : String;

        public function set text(value : 
            String) : void {

        _text = value;
        invalidateSize()
        }

        public function get text() : String {
            return _text
        }

        override protected function measure(): void {
            if (!_text) return super.measure();
            //measure text here!
            var textLineMetrics : TextLineMetrics = 
                super.measureText(_text);
            super.setActualSize(textLineMetrics.width,
                textLineMetrics.height);
        }

This code creates a simple MyLabel class that extends UIComponent, so you can concentrate solely on the measurement phase. Note that you call invalidateSize() when text is set, which signals LayoutManager to add your component to its invalidateSizeQueue() during the next screen refresh. LayoutManager then calls the validateSize() method of your component, which is defined in UIComponent. Eventually, validateSize() calls measure(), which you have overridden. That call gives you a chance to set the size of your control. You could simply set width to 200 and height to 45, and then accept the consequences. All instances of your MyLabel class would share the same size, an unattractive proposal that somewhat defeats the point of using a robust UI development tool like Flex in the first place. But if you do go with fixed sizes, you don't have to override the measure() method, and you can even define the width and height when adding the component to its parent. Listing 7 shows a couple variations of acquiring measurements for label controls.

Listing 7. Get label measurements
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" 
    layout="vertical"
xmlns:components="article1.components.*">
    <components:MyLabel id="label1"/>
    <components:MyLabel ="label2" width="200"  
        height="50"/>
</mx:Application>

In this example, label1 would have its measure() method called because you don't define the size explicitly. However, measure isn't called in the case of label2 because you define that control's size explicitly. Note that the measure() implementation in Flex's Label class does much more than call measureText(). You need to take care of paddings on the left, right, top, and bottom. However, the basic philosophy remains the same.

The measurement phase is an important phase of a Flex component's life cycle. It is a critically important phase, but you seldom need to focus on this phase unless you're writing your components from scratch. Most custom Flex components will probably ignore this phase and leave it to Flex to take care of. Flex does an excellent job of taking care of these kinds of nuances and spares you from doing tedious measurement tasks. However, you need a good understanding of this phase if you want to write brand new components for the Flex framework.


Layout phase: Where the action happens

The layout phase is the third of the Flex framework's three life cycle phases. You spend so much time in this phase that it's hard to think of it as a phase at all. All graphics work, including skinning and art-based enhancements, occur in this phase.

Assume you have a component that is properly validated and measured. The next step is to lay it out. The layout phase handles many aspects of a component. For example, you handle all background graphics during this phase. You also wrap any components that need skins in this phase. This is also the phase for moving a component to a desired location. Consider again the case of a VBox, a vertical-layout container. When you add children to it, you must move them below one another in a vertical orientation. That movement happens during the layout phase.

How the Flash coordinate system works

I'll walk you through how to move children into a VBox using Flex, but first let's backtrack a step and review the Flash Player coordinate system. The top left corner of any component is represented by x and y values. Each component's x and y values are relative to its immediate parent. The x axis is positive in the right direction, while the y axis is positive in the downward direction (again, this is in contrast to its traditional upward direction). So, if you place a label inside a VBox and its x and y values are 30 and 40 respectively, the label's top left corner is 30 pixels to the right and 40 pixels down from the top left corner of VBox itself. Each component has mouseX and mouseY properties, which tell you the relative location of the mouse pointer with respect to the component. For example, if mouseX is -46 and mouseY is 78, the mouse pointer is 46 pixels left of and 78 pixels down in the component's coordinate system. In addition to its local coordinate system, Flex also features a global-coordinate system that measures the x and y coordinates from the top left corner of entire application. These coordinates are called globalX and globalY.

Flex manages many changes

During its life cycle, a component undergoes a series of translations, rotations, scaling, shearing, and stretching before it's finally displayed on the screen. Each component has a matrix property associated with it; this property holds information about these adjustments. Therefore, x and y and globalX and globalY are related to each other through these matrix transformations. Getting x and y typically involves applying matrix transformations to globalX and globalY. Getting globalX and globalY values from x and y typically requires performing reverse transformations. Conveniently, Flex hides all these details and gives you two methods that simplify carrying out these transformations: localToGlobal()and globalToLocal(). You don't need to worry about the underlying transformations to use these methods flawlessly. There is one important caveat, though: Functions work at a point level, not a component level. Let's look at an example that relies on the VBox again. Listing 8 creates a label inside a VBox:

Listing 8. Create a label inside a VBox
<mx:VBox id="box">
    <mx:Label id="myLabel" text="I am a label"/>
</mx:VBox>

Both box and myLabel have localToGlobal() and its counterpart available. Assume you want to get the global location of label. Your first thought might be to use the localToGlobal of the label's parent because x and y of the label are relative to the x and y of its parent, box. However, myLabel also has the same method available, so you might as well invoke that. It turns out that both approaches are correct, but you need to invoke them with different parameters. You can use either box.localToGlobal(new Point(myLabel.x,myLabel,y)), or myLabel.localToGlobal(new Point(0,0)). When you invoke box.localToGlobal(new Point(myLabel.x,myLabel.y)), you're asking Flex to provide the global location of a point in VBox, the coordinates of which are myLabel.x and myLabel.y. Note that this has nothing to do with myLabel.

Get precise global coordinates

Assume myLabel's x and y coordinates are 40 and 30, respectively. In this case, you're asking for the precise global location of (40,30), which is a point in the VBox. The transformations that get applied here are those of VBox matrix. The top left corner in myLabel's coordinate system is at (0,0)—which is always true for any Flex component. This means that in the second option, where the coordinate system of myLabel is considered, you're asking for Flex to provide the global location of a point that is at (0,0) inmyLabel's coordinate system. The different approaches yield the same answer because they basically pick the same point from two different coordinate systems. Failing to understand that distinction could lead to errors and confusion down the road.

Add a tooltip

There are several important reasons that might require you to get a global position when using Flex. For example, consider a typical use case where you show a tooltip for a component. The tooltips are added to a Flex application's systemManager, so they reside in the global coordinate system. However, you need to show the tooltip for a component that lies in the local-coordinate system of its parent. Therefore, Flex picks up the local coordinates of a point near the bottom right corner of the component in question and applies "local to global" conversions to it. Finally, a tooltip component is drawn and added as a child of a tooltip for a component. The tooltips are added to a Flex application's systemManager, and then placed at the global point thus calculated. Consider another case where you display a menu after someone clicks on a control, such as the File menu in the top left corner of any browser. You cannot add such a menu as the child of anything related to the label showing the File option. For example, if you add the menu as a child of the label's parent, then Flex would place the control vertically beneath that point because that is VBox's default behavior. If you add the menu as a child of the label itself, then the menu might not show up at all because the label will measure the text that it holds and create a dimension that fits the text only. Instead, you need to add your menu to systemManager, where it behaves similarly to pop-up children controls. You can place controls you add to systemManager anywhere inside the entire application area. To place such a control exactly below the label, you need to translate the desired location to global coordinates. For example, the bottom point of a label is (0,label.height) in a label's coordinate system. You then translate this point using localToGlobal() of the label and then place the menu at that point. You place the menu using a move() method, which maps the top left corner of any component to the desired location. Listing 9 shows you how to add a menu to a label control.

Listing 9. Add a menu to a label
<?xml version="1.0" encoding="utf-8"?>
<mx:Label xmlns:mx="http://www.adobe.com/2006/mxml"
    click="showMenu()">

    <mx:Script>
        <![CDATA[
            import mx.controls.Menu;
            private function showMenu() : void {
                var m : Menu = new Menu();
                // populate the menu control with options:
                systemManager.addChild(m);
                var global : Point = localToGlobal(new 
                    Point(0,height));
                m.move(global.x,global.y);
            }
        ]] >
    </mx:Script>
</mx:Label>

Clicking the label in this sample calls an event handler, which creates a new Menu control, populates it (I omitted some of this code for the sake of clarity), and adds it to the systemManager as its child. This component shows up in systemManager's rawChildren list. Finally, this code moves the menu control to a global coordinate that is just below the label control. Note the translation that happens from the local-coordinate system to the global-coordinate system, enabling the appropriate placement of the control in question.

When working with containers, you need to worry about a third coordinate system in addition to the local and global systems. A container silently creates a content pane that holds a control's children. Therefore, a child added to a container is not placed in the local-coordinate system of a container, but in its content pane, which has its own coordinate system. You might wonder why there is a content pane in the first place, as well as why it's required. In a container, Flex needs to be able to add many things in addition to the container, such as scroll bars, borders, overlays, and so on. Such elements are added to the container, and the content pane takes care of child additions only. If a child isn't visible in the container's visible area due to scroll bars, it still exists in the content pane. You need some extra methods to convert that item from its local-coordinate system to its content-coordinate system. These additional methods are localToContent() and contentToLocal(). However, the content-coordinate system is generally used in the cases where container uses absolute positioning of its children.

Signal the layout phase

Like the commit and measurement phases, the layout phase is signaled by calling invalidateDisplayList() phase. This tells LayoutManager that the current display list is no longer valid and that it should be updated. Flex adds the component that signals LayoutManager to invalidatedDisplayListQueue and processes this instruction in the next layout UI update. LayoutManager calls validateDisplayList() on each component in the queue, and this method in turn calls a protected, updateDisplayList() method. You need to override this method after calling super.updateDisplayList() if you're creating a custom layout, and then substitute the desired logic. Pay special attention to the parameters that updateDisplayList() takes: unscaledWidth and unscaledHeight. These naming of these two values give you a good idea of where you need to place the content for this component. Together, the parameters define the bounds of the component in question. Note that they are unscaled bounds. Flex takes care of scaling later, once this method completes execution. The scaleX and scaleY properties allow you to change the scale of the vector drawing, if you so desire. By default, scaleX and scaleY have values of 1. Every component contains a protected graphics object, which Flex uses to perform vector drawing. The vector drawing is employed for a variety of tasks, such as filling the background color, border color, gradients, fills, and strokes. Vector-based drawing is a big topic in itself and beyond the scope of this article. Suffice it to say that this graphics object exposes methods like drawCircle(), which you can use to draw basic shapes and boundaries. For example, assume you want to extend the Flex Label control by creating a boundary and background color for it. Both of these options are unavailable in standard Label control implementation, but Listing 10 shows you how to do this:

Listing 10. Extend Flex's Label class
package article1.components {

import mx.controls.Label;

public class MyLabel extends Label {

override protected function updateDisplayList(
    unscaledWidth:Number, unscaledHeight:Number): void {
    super.updateDisplayList(unscaledWidth,unscaledHeight);
    graphics.lineStyle(1);
    graphics.beginFill(0xcccccc);
    graphics.drawRoundRect (0,0,unscaledWidth,unscaledHeight,5,5);
    graphics.endFill();

This code extends a Label class and overrides the updateDisplayList() method. In it, you draw a rounded rectangle, set the width to unscaledWidth, the height to unscaledHeight, and the parameters for corner rounding, ellipseWidth and ellipseHeight, to 5,5. Finally, this snippet paints the rectangle's background as grey. You might have noticed one thing the code doesn't do, which is call invalidateDisplayList(). This is unnecessary in the class that extends Flex's Label class because invalidations happen silently at many places in Flex. For example, check out the scope of the code in Listing 11, which is taken from the Label class the previous snippet extends:

Listing 11. Invalidation in the base class
// some code omitted for clarity

public function set text(value:String): void {
    _text = value;
    textChanged = true

    invalidateProperties();
    invalidateSize();
    invalidateDisplayList();

Note how Flex calls all three invalidation methods when the text for the label is set. This means you don't have to call them explicitly. It's helpful to understand why all three invalidations are called here. When text is set, the older text is "dirty" and the new text must be "committed"—hence, the commit phase. The new text can be larger or smaller than the older text, so the bounds must be remeasured. At some point, the component must also change some parts of its layout. For example, if the earlier label displayed HTML-formatted text and the new text is plain and simple, the layout must change accordingly. If the text is too big for the allowed size, then label might need to be truncated and a tooltip set. All these issues are handled in the updateDisplayList() (or layout) phase. Also, all three of these invalidations take place automatically whenever addChild() is called. The Flex APIs offer some insight into how the invalidation methods work:

"Invalidation methods rarely get called. In general, setting a property on a component automatically calls the appropriate invalidation method."

The take-home point here is that most of the components written in Flex signal the proper invalidation method whenever their properties are changed. This holds true as long as you're extending already existing Flex controls. But you need to remember to call the invalidation methods at the appropriate time if you create your own Flex controls from scratch.

Sequencing matters

The sequence in which these phases are called also tells you something about when you shouldn't call them. Consider a case where a developer is extending a Label and inadvertently chooses to set the text property in the updateDisplayList() method. The practical consequence of this could be gruesome. Setting the text property calls validateProperties(), which in turn calls commitProperties(), then invalidateDisplayList(), and, eventually, validateDisplayList() and updateDisplayList(). The end result is an infinite recursive loop, and Flash Player will hang, freezing the entire application and possibly the user's system, too. So be careful not to cross the boundaries of a phase. Fortunately, you can ensure your components respect phase boundaries by adhering to three simple rules:

  1. Deal with all the properties except those related to size and positioning in the commit phase. This includes setting styles, border layouts, corner radii, text, fonts, colors, and other, similar activities. Never change a property here that can call invalidateProperties() again.
  2. Deal only with size-related properties in the measurement phase. Never change a property here that can call invalidateSize() again.
  3. Deal with positioning, paddings, movements, vector drawings, bitmap fills, and so on in this phase. Never change any property here that can call invalidateDisplayList() again.

validateNow() requires special attention

Another method that warrants special mention is validateNow(). Sometimes when you create a component, you need to measure it immediately to get its size. Note that the component hasn't been added to the main display list (or stage) yet, so LayoutManager hasn't had a chance to perform measurements. Consider a scenario where you need to place an image at the extreme top right corner of a canvas. You create an image instance, and you move it to the right-hand corner. Then you do something like this: Image.move(canvas.width-image.width,canvas.y);

This snippet doesn't work because the image has just been created and never measured; hence, the image.width is zero. If you call image.validateNow() before calling image.move(), Flex invokes LayoutManager forcefully, prompting it to go through all three phases, including the measurement phase where the image gets measured properly, and width and height are assigned valid values. Fortunately, a Flex canvas employs something called constraint-based layout, which lets you specify a constraint for the image such as 'right=0'. This instruction basically means that whenever image is laid out in Flex's canvas, it will have a constraint that its distance from the canvas right edge should be zero. You can achieve this result by specifying right=0 and top=0 constraints for an image. Please bear in mind that validateNow() is a resource-intensive method, and you shouldn't call it unless you have a good reason for doing so.

Build a component from scratch

So far you've learned about the benefits of using Flex and the use cases where it makes sense, how to manipulate and extend Flex components, and you've drilled down on its component life cycle. The final step is to put the rendering features covered so far to use in a custom component that you build from scratch. I'll walk you through all the required steps, showing you how to build a tree chart that presents the hierarchy of an organization. Note that this sample app is filled with dummy data.


Build a node

Your first step is to make a node of the organization tree. Figure 3 shows how you can enhance the node's jazziness by putting a background image in it. It's not important which image editor you use to create such images, so use whatever you or your graphics designer feels comfortable with.

Figure 3. Add a background image
A pink swirly background image

Call your node class Node.as. Listing 12 creates a node element and uses Figure 3 to compose the node's background.

Listing 12. Create a node element
override protected function updateDisplayList(
    unscaledWidth:Number, unscaledHeight:Number): void {
    graphics.clear();
    var bitmapData : BitmapData = image.content[
        "bitmapData"] as BitmapData;

    graphics.beginBitmapFill(bitmapData);
    graphics.drawRect(0,0,bitmapData.width,bitmapData.height);
    graphics.endFill();
    graphics.beginFill(0xff0000,.3);
    graphics.drawRoundRect(-3,3,bitmapData.width+6,
        bitmapData.height+6,10,10);
    graphics.drawRoundRect(0,0,bitmapData.width,
        bitmapData.height,10,10);
    graphics.endFill();

The graphics.beginBitmapFill() method is what creates the bitmap fill as the background of your node element. Figure 4 shows you how Flex renders this component.

Figure 4. Flex renders an element node
The pink swirly background image bound in a blue-grey box

Listing 13 shows you how to add a roll-over effect to your component, so that the component shows a "focus" selection whenever a user rolls over the component:

Listing 13. Add a roll-over effect
public class Node extendsUIComponent {

    private var rollOver : Boolean;

    public function Node() {
        addEventListener(MouseEvent.ROLL_OVER,
                function(e : Event) : void {
            rollOver = true;
            invalidateDisplayList();
        });
        addEventListener(MouseEvent.ROLL_OUT,
                function(e : Event) : void {
            rollOver = false;
            invalidateDisplayList();
        });
    }

    override protected function 
            updateDisplayList(unscaledWidth:Number, 
            unscaledHeight:Number): void {
        if(rollOver) {
            graphics.beginFill(0x0000ff,.2);
            graphics.drawRoundRect(
                -10,-10,bitmapData.width+20,
                bitmapData.height+20,10,10);
            graphics.drawRoundRect(
                -3,-3,bitmapData.width+6,
                bitmapData.height+6,10,10);
            graphics.endFill();
        }
    }
}

In this code, you define a boolean variable rollover and attach event listeners for ROLL_OVER and ROLL_OUT. In the former case, you set rollover as true; in the latter case, you set rollover as false. Note that you invalidate the display list in either case because you want Flex to render (or remove) the focus rectangle around the box.

Next, you want to add some text to your application. For this step, you define a label inside the Node and override the createChildren() method to add that label as a child. You define a nodeName attribute in the Node class and in commitProperties(), and then populate the label's text field with the nodeName. Next, you override the measure() method and measure the height and width of the text using text-line metrics. You also measure the Node component here and set its measuredWidth and measuredHeight equal to the bitmapData that your embedded image contains. Listing 14 walks you through how to add text to your application:

Listing 14. Add text to your Flex application
private var _nodeName : String; 

private function set nodeName(
        nodeName : String) : void {
    _nodeName = nodeName;
    invalidateProperties();
}

override protected function  createChildren():void {
    text = new Text();
    addChild(text);
}

override protected function commitProperties(): void {
    super.commitProperties();>
    text.text = nodeName;
}

override protected function measure(): void {
    super.measure();
    var metrics : TextLineMetrics = measureText(nodeName);
    text.setActualSize(metrics.width+10,metrics.height+5);
    measuredHeight = image.height;
    measuredWidth = image.width;
}

Note how you invalidate the properties while you set the nodeName. You do this because you want to signal to LayoutManager that the properties have changed, and it's time to call commit. You set the real text of the label during the commit phase using the (commitProperties() method. Finally, Listing 15 shows you how to add your node element to the main application file:

Listing 15. Add node to the main file
<?xml version="1.0" encoding="utf-8"?>
<mx:Application 
    xmlns:mx="http://www.adobe.com/2006/mxml" 
    layout="vertical" 
    xmlns:components="article1.components.*">

    <components:Node nodeName="John Michael"/>
</mx:Application>

Figure 5 shows you the final version of the node element Flex control, complete with its rollover focus rectangle.

Figure 5. The finished node element
The pink swirly background image, bound by a light blue border and a blue-grey box with the text 'John Michael' in the center

Building a Link

You're almost finished. One of the final steps is to create a link to connect your nodes. Building a link is much easier than building a node. Listing 16 shows you how to extend from a UIComponent, override updateDisplayList(), and create lines using the graphics object of the link component.

Listing 16. Build a link
package article1.components {
    import mx.core.UIComponent;

    public class Link extends UIComponent {

        public var fromNode : Node;
        public var toNode : Node;
        override protected function updateDisplayList
                (unscaledWidth:Number,
                unscaledHeight:Number): void {
            graphics.clear();
            graphics.lineStyle(4,0x0000ff,.5);
            graphics.moveTo(fromNode.x+fromNode.width/2,
                fromNode.y+fromNode.height/2);
            graphics.lineTo(toNode.x+toNode.width/2,
                toNode.y+toNode.height/2);
        }
    }
}

You create a pure vector-based drawing here and render a line here from source node to destination node. Listing 17 shows you how to put together the nodes and links and create a tree chart.

Listing 17. Put everything together
<?xml version="1.0" encoding="utf-8"?>
<mx:Application 
    xmlns:mx="http://www.adobe.com/2006/mxml" 
    layout="absolute" xmlns:components="article1.components.* ">

    <components:Link fromNode="{node1}" 
        toNode="{node2}"/>
    <components:Link fromNode="{node2}" 
        toNode="{node3}"/>
    <components:Link fromNode="{node1}" 
        toNode="{node3}"/>
    <components:Node id="node1" 
        nodeName="John Michael" 
        x="400" y="300"/>
    <components:Node id="node2" 
        nodeName="Harris Scott" 
        x="700" y="600"/>
    <components:Node id="node3" 
        nodeName="Paul Simpson" 
        x="100" y="600"/>
</mx:Application>

And finally, Figure 6 shows the finished version of your custom Flex organizational tree view application.

Figure 6. The final UI
A diagram shows a blue-grey background with three boxes arranged in a triangle and joined by light blue lines. The box at the top of the triangle is the swirly pink backround with the text 'John Michael' centered inside. The box at the bottom left of the triangle is the swirly pink background with the text 'Paul Simpson' centered inside. The box at the bottom right of the triangle is the swirly pink background with the text 'Harris Scott' centered inside.

You can download the complete source for the custom Flex component described in this article from the Download section.

At this point, you've created a tree chart component with three nodes. Adding more nodes and links is a simple matter of having the requisite data and is unrelated to the Flex rendering engine per se, so I'll leave the rest to you. You can use this kind of component for expressing the kinds of relationships you find in an organization chart, a router-link topology, and similar kinds of applications. Even if you don't need to create that kind of application, you can use the techniques and samples discussed in this article to build extendable and custom Flex components that serve your specific business needs.


The next step

This article covered Flex's rendering engine in some depth, including how you extend the built-in Flex components and how you create your own Flex components from scratch. But there's so much more to Flex to explore and understand. For example, the layout phase is an important part of Flex component's life cycle, but there are other important phases, too, including the initialization phase and creation phases. The Flex Language Reference (see Resources) is a great place to learn about what Flex offers in general. Understanding the event-dispatch model is also important when it comes to building sophisticated RIAs. Finally, you can find many blogs and other Web sites that showcase amazing Flex components, often with source code—I've listed some of these in the resources section as well.


Download

DescriptionNameSize
sample codesample.zip575KB

Resources

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Web development on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development
ArticleID=413666
ArticleTitle=Create a Flex component
publish-date=07282009