You have used many components while building the application to its current state. Every time you use an MXML tag, you are using a component. In fact, Flex is considered to be a component-based development model. In this lesson you’ll learn how to create your own components. The custom components you build will either extend functionality of the components that the Flex SDK provides or group functionality of several of those components together.
Up to this point, you did not have a way to divide your application into different files. The application file would continue to get longer and longer and become more difficult to build, debug, and maintain. It would also be very difficult for a team to work on one large application page. Components let you divide the application into modules, which you can develop and maintain separately. With careful planning, these components can become a reusable suite of application functionality.
You will learn two things in this lesson. The first is how to build components. You will learn the syntax and rules for creating and using custom components. Second, you will learn why you’d want to do this and how components can affect your overall application architecture. The “Introducing MXML Components” section provides an overview of how to build components. In the tasks throughout this lesson, you will reinforce your component-building skills and continue to learn more and more details about building custom components. You’ll start with a theoretical discussion of why you would want to use components. The rest of the lesson will use an architectural approach to implementing components.
All Flex components and all the components you will build are ActionScript classes. The base class for the visual components you have been using and the MXML components you will build in this lesson is UIComponent. In a hierarchy of components, UIComponent is at the top, and all the other components inherit from it.
These classes fall into general groupings based on their functionality, such as component, manager, and data service classes. In fact, UIComponent has itself inherited from a set of classes that provide functionality, such as event dispatching, interactivity, containment of other objects, and so on.
You can examine a complete description of the class hierarchy in the Flex ActionScript and MXML API reference, referred to as ASDoc.
When you build your own component, you basically want to do one of two things: add functionality to a predefined component, or group numerous components together.
The basic steps to build a component are as follows:
<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" >
</s:Group>
xmlns:views="views.*"
<views:UserForm/>
Now that you know the general approach to building a component, here is a simple example of adding functionality to a predefined component. Assume that you want to build a List that will automatically display three grocery categories. Your component will use <s:List>
as its root tag. Until now, all the MXML pages you’ve built use the <s:Application>
tag as the root tag. Components cannot use the <s:Application>
tag as the root tag because it can be used only once per application. Here are six steps for creating a simple component. Of course, using Flash Builder further simplifies the process by presenting a dialog to help you build a template of your component.
<?xml version="1.0" encoding="utf-8"?>
<s:List>
, you will use it as the root tag. Your skeleton component will appear as follows:<?xml version="1.0" encoding="utf-8"?>
<s:List xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<s:layout>
<s:VerticalLayout/>
</s:layout>
</s:List>
<s:String>
tags in the <s:List>
. You know you use the <s:dataProvider>
tag to supply data to an <s:List>
, so here is the finished component:<s:Application>
tag is xmlns:custom="myComps.*"
.You will see shortly that Flash Builder makes this process of creating the skeleton of the component even easier.
The CompTest.mxml output would appear as shown here.
You now know the basic mechanics of creating custom components. You might ask yourself, So now what? How does this affect what I have been doing? Why should I use them? How do I use them?
The advantages of components mentioned in the opening pages of this lesson should now be clearer:
• Components make applications easier to build, debug, and maintain.
• Components ease team development.
• With planning, components can lead to a suite of reusable code.
To facilitate using components as reusable code, you should make them independent of other code whenever possible. The components should operate as independent pieces of application logic, with a clear definition of what data must be passed into them and what data will be returned from them. The object-oriented programming term loosely coupled is used to describe this type of architecture.
Suppose you have a component that uses an <s:List>
to display some information. You later learn of a new component that offers a better way to display that data. If the custom component is built correctly, you should be able to switch the display component and not need to make any other changes. You change the inner workings of the custom component, but the data going into the component and what comes out will not change, so no changes to the rest of the application are needed.
Now, you need to think about how components fit into the application architecture. Although this book is not meant to be a discourse on Flex application architectures, it would be negligent not to show how components can fit into the bigger picture. In the application you are building in this book, you will implement a primitive form of model-view-controller (MVC) architecture.
MVC is a design pattern or software architecture that separates the application’s data, user interface, and control logic into three distinct groupings. The goal is to implement the logic so changes can be made to one portion of the application with minimal impact to the others. Here are some short definitions of the key terms:
• Model: The data the application uses. The model manages the data elements, responds to queries about its state, and manages instructions to change the data.
• View: The user interface. The view is responsible for presenting model data to the user and gathering information from the user.
• Controller: What responds to events—typically user events, but also system events. The controller interprets the events and invokes changes on the model and view.
Here is the general flow of the MVC architecture:
Consider the application you are building. Eventually your FlexGrocer.mxml main application page will be part of the controller. There will be views that do the following:
• Display the different grocery item categories
• Display the items in the shopping cart
• Display a detailed view of a particular grocery item
• Display all the grocery items in a particular category
Each of these views will require some logic for interactivity. In a strict MVC architecture this code is usually located in a separate class or classes. In this book, the code will exist alongside the views as our desire is to teach Flex, not strict MVC architecture. The model is provided by the data loaded via HTTPService classes, which will soon be moved to their own classes.
Now the stage is set, and you’re ready to get started building components and enhancing the architecture and functionality of the applications you are building.
This first exercise will improve the overall architecture of the application, but it will not add any functionality from the user’s point of view. In fact if everything is done properly, you’ll want the application to appear exactly as it did before you started. You will pull the application’s visual elements into a component, a view in terms of MVC architecture. FlexGrocer.mxml will begin to transform into the controller.
Alternatively, if you didn’t complete the previous lesson or your code is not functioning properly, you can import the FlexGrocer.fxp project from the Lesson09/start folder. Please refer to Appendix A for complete instructions on importing a project should you ever skip a lesson or if you ever have a code issue you cannot resolve.
It is a best practice to organize your components. In this case, the views folder will contain the views for your applications.
In this case, you are using an <s:Group>
as your root tag and are applying a HorizontalLayout to it, which means the children you insert in this component will be aligned horizontally beside each other.
<fx:Script>
block just after the closing </s:layout>
tag.You will have an <fx:Script>
block in this component. Some of the code you will copy from the FlexGrocer.mxml file, and other code you will write new.
groceryInventory
and shoppingCart
and paste them inside the Script block in the ShoppingView component. Change the scope groceryInventory from a private variable to a public variable.Pressing Ctrl+Spacebar will force Flash Builder to use its code-completion feature, which in this case will automatically import the classes for you.
When you copied these variables into the component, they became properties of the component. Simply by using the var
statement and defining the variables to be public, you are creating these as public properties of the components that can have data passed into them.
This is no small matter. The basic building blocks of object-oriented programming are objects, properties, and methods. So knowing how to create properties is a very important piece of information.
Later in this lesson, you will add functions to a component. Just as variables in a class are properties, so functions in the class are the methods of your components.
<fx:Declarations>
tag pair. In ShoppingView, remove the opening and closing HGroup tags, but leave the contents of this tag pair in place.You will no longer need the HGroup in place, as your ShoppingView component is set to have a HorizontalLayout.
Lastly, move the VGroup that shows the expanded state so it is defined before the VGroup with the id
of cartGroup
. The remaining code should look like this:
<fx:Script>
tag and the starting <fx:Declarations>
tag.The reality is that the order of where you place the states tag is unimportant, as long as you do not place it as the child of something other than the root node.
addToCart()
and removeCart()
methods from the main application, and paste them into your component in the <fx:Script>
block. Use the code-completion feature to import the Product and ShoppingCartItem classes. Remove the trace ( shoppingCart );
statement from your addToCart()
method. It is no longer needed.At this point, the full Script block should read as:
These methods are called from some of the MXML tags you moved into the ShoppingView class, so they need to be moved into that component.
handleViewCartClick()
method from the main application, and paste it into your component’s <fx:Script>
block.At this point, the full Script block should read as follows:
These methods are called from some of the MXML tags you moved into the ShoppingView class, so they need to be moved into that component.
You have created your first MXML component. Now that the component is built, you will instantiate the new component from the main application.
<Shopp
and choose views:ShoppingView
from the code-hinting menu.id
of bodyGroup
, and make it a self-closing tag. Add a width
and a height
of 100%
to the tag.<views:ShoppingView id="bodyGroup" width="100%" height="100%" />
The code-hinting feature automatically added to your main application tag the namespace that references the views directory. The Application tag should now read like this:
<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/mx"
creationComplete="handleCreationComplete( event )" xmlns:views="views.*">
click.state1="handleViewCartClicked( event )"
to click="handleViewCartClicked( event )"
The state definitions have all been moved to the ShoppingView class, so the main application no longer has any states other than the base state. For this reason, the click
event handler should be defined without the explicit reference to State1.
<s:Button id="btnCartView" label="View Cart" right="90" y="10"
click="handleViewCartClick( event )"/>
handleViewCartClick()
method so it changes the state of bodyGroup instead of the state of the main application.private function handleViewCartClick( event:MouseEvent ):void {
bodyGroup.currentState = "cartView";
}
As mentioned in the previous step, the main application no longer has states. When the user clicks the View Cart button in the control bar, you need to change the state of the ShoppingView in order to show the cart.
groceryInventory
property from the FlexGrocer application into the groceryInventory
property of the ShoppingView component.<views:ShoppingView id="bodyGroup"
width="100%" height="100%"
groceryInventory="{groceryInventory}" />
Now the ShoppingView should have the data it needs to render products as it did before.
The purpose of this first exercise was not to add functionality to the application but to refactor it. As the functionality of the application continues to grow, the main application page would have become much too long and complex. Using components gives you the chance to break it up into manageable modules.
Right now, the application continues to behave as it did at the end of the previous lesson; it shows a single product and allows you to add or remove that product from the cart. However, if you wanted to show more than one item at a time, you would need to copy and paste a large block of code multiple times. Instead, you will split the elements specific to viewing a product into a separate class, and you can then create several instances of this one class to show multiple products. The new component you will create is more than just a simple view onto the data. It is intended as a reusable component that can be used anytime you need to display product information for this application. So instead of creating it in the views directory, you will create a new components directory to hold this and other reusable components.
<fx:Script>
tag pair just after the layout declaration.Much as with the last component, the Script block will be used to add properties and methods for your new component.
product
, with a data type of the Product class, to the Script block. Use code-completion to import the Product class, or manually add an import
statement for valueObjects.Product
. Add another public variable, called shoppingCart
, with a data type of ShoppingCart.This will allow you to pass a specific product to each instance of this component, and pass a reference to the shoppingCart to each instance as well. Remember to either use the code-completion functionality when specifying the ShoppingCart class, or to manually add an import
for cart.ShoppingCart.
addToCart()
and removeFromCart()
methods from the ShoppingView component, and paste them into the Script block of the ProductItem component. You will need to make sure the ShoppingCartItem class is imported as well.id
of products, and the one that follows it, which is included in the expanded state, and paste them after the <fx:Declarations>
section of ProductItemYour ShoppingView states block should read like this:
<s:states>
<s:State name="State1"/>
<s:State name="cartView"/>
</s:states>
Your ProductItem states block should read like this:
<s:states>
<s:State name="State1"/>
<s:State name="expanded"/>
</s:states>
Now both components have a base state (State1), and each has another state specific for it. ShoppingView has the cartView state, which it can use to show the details of a shopping cart, and ProductItem has an expanded state, which shows expanded product details.
width
, height
, and visible
properties for the cartView state. While removing the attributes, also remove the normal width
and height
attributes as well, as those will no longer be needed, either."@Embed('assets/dairy_milk.jpg')"
and instead dynamically load the image from the assets directory, using the image name of the product.Your component can now show the appropriate image for any product passed to it, rather than always showing milk.
text="Milk"
to text="{product.prodName}"
to dynamically display the product name.<s:Label text="{product.prodName}" id="prodName"/>
Your component can now show the correct name for whichever product it has.
text="$1.99"
to text="${product.listPrice}"
to dynamically display the product price.<s:Label text="${product.listPrice}" id="price"/>
Your component can now show the correct price for whichever product it has.
groceryInventory.getItemAt( 0 ) as Product
to product
.<s:Button label="Add To Cart" id="add"
click="addToCart( product )"/>
<s:Button label="Remove From Cart" id="remove"
click="removeFromCart( product )"/>
Since your component is no longer dealing with the entire groceryInventory collection, but instead with an individual product, the reference to the product for this component is now greatly simplified. When you create an instance of this component from the ShoppingView component, you will pass just one specific product to each instance.
groceryInventory.getItemAt( 0 ) as Product
to just product
.Your final code for the ProductItem component should read like this:
Next, you will need to create one or more instances of this component from the ShoppingView.
<fx:Declaration>
tag pair, but before the cartGroup VGroup, create a new VGroup with a width
and a height
of 100%
. Inside this group, create an instance of ProductItem. If you begin typing it and use code-completion, as you did when you created the instance of ShoppingView in the previous lesson, the import
statement for the components package will be automatically added.id="product1"
, specify a width
and a height
of 100%
, bind a reference of the local shoppingCart into the shoppingCart
property of the new component, and bind groceryInventory.getItemAt(0) as Product
to its product
property.Your application is now displaying the first item from the groceryInventory, which is buffalo meat, rather than the milk you have been used to seeing in the application thus far. But wait, there’s more. Since you now have a component that can easily show individual products, you can show several at once.
id
of the new ones to be product2
and product3
. Also change the binding to the product
property to use item 1
and item 2
, while the original is getting item 0
.Now, as you save and run the application, you should see several products shown.
In the next lesson, you will learn how to use a DataGroup to create one ProductItem for each Product in the groceryInventory collection.
One bug still needs to be fixed. If you click the View Cart button, the products are still being shown, rather than being hidden. You will fix that in the next step.
width
and the height
of the cartView state to be 0
, and the visible
property to be false
.<s:VGroup width="100%" height="100%"
width.cartView="0" height.cartView="0"
visible.cartView="false">
The application should now be able to switch between the various states of the ProductItem and ShoppingView components correctly, while now displaying three products instead of just one.
In the first exercise, you refactored part of the application without adding any functionality. In the second exercise, you added functionality (showing multiple products) while building another component. This exercise is akin to the first, in which you are refactoring the application without adding any visible functionality for the user.
Right now, the main application file is a bit cluttered by the instantiation and event handlers for the two HTTPServices. In this exercise, you are going to create ActionScript classes for these services, which will contain the HTTPService components as well as result and fault event handlers, and will expose the data loaded through public bindable properties. The new class will provide certain types of data to all the applications when they need it. This data manager component will be different from other components you’ve built in this lesson in that it will not have any representation that a user will see. Such a component is referred to as a non-visual component.
Alternatively, if you didn’t complete the previous exercise or your code is not functioning properly, you can import the FlexGrocer-PreData.fxp project from the Lesson09/intermediate folder. Please refer to Appendix A for complete instructions on importing a project should you ever skip a lesson or if you ever have a code issue you cannot resolve.
Because the new components are neither a value object nor a view, a new package is needed to continue organizing your components by function.
As you want a class that provides all the functionality of HTTPService, but has some additional methods and properties, HTTPService is the most logical choice for a base class.
categories:XMLListCollection
.This categories
property will determine how other classes interact with the data loaded by the service. Don’t forget to use the code-hinting feature, or to manually import the XMLListCollection class.
super()
, set the resultFormat
property of your class equal to e4x
and the url
property to http://www.flexgrocer.com/category.xml.Take a look at the constructor here. The first line inside the function definition (which was automatically added by the new-class wizard), passes the rootURL
and destination
arguments to the constructor of the superclass. This way, it is not necessary to duplicate the logic found in the superclass’s constructor. The two lines you added are setting the resultFormat
and url
properties of the HTTPService class, as you learned in previous lessons.
handleCategoryResult()
method, and paste it into the new CategoryService class, after the constructor.As with each new class you introduce, make sure the other classes you are using get imported. In CategoryService, you need to ensure that the ResultEvent class gets imported, either by typing in the import
statement for mx.rpc.events.ResultEvent
manually, or by using the code-completion feature. This method will populate the categories
property with the results from the service call.
result
event. Set handleCategoryResult
as the handler for that event.addEventListener(ResultEvent.RESULT, handleCategoryResult);
The addEventListener()
method allows you to specify an event to listen for (in this case it’s the event result and a method that will be used as the event handler).
Your service class is now complete. All that remains is to use it in the application. The completed CategoryService class should read like this:
<fx:Declarations>
block of FlexGrocer.mxml, delete the <s:HTTPService>
tag with the id
of categoryService
.id
of categoryService
.As with the previous components you instantiate, if you use the code-hinting features, the namespace will be automatically added for you.
<services:CategoryService id="categoryService"/>
You now have your new component being used in place of (and in fact with the same id
as) the previous HTTPService. Since the id
is the same, the existing call to categoryService.send()
in the handleCreationComplete()
method does not need to change.
categories
property.You will no longer need this, as the categories are now available from the categoryService instance directly.
categories
to categoryService.categories
.The FlexGrocer application is now using your new CategoryService class, instead of having the service properties and handlers all coded into the main application.
Next, you will create a service class similar to CategoryService to load and manage the products, and you’ll remove that logic from the main application.
mx.rpc.http.mxml.HTTPService
; leave the rest of the defaults, then click Finish.products:ArrayCollection
.[Bindable]
public var products:ArrayCollection;
This products
property will determine how other classes interact with the data loaded by the service. Don’t forget to use the code-completion feature, or to manually import the ArrayCollection class.
super()
, set the resultFormat
property of your class equal to e4x
and the url
property to http://www.flexgrocer.com/categorizedProducts.xml.The constructor is just like the one for the CategoryService, with a different url property.
handleProductResult()
method, and paste it into the new ProductService class, after the constructor. Change the final line so that it populates the products
property rather than the groceryInventory
property. Change the local products
variable to be productArray
.As with each new class you introduce, make sure you import the newly referenced classes (ResultEvent and Product), either by typing in the import, or by using the code-completion feature. This method will parse the results of the service call into Product instances and populate the products
property with them.
result
event. Set handleProductResult()
method as the handler for that event.addEventListener(ResultEvent.RESULT, handleProductResult);
Just as with the CategoryService class, you will want to listen for the result
event, and pass the results on to a handler method. The final ProductService class should read like this:
Your service class is now complete. All that remains is to use it in the application.
In the <fx:Declarations>
block of FlexGrocer.mxml, delete the <s:HTTPService>
tag with the id
of productService
. In its place, create an instance of the ProductService class. Give this new instance an id
of productService
.
As with the previous components you instantiate, if you use the code-hinting feature, the namespace will be automatically added for you.
<services:ProductService id="productService"/>
Since the id
is the same, the existing call to productService.send()
in the handleCreationComplete()
method does not need to change.
groceryInventory
property and the Bindable public shoppingCart property.You will no longer need these, as the products are now available from the productService instance’s products
property and the ShoppingCart is now defined in the ShoppingView.
groceryInventory="{groceryInventory}"
to be groceryInventory="{productService.products}"
Your refactoring of the FlexGrocer application into components is now complete.
Your refactored FlexGrocer file should now read like this:
In this lesson, you have:
• Gained a theoretical understanding of why components should be used and how they fit into a simple implementation of MVC architecture (pages 204–209)
• Built a component that moved the visual elements from a main application page to the component and then instantiated the component in the main application page (pages 210–226)
• Created non-visual components that provide category and product information to the applications (pages 226–235)