Home > Articles > Web Development

  • Print
  • + Share This
This chapter is from the book

Understanding the Structure

Now, you have a clearer understanding of what the component life cycle consists of. The next step is to understand how it actually moves through these phases and what each of the component methods actually does. After you have a clear understanding of that, you can quickly create the core structure of your component without thinking about it. However, to avoid running before you can walk, you need to know what you can harness within each method, so you can place your component code in the correct place to avoid potential pitfalls.

The obvious place to start is the component constructor. Although not strictly part of the component life cycle, it is the initialization point of all classes—including components—by invoking the class's constructor via the new keyword. Don't skip the constructor section, though; you need to adhere to some specific rules that differ from a standard ActionScript class. That said, let's see what those differences are.

Component Constructor

The component constructor is slightly different from normal class constructors because you don't create it with any parameters. There are a few good reasons for this. First, your component is more likely to be declared in MXML, where you cannot pass parameters into the constructor of your component. Second, if another component instantiates your component purely by interface or by base class type (UIComponent, for example), there is no way for you to cleanly set those parameters. Although you could provide these parameters and simply give them all default values, as the following example shows, this is a poor design approach that's better handled within the constructor and associated component methods via the components properties and accessors/mutators (getter/setters).

private var _property1:Number;
private var _property2:Number;

// An incorrect implementation of the property
// variables within the component constructor.
public function MyComponent(property1:Number=100, property2:Number=100)
{
  _property1 = property1;
  _property2 = property2;
}

// The correct approach
public function MyComponent()
{
  _property1 = 100;
  _property2 = 100;
}

As you can see, the second example sets the values of two variables when the component is instantiated. This is the preferred method by which you would assign internal values to your "startup" variables. Now this may sound all well and good for internal values, but what if you want to provide external values and have those applied to your component during the startup phases? Well, again, the Flex component framework provides us with a method to achieve this, commitProperties(), which is covered in a bit. For now, remember that the constructor is where you instantiate your variables with internally defined values.

After you have these values assigned, you need to know that they can be accessed. The Component framework takes care of this by dispatching a preinitialize event when the constructor finishes processing. It also does a few things in the background: It calculates any style values that have been assigned to your component and adds a reference to its parent. For example, if you placed this directly into your main MXML file, it would set this property to be a reference to Application. If it were placed inside a Panel component, it would be a reference to that.

Now, even though you should assign your variables needed for the initial startup of your component in the constructor, a few items shouldn't be declared here—namely anything that needs to be added to the DisplayList at startup. These objects need to be instantiated and added in a method called createChildren().

createChildren()

The createChildren() method is invoked directly after the constructor, after the component is added to the parent container via the addChild() method, be that implicitly by declaring the component in MXML within a parent tag or explicitly by using the parents addChild() method in ActionScript. Note that, in the case of container components that inherit from Group, you need to use the addElement() method instead of addChild(), because Group and its subclasses don't support addChild(). This is because they also support the adding and displaying of graphical elements that have been declared in FXG and those components that inherit from UIComponent or one of its subclasses. We look at FXG in Chapter 10, "Skinning and Styling."

The createChildren() method is where you should instantiate and add all your display objects required to get your component up and running with its default values and views. Like the constructor, the createChildren() method is invoked only once during the entire life cycle of your component, so it is ideally suited for the creation and adding of display objects that persist because you need to worry about this process only once. A good approach to take with the createChildren() method is if you have to create drawn objects as part of the initialization process of your component, separate these into individual methods and just invoke these during the createChildren() phase. This has two benefits: It keeps your code clean, and it enables a level of separation, so if you want to draw or process something again after the initial setup phases, you can easily access these methods.

There are a few exceptions to this rule of using createChildren() to add all your display objects to your component. What if you need to add something to the display that exists only if the user clicks or rolls over a bit of your component? Do you create those elements here or is there an alternative? I tend to create only those display items needed and use additional methods to create items when required and add them to the DisplayList at that point; but, as I said, you can do that or instantiate the objects in createChildren(), but don't add them to the DisplayList until you need them. Either way, keep in mind that you need to take the stack index of your children into consideration to ensure that they aren't obscured or masked from input when they finally get added; each item added to a particular DisplayList is effectively placed above the previous one (like a stack of magazines, the oldest being at the bottom and the most recent addition at the top).

commitProperties()

