This article is based on Flex4 in Action, published on 10-November-2010. It is being reproduced here by permission from Manning Publications. Manning publishes MEAP (Manning Early Access Program,) ebooks and pbooks. MEAPs are sold exclusively through Manning.com. All print book purchases include an ebook free of charge. When mobile formats become available all customers will be contacted and upgraded. Visit Manning.com for more information.
also read:
Flex 4 Components ExposedThis article is taken from the book Flex 4 in Action. The authors discuss Spark component architecture and explain how to create custom components. The article also features skinning with the SparkSkin Object.
You’ve used components since the start of this book. They range from controls like the simple Button that accept input from the user, to container components like the VBox. Custom components are “home-brewed” so to speak, and are created by extending the same base classes as the default components that are part of the Flex framework.
Now we’ll tell you about the underlying foundation of the Spark component architecture in Flex 4. Most importantly, you will learn key facets of that are specific to the Flex 4 Spark architecture. This will get you on your way toward building your own collection of Spark-based components that take advantage of Spark architecture.
Spark Component Architecture
If you’ve been a Flex developer for a little while now, or you’ve used a different framework for client side interface creation, you may have heard of the conceptual Model-View-Controller (MVC) architecture. The Flex 4 Spark component architecture puts a little spin on the MVC pattern, and adds a fourth part – the skin. This means that every Spark-based component has the following four pieces to it:
- Model: Properties and business logic are contained here, and are usually written in ActionScript. For the sake of best practice, you really should not be placing visual or behavioral logic along with the model. We will elaborate on that in a moment.
- View: Contains logic used mostly for the layout and positioning of the component on the stage during runtime. This information can be its own ActionScript class, but it is usually more logical to either: A) abstract the logic into a base class so it can be reused by other components, or B) add it to the component’s
controller class. - Controller: This is responsible for defining the behavior of the component, the states that are contained within it, and provides definitions of the sub-parts that are declared in the Skin class using variable names. This typically comes in the form of ActionScript.
- Skin: Declares all of the visual elements that make up the component. This is usually an MXML file (largely thanks to FXG), and is tightly coupled with the Controller.
There is currently a lot of debate on what we will explain next, and you will ultimately have to exercise best judgment using a combination of your logical reasoning skills, along with the knowledge that you are gaining in this article.
As you saw a moment ago, there are four pieces that make up the Spark component architecture. Where this gets confusing is that you will often only see two classes for these four parts, one in ActionScript and the other MXML. The ActionScript class typically wraps the model, view, and the controller pieces into a single class, while the MXML is strictly meant to act as the skin for the respective component. According to the Flex 4 documentation:
“The general rule is any code that is used by multiple skins belongs in the component class, and any code that is specific to a particular skin implementation lives in the skin.”
This makes sense in theory, but does not usually provide enough of what we often refer to as separation of concerns. This is why you might notice that the better Flex developers are using three layers for their components:
- Component: The controller and the view logic, often written in ActionScript
- Presentation Model: Contains the code for the model.
- Skin: The MXML skin file
With this method, it is reasonable to suggest that this takes things back to the core concept of the Model-View- Controller again; whereas the Component class is the controller, the presentation model class is the model, and the Skin becomes the view element of the MVC architecture.
Let’s put all this theory into practice by taking a look at some real-life situations.
The many flavors of custom components
Creating a custom component means extending a pre-existing class that either directly or indirectly extends one of the Flex framework classes. Picture three 2010 Ford Mustangs side-by-side. The first is the base Mustang Coupe with the standard V6 engine. The second is the Mustang GT with a 4.6-liter V8, upgraded wheels, and some other upgrades. The third is a Shelby Cobra, sporting 510-horsepower, and premium sound and navigation system among other upgrades. The second and third mustangs both extend the base mustang, so they inherit all of the properties of the first, and then add their own additional amenities.
Similarly, the Cobra extends the GT, so the Cobra inherits the properties of the GT and the standard Mustang, while adding its additional features. But wait! How could the Cobra and the GT inherit the properties of the standard Mustang if the engines are different? The answer is that the GT overrides the engine property of the standard Mustang, and the Cobra overrides the engine of the GT.The relationship between these three automobiles is illustrated in the simple class diagram shown in
Figure 1.
When you extend classes in the Flex framework, you inherit the properties, functions, events, and styles from the class you are extending. This makes code a lot more manageable and is one of the fundamental characteristics of object oriented programming and code reusability. Let’s take a look at two different component types.
Simple vs. Composite
There are two major types of custom components: simple and composite. A simple custom component extends a single, pre-existing component.
The fun doesn’t stop there though. When developing Flex applications, you will find it valuable to group simple components together, keeping your application better organized. You can create components that group disparate components in what’s called a composite component, which is a container that holds any number of additional components inside of it.
MXML vs. ActionScript
When developing custom components, you have the option of creating your component in either MXML or ActionScript. In most cases, either one will do. The main advantage of MXML is that you can usually write the code needed with fewer lines. However, some flexibility is lost in regard to object customization, so advanced custom components are almost always written as ActionScript classes.
Creating your component in MXML is often a popular choice for composite components that are not very advanced in nature. An excellent example of this is a shipping information form. It is useful to have a reusable shipping form component because there are many applications that it can be used for. This means that if the component is created with loose couplingin mind,it can be a highly reusable composite component that can be built using MXML.This approach is the easiest to get started with.
MIGRATION TIP
In Flex 3, MXML composite components were usually based on the Flex Canvas container from the Halo library.
In Flex 4, MXML composite components are created using the MXMLComponent base class or the Group class from the Spark library.
Your second option is to create your component in ActionScript. This technique is more advanced because it requires stronger ActionScript skills.You can override the functions of the component class you’re extending from, and you have fine-grained control.
The advantage of this method is that you have far greater power to turn the coolness factor on your custom component into overdrive.The disadvantage is that if you are building a composite component, you can no longer lay out the controls in Flash Builder’s design view. Advanced developers consider this a small price to pay for the increased flexibility, while others feel that it slows down the development process. Finding the formula that works best for you is just a matter of trial-and-error.
Now that you have learned about the options available, the first custom component you will build is of the simple type.
TIP
When it comes to making custom components, your goal should be to make it simple for other developers to use your component (even if it’s complicated inside). Later, we’ll give you tips on how to achieve this goal.
Creating simple custom components
At the most fundamental level, a viewable component (one which is added to the display list of your application) can be categorized according to the purpose served by the component. This includes: controls, containers, item renderers, effects, and skins.
The first custom component that you will build is an extension of the Flex ComboBox, and is considered a control because it is used to control application behavior by initiating an action or sequence of actions when the value is changed.
Build your own home-grown simple ComboBox
Let’s walk through a simple example. Say your application is geocentric, meaning that pieces of information tend to carry location data. As a result, a few forms require the user to specify an address of some sort. To help your users, you want to provide a drop-down menu of U.S. states, as figure 2 demonstrates.
Wouldn’t it be awesome if any part of your application could display this list by calling a single line of code? OK, that’s a loaded statement, but the example is as simple as this:
Listing 1 CBStates.mxml: ComboBox-based custom component
<?xml version="1.0" encoding="utf-8"?> <mx:ComboBox xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/halo"> <mx:dataProvider> <mx:Object stateCode="AK" label="Alaska"/> <mx:Object stateCode="AL" label="Alabama"/> <!-- the rest of the US states --> </mx:dataProvider> </mx:ComboBox>
In code listing 1, you are presented with an example of simple custom ComboBox control that shows a listing of U.S. states when clicked, and allows selection of a state to be submitted as a data object. This type of component is highly reusable, since it is often needed when creating an address submission form. Listing 2 demonstrates how the component is instantiated with MXML in a Flex application.
Listing 2 Main application file for CBStates
2 3 4 5 6 7 <?xml version="1.0"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:mx="library://ns.adobe.com/flex/halo" backgroundColor="white" xmlns:local="*"> <local:CBStates/> </s:Application>
If you save the two files to the same directory and then run the application, you should end up with a drop-down combo box that lists all the U.S. states, as shown earlier in Figure 1.
One advantage of extending pre-existing Flex user interface components is that anyone who uses it will know its general properties and behaviors as a result of inheritance. In this case, if they know your simple custom component is based on a ComboBox, then they also know your component supports the same properties, events, styles, and functions as the Flex ComboBox control. Now you will take what you just learned and relate it to the Flex 4 Spark library and architecture.
Simple Spark Components
A visual object on the stage is considered a Spark component when it inherits the Spark component architecture by extending a Spark visual component base class. This includes:
- SkinnableContainer
- SkinnableDataContainer
- Group
- DataGroup
- MXMLComponent
- ItemRenderer
- SkinnableComponent
- Any Spark library component (e.g. a Spark List)
- UIComponent
If you are familiar with Flex 3, you may be wondering about the last item in the list, UIComponent. Technically, all visual components in Flex are subclasses of UIComponent, regardless of the component library that the component comes from.
However, certain facets of a component that extends the base UIComponent class can make it a Spark component. For example, if an ActionScript composite component extends UIComponent as the base class, and then instantiates a Group or SkinnableContainer object to hold a set of display objects, then we would refer to it as a Spark composite component.
Most of the time, custom Spark-based components will extend the SkinnableComponent class, as you will see in the examples that will come later.But first, it’s time for some discussion on skins and how the SparkSkin object fits into the picture.
Migration Tip
Beware! One of the biggest compatibility issues that haunted the Flex 4 beta releases involved placing Spark components inside of a Canvas container. The problem reared its ugly head in some unusual ways, even after it was thought to have been resolved. In one of my own experiences, the problem caused compile errors within Flash Builder beta of type “unknown” and source “unknown”. The source of the problem turned out to be a SkinnableContainer that was instantiated inside of a Canvas. The moral of the story here is: if you find yourself debugging and feeling like you are chasing a ghost, consider checking your application to see if you have any Canvas objects before you start banging your head against the wall. If you do, try switching the Canvas to a Group. If that doesn’t fix the problem, we still suggest leaving the Group in place of the Canvas as we have generally found the Group container to be more reliable and functional than Canvas.
Skinning with the SparkSkin object
The decoupling of display logic is one of the most valuable architectural enhancements in the Spark library.
SparkSkin basics
Imagine you just built an application with a set of five custom components. If your five components each extend one of the classes listed earlier from the Spark library, you can create an unlimited number of skins for each of your five custom Spark-based components and keep them organized in an entirely separate package or library. You can then declare a different default skin for each instance of the same component, or even swap skins on the fly by triggering an event during runtime from a user initiated behavior or sequence. Isn’t that cool? Code listing 3 is an example of a Spark skin component.
Listing 3 Example of a skin for a Spark Button
<?xml version="1.0" encoding="utf-8"?> <s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009" #A xmlns:mx="library://ns.adobe.com/flex/halo" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:ai="http://ns.adobe.com/ai/2008" #B xmlns:d="http://ns.adobe.com/fxg/2008/dt" #C minWidth="21" minHeight="21"> <fx:Metadata> [HostComponent("spark.components.Button")] </fx:Metadata> <s:states> <s:State name="up"/> <s:State name="over"/> <s:State name="down"/> <s:State name="disabled"/> </s:states> <!-- FXG exported from Adobe Illustrator. --> <s:Graphic version="1.0" viewHeight="30" viewWidth="100" ai:appVersion="14.0.0.367" d:id="mainContainer"> <s:Group x="-0.296875" y="-0.5" d:id="firstGroup" d:type="layer" d:userLabel="Group One"> <s:Group d:id="secondGroup"> #D <s:Rect x="0.5" y="0.5" width="100" height="30" ai:knockout="0"> <s:fill> <s:LinearGradient x="0.5" y="15.5" scaleX="100" rotation="-0"> <s:GradientEntry color="#ffffff" ratio="0"/> <s:GradientEntry ratio="1"/> </s:LinearGradient> </s:fill> <s:stroke> <s:SolidColorStroke color="#0000ff" caps="none" weight="1" joints="miter" miterLimit="4"/> </s:stroke> </s:Rect> </s:Group> </s:Group> </s:Graphic> <s:Label id="labelElement" horizontalCenter="0" #E verticalCenter="1" left="10" right="10" top="15" bottom="2"> </s:Label> </s:SparkSkin> #A Skins ALWAYS extend SparkSkin in Flex 4 #B Namespace declaration for Illustrator graphics #C Namespace declaration for FXG graphics #D Group object followed by design #E Label declaration followed by style information
When you create Spark components, you will usually create two classes for every component. The component class holds behavioral logic, such as events that are dispatched by the component, the component’s data model, skin parts that are implemented by the skin class, and view states that the skin class supports.
The skin class on the other hand, is responsible for managing visual appearance of the component and visual sub components, including how everything is laid out and sized. It must also define the supported view states, graphics, and the data representation.
Migration Tip
FXG stands for “Flash XML Graphics”, and in essence, does for MXML what CSS did for HTML. However, FXG has quite a bit more power under the hood, so while its relationship to Flash and Flex components can be loosely compared to the relationship between CSS and HTML, it does not make logical sense to compare FXG directly to CSS. Many of the limitations imposed on CSS do not exist with FXG. Although it may seem strange to compare FXG to CSS, FXG theoretically accomplishes the same thing that CSS does: It separates layout structure and behavioral code from design and graphics code. This makes it easier to create components that can be quickly and easily integrated into any application without even having to look at the component’s code base.
Using Metadata to Bind Component Skins
The component and skin classes must both contain certain metadata in order for them to work properly together.
The component class must:
- Define the skin(s) that correspond to it
- Identify skin parts with the [SkinPart] metadata tag
- Identify view states that are supported by the component using the [SkinState] tag
The skin class must:
- Use the [HostComponent] metadata tag to specify the corresponding component
- Declare view states and define the appearance of each state
- Define the way skin parts should appear on the stage
Let’s take a look at the three basic essentials of a skin component in further detail, starting with the SkinState
metadata tag.
Migration Tip:
Note that skin parts must have the same name in both the skin class and the corresponding component class or your application will show compile errors or throw runtime errors.
CUSTOM COMPONENT VIEW STATES
The view states that are supported by a component and its corresponding skin must be defined by placing a [SkinState] tag for every view state. These tags are placed directly above the class declaration, as seen in Listing 4.
Listing 4 View states are defined directly above the class statement.
package { import spark.components.supportClasses.SkinnableComponent; [SkinState("default")] #A [SkinState("hover")] [SkinState("selected")] [SkinState("disabled")] public class SkinStateExample extends SkinnableComponent { public function SkinStateExample() { super(); } } } #A SkinState tags preceed the class declaration
You can now control the state of the component by setting the currentState property with the component
declaration in MXML, as seen in Listing 5.
Listing 5 Controlling custom component state
<?xml version="1.0" encoding="utf-8"?> <s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:mx="library://ns.adobe.com/flex/halo" xmlns:s="library://ns.adobe.com/flex/spark" xmlns:MyComp="myComponents.*"> <s:layout> <s:VerticalLayout/> </s:layout> <s:Label text="Login or Register" fontSize="14" fontWeight="bold"/> <MyComp:SkinStateExample currentState="default"/> #A </s:Application> #A Use currentState to set the component's state
While states are defined in a component’s implementation class and controlled in the skin, skin parts are defined in the MXML skin and controlled by the implementation class.
DEFINING SKIN PARTS
The importance of the [SkinPart] metadata tag is another critical facet of understanding the relationship between component classes and skin classes in the Spark architecture. As you will see in a moment, this metadata tag is especially useful for creating custom components.
The implementation of the SkinPart metadata is simple. First, define skin parts in the same way as you would declare objects in a standard MXML file. For example, if one of your skin parts was a Spark Button with an id property of myButton, you would define it with the code:
<s:Button id="myButton" width="100" height="20" x="0" y="0"/>
Next, declare the skin parts in the component using the [SkinPart] metadata tag. Make sure that the variable is typed the same as what you just defined in the MXML skin, and that the id of the object in the MXML skin matches the name of the variable in the component. The component code for the myButton skin part that you just saw is illustrated in Listing 6.
Listing 6 The SkinPart tag binds component variables to MXML skin part definitions
public class CustomComponent extends SkinnableComponent { public function CustomComponent() { super(); } [SkinPart(required="true")] #A public var myButton:Button; } #A SkinPart metadata binding on the myButton variable
Notice the use of the required property after the SkinPart metadata tag is declared. Make sure you declare this property because required is – pardon the pun – required.
You have now learned two of the three essentials to Spark skinning. The third essential element is tying the component implementation to the skin using the HostComponent metadata.
WARNING
In order for your skin part bindings to work, you must be sure to type the respective variable the same as the
MXML object definition and the variable must be named the same as the id attribute that is set on the respective
MXML object definition.
DECLARING THE HOST
The last of the three basic essentials to using skins with custom Spark components is the declaration of the host component in the skin. To accomplish this, you use the [HostComponent] metadata tag. An excellent example of this was provided in the example skin at the beginning of this article, in Listing 3. The code from listing 3 that is used for the declaration of the host component is provided again in Listing 7.
Listing 7 Use HostComponent metadata to bind a skin to a component class
<fx:Metadata> [HostComponent("spark.components.Button")] </fx:Metadata>