Adobe Flex and Flex Builder are great tools to build a rich, highly functional web application. As a Java developer I was surprised at how familiar ActionScript 3.0 was, making the learning curve fairly flat. Once I started using Flex, it was great to see that I could use most, if not all, of the approaches to writing software that I was used to. This article presents two of my favorite approaches: creating components with a mixture of inheritance and composition, and enforcing separation of concerns, especially between view and business logic.
The file custom_comps.zip contains all of the source files for this article. To use the source files, create a new Flex project, and then extract the contents of custom_comps.zip in the root of the project.
Prerequisites:
To benefit most from this article, it is best if you are familiar with Flex Builder, object oriented techniques, and ActionScript 3.0.
When I started using the Visual Designer with Flex Builder, I was impressed with how quickly I could develop a GUI. I was well on my way to completing a substantial part of the application when I began to notice something familiar. As an experienced Java web developer, I learned over time that a JSP page should be used only for presentation. All business logic should be in classes, and if you absolutely had to have code in your JSP page, you would wrap it up in a custom tag.
I started looking at my Flex application and sure enough I
had a bunch of MXML files with <mx:Script></mx:Script> tags that
contained behaviors for my components. I felt the need to do what I learned
from working on another platform—separate my view and business logic. In this tutorial, I describe a practice called "code behind" where you define
your visual component completely in ActionScript 3.0, but use MXML to decide
how your component renders visually.
Note: This is an architectural approach for building an application. If you are doing quick prototyping, there is no need to always use the "code behind" technique.
At first it may seem a bit intimidating to do so, but once you get the hang of developing using this practice, it actually becomes much easier.
In brief, you extend an existing Flex visual component class and add
properties that themselves are other visual components. I will demonstrate
this by building a very simple search interface using a DataGrid, TextInput,
and two button objects. Here is an overview of the process you use:
DataGrid class to add behaviors needed to display search
results.Panel class that will be the overall container for your component.container class for the member objects.get and set methods for your variables.Now I will go over each step in detail.
DataGridYou will
extend the DataGrid with the class named SearchResultGrid. The
SearchResultGrid class will be fairly straightforward, displaying search
results returned as XML.
You create the
SearchResultGrid class to:
DataGridColumn objects.displaySearchResults.First, you
declare three instance variables of type Array named fields, headers, and
cols. The fields array will contain strings to map the elements in the
returned XML to display in the column of the same index. The labels array will
contain strings to be used as labels for the header of each column of the same
index. The cols array should be initialized to be an empty array of the same
length as the fields or headers arrays. You do the following by overriding
the protected commitProperties method in SearchResultGrid class:
DataGridColumn object.dataField and headerText properties of the DataGridColumn object with the
values from the fields and labels arrays respectively.DataGridColumn object to the cols array.DataGrid class.The commitProperties method should look something like the following:
override protected function commitProperties():void{ super.commitProperties(); _cols = new Array(_fields.length); for(var i:int=0;i<_fields.length;i++){ var column:DataGridColumn = new DataGridColumn(); column.dataField = _fields[i]; column.headerText = _labels[i]; _cols[i] = column; } this.columns = _cols; }
That is pretty much all
there is to set up the SearchResultGrid class. The only thing left is to write the
displaySearchResults method. The DisplaySearchResults method will take an XML
object, drop it into a XMLListCollection object, and then set the dataProvider property equal to the collection. The results are automatically displayed in
the SearchResultGrid. The method looks like the following:
public function displaySearchResults(search:XML):void{ this.dataProvider = new XMLListCollection(search.result); }
I don't think it could be any simpler to update your data display! I am making two assumptions here that I should point out:
<result>...</result> elements.<result>...</result> match the fields array defined
earlier.Panel class and
add private propertiesNow you
can create the overall container for your search interface by extending the Panel class with the SearchPanel class. The next step is to add a private property to the
SearchPanel class of type SearchResultGrid. After that you add properties for a
TextInput, and two Button objects. At this point your SearchPanel class looks
like the following:
public class SearchPanel extends Panel{ private var _searchResultGrid:SearchResultGrid; private var _searchButton:Button; private var _clearButton:Button; private var _searchTerm:TextInput;
get and set methodsAt this point you need to define how
to "wire up" the SearchPanel class with its objects. This part is very simple. For
each private property defined above. you will have a get and set method. Each
get and set method takes the following form:
public function set searchResultGrid(searchResultGrid:SearchResultGrid):void{ this._searchResultGrid = searchResultGrid; } public function get searchResultGrid():SearchResultGrid{ return this._searchResultGrid; }
Now you could just as
easily declare each property as public, but let's make them private and use the
accessor/mutator methods. This way you have a "hook" when these objects are
set or returned, in case there is any action you want your object to perform
during the function call. You will write a get and set method for each object
that needs to be "injected" into our search panel by Flex. Exactly how this is
accomplished will be discussed a bit later when you get to the point where you
will create the MXML file for the SearchPanel class.
At this point, you are
almost done. All that is left is to add event listeners to your button
objects. Now you might think that you should add the listeners to your button
objects in the constructor for the SearchPanel class, I know I certainly did when I
first used the code behind approach. If you do that, you will effectively get
an error complaining about not being able to access a method or property of a
null object reference. When the constructor for your SearchPanel class is called,
the other objects contained within the SearchPanel class do not exist yet! What you
will do is to override the childrenCreated method and add event
listeners there. Any other properties that need to be set on your collaborator
objects should also be done at this point.
At this point all of the
mutator methods you defined earlier have been called, so it is safe to work
with any of the dependent objects. In the childrenCreated method, you
will have the SearchButton and ClearButton listen for click events. The
SearchButton will call the doSearch method and use the text in the TextInput object. The ClearButton will simply clear any text in the TextInput. You also
set the fields and labels arrays for the SearchResultGrid described earlier.
The childrenCreated method
looks something like the following:
override protected function childrenCreated():void{ _searchButton.addEventListener(MouseEvent.CLICK,doSearch); _clearButton.addEventListener(MouseEvent.CLICK,clearText); _searchResultGrid.fields = new Array("title","author","date"); _searchResultGrid.labels = new Array("Title","Author","Publish Date"); }
All that is left now is
to add the method doSearch. The doSearch method will use the HttpService class
to send the search string to your search endpoint. The method onSearchComplete will be used for the callback once the response is returned from the server,
and simply calls the displaySearchResults method of the searchResultsGrid object. Then your search results are displayed.
Now the search panel component is complete.
The next step is to
create a MXML file for the view representation of the component. On the
current project you have named the MXML files the same as the ActionScript
class name with "View" on the end, so the MXML file for the SearchPanel would
be SearchPanelView and look something like:
<?xml version="1.0" encoding="utf-8"?> <SearchPanel xmlns="bbejeck.example.*" xmlns:comp="bbejeck.example.*" xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"> <comp:SearchResultGrid id="searchResultGrid" percentWidth="100" percentHeight="60"/> <mx:TextInput id="searchTerm" x="215" y="320" /> <mx:Button x="215" y="350" id="searchButton" label="Search" /> <mx:Button x="315" y="350" id="clearButton" label="Clear"/> </SearchPanel>
As you can
see, what you end up with is a very light, clean MXML file that only has view
specific information in it. The key thing to note here is that the ID of each
component maps back to a "set" method of the SearchPanel class. This defines how
the ActionScript class and MXML file end up being "wired "
together. You will also notice that you have a custom object
<comp:SearchResultGrid../> mixed with
standard Flex objects such as the Button or TextInput class.
Now to use
your newly crafted SearchPanelView in the application, you only need to add the
following to your main MXML file:
<comp:SearchPanelView id="searchPanel" title="Search Panel" horizontalCenter="0" verticalCenter="0" width="600" height="500" />
As you can see, by using the "code behind" approach, your code is very concise and maintainable. As you develop more complex applications, not only would you have a palate of components, but maintenance and changes to your applications would be very manageable.
Now that you are done, here are some thoughts I would like to leave you with that I have found helpful:
SearchPanel to a live source, simply change the headers and labels
arrays to match the XML being returned and set the URL property of the
HTTPService object in the SearchPanel.
This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License
Bill Bejeck is a software engineer for Near Infinity in northern Virginia. When not playing with his daughters, he spends too much time on his laptop. You can send your comments to Bill via e-mail.