The commitProperties() method deals with any updates or alterations to the values within your component that have been influenced by external means; for example, setting the value of an attribute in a component's MXML tag like this:

<mx:Button label="Hello World" />

Here, the button's default value for its label property of Button has been replaced with my Hello World (I know, not original). What actually happens is that the value gets set within the Button component, which then triggers a request to the component framework to recommit the properties of the button with the new value. To do this, the button invokes its invalidateProperties() method, flagging that it needs to be updated in the next render phase because something has changed. When the next render phase happens, the commitProperties() method is executed and the value of the label property is set to Hello World.

If any of the display objects have also been altered or resized, additional invalidation calls may be required, so each one is flagged and they are all batched up to be invoked in turn within subsequent render phases. In the previous example, Hello World is longer than Button, so after the Button has assigned the new label value, it also calls the invalidateSize() method because the component needs to make sure it is still the correct size. The invalidateSize() method in turn calls the measure() and/or updateDisplayList() method.

measure()

The measure() method is slightly different from most of the other methods within the components life cycle. By this, I mean that it is invoked only if the width and height of your component are not provided with an actual value. Therefore, if you provide a width and height for your component when you place it in your application, measure() is completely ignored by Flex, even if you invoke the invalidateSize() method because Flex doesn't need to work out what size your component is because it is already defined.

For those of you who want to know what it actually does, here is the deal: When the measure() method is invoked, it checks to see whether the explicitHeight and explicitWidth properties return NaN (Not a Number); if they do, it then executes whatever code it contains to calculate the actual width and height of your component. Now, you may be rereading the previous sentence at this point; where did this explicitHeight and explicitWidth stuff come from? Well, the Flex framework has an extended selection of height and width properties, and all of them perform slightly different roles. To help clarify what each does, they are all listed in Table 3.1.

Table 3.1. Various Size Properties Within the Flex Framework

Type

Description

Notes

explicitHeight

explicitWidth

explicitMaxHeight

explicitMaxWidth

explicitMinHeight

explicitMinWidth

explicitHeight

explicitWidth

The explicit sizes are used by the components' containers, not the component, and they relate to various values, such as the actual, minimum, or maximum size of that component.

Used by the parent container to calculate both the size and position of your component within itself.

The component's container uses these values to calculate its size. If the parent of your component is a container-based component, these values are unlikely to be factored.

Also, all these values are based on the component's own coordinate system and are affected by scaling when applied in relation to their parent. Therefore, dimensions may not return identical values from the respective coordinate system.

maxHeight

maxWidth

minHeight

minWidth

Used by the parent container to calculate both the size and position of your component within itself.

The max and min size values are similar in nature to the explicit values.

These values are also affected by scaling when applied in relation to their parent and, as such, may return different values as calculated by their respective coordinate systems.

percentHeight

percentWidth

Enable you to define a percentage value (0–100) for your component.

If you set the height, width, explicitHeight, or explicitWidth, these values are reset and ignored.

measuredHeight

measuredWidth

measuredMinHeight

meaasuredMinWidth

The properties you use to define the default sizes for your component. They are the only properties to do with sizing you should use in your measure() method.

You do not need to set any of these externally because they are automatically set via the width and height properties.

height

width

The properties that you use to dictate the size of your component externally.

When either is set, it causes a resize event to be dispatched, which eventually results in your component recalculating its dimensions, if applicable.

Although you can place a percentage value in the MXML width (width="100%"), you cannot do this in ActionScript. You need to use the percentHeight or percentWidth if you want to achieve the same result.

As you can see, only a couple of them are actually used by your component as a basis for size calculation by the component itself. The others are part of the framework and, for the most part, happily trot alongside your component. I will point out the percentHeight and percentWidth attributes, however, because these are not exposed within MXML. This is because the width and height attributes of MXML are clever enough to work out the data type when it is applied and, therefore, enable you to set a percentage value for that property. After all, every attribute in MXML is basically a string. ActionScript, on the other hand, isn't as forgiving, so if you want to set percentage values for your components in ActionScript, you need to use the percentHeight and percentWidth properties as required. I know that was a bit of a slog, but you'd have come across all those different sizing options at some point if you hadn't already, so it was worth getting it out of the way where it has some relevance.

Given all that preceding information, how do you actually set the size of your component? Well, you have a few options. First, you can obviously set the default width and height of your component. If you don't supply a value for either or both of these, they default to 0, effectively making your component invisible. I personally wouldn't use this as a mechanism to set the visibility of your component because you are likely to have child components that may size incorrectly if they are relying on their parents' dimensions. The second thing you can do is set the minimum width and height of your component; this has the benefit of letting Flex know that you don't want your component to go below a certain size—this is especially useful if you have a complex layout that doesn't degrade gracefully or just becomes completely unusable if it goes below a certain size.

override protected function measure():void
{
  measuredHeight = 200;
  measuredWidth = 200;
  measuredMinHeight = 25;
  measuredMinWidth = 100;
}

Be warned: This isn't set in stone, and there are certain instances in which external factors can override and even ignore this.

For the most part, you are likely to set these properties to the same value, as the following code shows. Either way, the main use of the measure() method is to set the default dimensions of your component so that another developer doesn't need to provide initial values just to get your component to display in his application.

The upshot is that these values are used when you drop a component from the Component Panel within Flex Builder onto the design view. The vast majority of the control components display at a default size for that specific component type as defined in their respective measure() methods.

override protected function measure():void
{
  measuredHeight = measuredMinHeight = 200;
  measuredWidth = measuredMinWidth = 100;
}

Now that we looked at resizing our component with measure(), let's look at updating those visual elements that may have been altered by resizing your component through external influences, such as if a developer supplies a specific width and/or height or via its children.

layoutChrome()

It is worth pointing out that layoutChrome() is a Halo-specific method. It has no use or equivalent within the Spark framework because of the life cycle Spark components have for their skins—something that you'll get a better understanding of in Chapter 10. With that in mind, I included it in this chapter for completeness; if you are interested in only developing components based on the Spark framework, you can skip this section. For those who want to work with the older Halo component framework (and some components are still provided only as Halo versions), read on.

The concept behind the layoutChrome() method is that container styling and visuals should be kept separate from content. For the most part, there isn't a huge need to have them separated because you can manage their updating together and everything is fine. However, if the container's autoLayout property is set to false, you could have a problem because when autoLayout is set to true, recalculation of the size and position of your component, and all its children, is performed whenever a child component's position or dimensions change. So, if the container changes size, or its contents do, they can both expand or contract to accommodate the changes if that is how they've been configured. That's why this is the default value in the vast majority of Flex container components.

If autoLayout is set to false, Flex updates only the measurement and/or the position when a child is added or removed—not too helpful for our border if it were clumped in with all the other layout code because it could cause unforeseen layout issues if it updated itself only when this happened.

Fortunately, Flex continues to invoke the layoutChrome() method even if autoLayout is set to false. So, as long as you put all your container's chrome within the layoutChrome() method, it updates correctly whenever the position or size changes, regardless of whether the rest of the content is set to update. But don't be tempted to throw all your visual element controls into a layoutChrome() method just for the sake of it. Most components don't require this because they update themselves and their children all at once; however, if you create a container-style component that may or may not be based on a template component, this process comes into its own.

updateDisplayList()

You've seen how to apply values to your component's properties and set the size, and for Halo-based components, you know how to manage the chrome of a container. The last core component method makes sure everything is cleaned up and ready to go because it is in charge of updating the display status of your component: updateDisplayList().

The updateDisplayList() method is the final step before your component actually appears within your application; it is responsible for setting any child component properties, sizing, and positioning elements as required. In addition, it is responsible for applying any graphical style elements, including any nonchrome skins. Also, at this point, the parent container calculates the component's final size, if it has been set externally and/or altered by its children, before setting its visibility to true.

For a method that does so much, it has a short description, I know. There is one more thing that you need to know about updateDisplayList(), and it may have already been sitting at the back of your mind while you were reading about measure(). If measure() is invoked only if you don't set the width and height, how do you set them after you have set your component's initial size? Well, it's probably no surprise that you do that here, within updateDisplayList(). Of all the core methods, updateDisplayList() is the only one that accepts parameters, as you can see in the following code line. But, like all the preceding methods, it shouldn't be directly invoked by you, the developer; this should be left to the Flex component framework via the relevant invalidation call.

updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void

These two values are passed to the component as part of the invalidateDisplayList() call and are set to the component's coordinate system, not the parents. The reason for this is that unscaledWidth and unscaledHeight values do not include any scaling, as the name implies, that may have been applied to the component. So, aim to use these values when performing update calculations that affect your component and its children, not width and height, as you might think as these do have the scaling modifiers applied.

  • + Share This
  • 🔖 Save To Your Account