Note: This article was created based on Flex 2. Minor changes in the description and code may be necessary before it can be applied to Flex 3.
Once developers become experienced with Flex applications, they tend to regret some of their coding choices to solve a particular problem rather than planning the structure of the application. The spaghetti code and hacks that got the job done earlier are now becoming roadblocks to scalability, code reuse, and project maintainability.
In this three-part series, I describe the development of a sample contact manager application.
Note: Extract these files into a new Flex project (if you are using Flex Builder) or a generic folder on your system (if you are using the Flex SDK and another editor).
In Part 1: Identifying code hacks there were three files for the sample application:
These files house the markup language that defines the layout of the application components as well as transitions. These items don't change much from the previous version. What is dramatically different inside these very same files is the application logic that controls what happens to the components that are laid out. I'll discuss the details of how the old hacks are replaced with best-practice code throughout this article.
The most prominent change to the project is the addition of two new directories, "vo" and "events". Just the fact that there are directories at all is a testament of better code organization. A big mistake that new developers tend to make is putting all the code into the project's root directory. While this is simple and seems harmless when you write simple apps with five or so files, it causes headaches when the file count gets to 50+, and becomes impossible to manage when the file count reaches hundreds or thousands.
Value Object, abbreviated as VO, is a design-pattern principle. According to this principle, related items describing the same entity should be grouped and passed together as a single object. The originally independent yet related items then become properties in this new VO class. Whenever you need another copy of the VO, you simply create a new instance of that class. A common practice is to put all of your VO classes into a directory called vo. When you need to modify a particular VO, it's much simpler to do so when they are all in the same directory.
In this version of the app, we have added only one VO: ContactInfo. As the name implies, this is used to describe
and transport information about a contact. If you open up the ContactInfo.as file, you'll
notice one obvious thing:
public class ContactInfo
The class name and the filename, in this case ContactInfo, must match. If they do not match, you will get an error.
This rule goes not only for VOs, but for any custom class you write for your
applications.
The second new directory you find is "events". In this directory, you put in all the custom events that your application is going to use. Events are the driving force behind any Flex application, i.e. events make things happen. When you push your mouse button down, for instance, a mouse down event is fired (along with many others). This event causes the button to change its state/appearance. Also, any code you put in that button's mouseDown event handler also runs. While the Flex/Flash frameworks have hundreds of built-in events for you to use, you quickly begin to see how a custom event can greatly assist your app.
The two custom events built for this version of the application are ShowContactEvent and UpdateContactEvent. ShowContactEvent is fired when we need to show a contact's details and UpdateContactEvent is used to update a specific contact's details with updated information. Aside from the class names matching the filenames, we notice something different about the custom event class declarations:
public class ShowContactEvent extends Event
public class UpdateContactEvent extends Event
They use
a new keyword: extends. Extending base classes to create your own
classes is very common in Flex because you can use classes as building blocks
and add more functionality to them when you need it. You can do the same for
any class, i.e. you can create your own custom button by extending the base
button class. What this means is you get all the built-in properties and
methods of that base class in your new custom class for free! In the button example, you get all the mouse
events, button states/styles, and properties (such as visible and enabled) without having to recode that for your custom
button.
In Part 1, I showed how hacks were used to accomplish the application logic. In this article, I'll show you how the code accomplishes the same tasks using best practice standards. I suggest you refer to each section in Part 1 and then reread it here in Part 2 to see how I've updated it based on the better coding practices.
I set up the contactListAC:ArrayCollection in this version exactly as I did in Part 1. This is the variable that is actually going
to house the contact list. The only
thing that changes is how I hard-code the first entry. In the previous version,
I added a generic object to the ArrayCollection:
contactListAC.addItem({First:"Tom",
Middle:"",
Last:"Ortega",
Cell:"555-222-3333",
Home:"555-333-4444",
Address:"1313 Mockingbird Lane",
City:"San Jose",
State:"CA",
Zip:"95134"});
In this
version, we add an instance of our new ContactInfo value object instead of an inline object:
contactListAC.addItem(new ContactInfo("Tom",
"",
"Ortega",
"555-222-3333",
"555-333-4444",
"1313 Mockingbird Lane",
"San Jose",
"CA",
"95134"));
I'll explain what's going on in that line a bit later. Right now, let me dissect the ContactInfo.as class file to explain its various parts. Though we call this custom class a value object, that is just a naming convention. There's nothing specific to this custom class that makes it a value object per se. It could just as easily be called a contact container or an info object. What makes it a value object is how we use it, not how we code the class file itself. The purpose of using terminology like value object is that it is a universally utilized design pattern. Therefore, new developers who are familiar with that term will be able to understand the code immediately.
Since I'm standardizing the code by adding
a value object class, there's
another thing I can fix up. Looking at
the original addItem method, I can see that my properties (First, Middle, etc.) begin with capital
letters. While there's
nothing technically wrong with using capital letters to begin
properties/variables names, there is something wrong with it convention
wise. Capitalizing the first letter in a
name commonly denotes a class name. Using all caps (as you'll
see later) is reserved for constants. Therefore, when I create the value
object, I go ahead and drop the non-conventional use of caps in the properties. I also update the dataField property of my
DataGridColumns in contactListComponent.mxml and various variables in
contactDetailsComp.mxml to use the non-capitalized versions of the property
names (i.e. swap out "Address" for "address", etc.).
As I go through
the ContactInfo class, remember these
explanations pertain to any custom class. When you choose New > ActionScript Class inside of Flex Builder, you'll be utilizing the following skillset
in every ActionScript class you build:
package vo
A package is an assortment of items put together in a single, convenient location. In the real world, that convenient location is usually a box or gift basket. In the coding world, the convenient location is a directory in your project. Therefore, looking at that line, we know that we can find this class file in the vo directory of the project. You use dot notation to indicated nested directories:
package vo.contacts
Therefore, if "contacts" were a directory inside of the "vo" directory, then any classes in the "contacts" directory would have the package declaration shown above:
[Bindable] public class ContactInfo
The [Bindable] is used in front of any variable you would like to make bindable. Only in this
case, I want to make all properties of this class bindable. Instead of having
to put [Bindable] in front of each property, I just
use it once in front of the class declaration and Flex knows how to make all
properties bindable.
Next, you see some of those property declarations:
public var first:String = new String(); public var middle:String = new String(); ... ... public var zip:String = new String();
These
properties are what hold the various bits of contact information. Notice that I
have dropped the first-letter caps that I originally had in the hacked code.
The last bit of the code that needs explanation is the constructor. A
constructor is a very special function in a class file. It is executed before
any other function when you instantiate a new class instance. It lets you add
some code to be executed every time the class is instantiated. Let's take a look at ContactInfo's constructor:
public function ContactInfo(
first:String="",middle:String="",last:String="",cell:String="",home:String="",street:String="",city:String="",state:String="",zip:String="")
{
this.first
= first;
this.middle
= middle;
...
...
this.zip
= zip;
}
There are some things you notice about the constructor:
:void added
after the closing parenthesis.zip:String="". This is a fancy
way of saying: If the user wants, they can pass in a parameter called zip, which will set that value inside the class instance. If they don't, then the default value of the zip parameter will be set to " ". This lets you pass in some initial values to use
in your class when it is constructed. Parameters are not required in a constructor; just add them when you need them.Inside the constructor function, you see this bit of code:
this.zip = zip;
The keyword this refers to the class itself. Therefore, you could read that assignment as: "Make
the class property named zip equal to the value of the
function parameter named zip." The reason I put this into
the constructor was so that we could easily send in the contact info. Let's go back to the hard-coded entry in main.mxml.
To help you understand better, I'm going to add some line breaks:
contactListAC.addItem(
new ContactInfo("Tom",
"",
"Ortega",
"555-222-3333",
"555-333-4444",
"1313 Mockingbird Lane",
"San Jose",
"CA",
"95134")
);
The green is
just the common addItem method you use on an ArrayCollection. Nothing new there. The orange code is me initializing a new ContactInfo class. I now know that the information
I pass in, i.e. "Tom" is going to autopopulate the
class property called first.
The way to view a contact's details has stayed the same. The user merely needs to click an entry in the DataGrid in contactListComponent.mxml:
<mx:DataGrid id="mainList" dataProvider="{contactListAC}" itemClick="itemClicked(event)"
width="100%" height="100%">
You see that I
have set up an itemClick event
handler. Whenever a user clicks the DataGrid, I call the itemClicked function. In Part 1: Identifying code hacks we saw the following hack used in the itemClicked function to tell the parent file (main.mxml) that an item was clicked:
this.parentDocument.contactSelectedFunc( mainList.selectedItem,true);
This time you
see the following code in the itemClicked function:
selectedItem = mainList.selectedItem as ContactInfo;
I set the selectedItem variable equal to the selected item in the list. However, there is the extra
bit of code that says as ContactInfo;. That bit of code is
saying, "I know that I'm inserting ContactInfo instances into the contactListAC that is the grid's dataProvider.
Therefore, when I get the selectedItem of the mainList,
I know that item is going to be of type ContactInfo." Similarly, in the variable
declarations at the top of contactListComponent.mxml, you see the
following:
[Bindable] public var selectedItem:ContactInfo;
To recap, I
know I'm inserting ContactInfo objects into the contact list. I then know that I want to set the value of selectedItem,
which is of type ContactInfo, to the currently selected item in
the list. While I know this will all work at runtime, Flex Builder knows
only that selectedItem is going to be a ContactInfo object at compile-time. As far as the
items inside of contactListAC, Flex Builder figures you can
add any type of object you want in there and it wants some reassurance that the
objects ixn there are indeed ContactInfo objects. Using the as keyword is known as casting. You are taking a variable of a known type (mainList.selectedItem, which in this case Flex
Builder defaults to the generic type object) and are casting it into a different
type (in this case, ContactInfo). Casting gives Flex Builder
that reassurance it wants that you know what you're doing and lets you compile the app so you can prove it at
runtime.
The next line of code in the item function introduces the concept of custom events:
dispatchEvent(
new ShowContactEvent(
ShowContactEvent.SHOW_CONTACT_TYPE,
selectedItem,
true)
);
ShowContactEvent is a custom ActionScript class, just like ContactInfo.
Open up the ShowContactEvent.as file to see what it looks
like. The first line is the package declaration.as:
package events
This line
informs you that the ShowContactEvent.as is located in
the "events" directory of your project.
Next, inside the package declaration you see some import statements:
import flash.events.Event; import vo.ContactInfo;
This tells you
that you will be using the Event class and ContactInfo class inside of the custom class. Next
comes the class declaration:
public class ShowContactEvent extends Event
You see a new
keyword here: extends.
All that says is that this class is based off another class. Remember earlier
in the article, I said you can create a custom class, base it off another class,
and inherit all its properties and methods? This is how you do it. Therefore, you know that ShowContactEvent will have all the same properties and methods as the base Event class, in addition to whatever new properties I add.
Next up are the new properties I'm adding. I'll only call out one special property:
public static const SHOW_CONTACT_TYPE:String = "ContactSelectedEvent";
This has a few unique qualities to it:
static means shared. Whereas normally a class instance gets its
own copies of the properties to fill with different information as they
see fit, a static property is shared across all instances of the class. Imagine if there was
a custom class named sibling. Each sibling would have a unique value for
the name property but they would all share a static property called
parents.const means constant, never changing, as opposed to the keyword var which means variable, prone to change. You use this
keyword when you want to set a value and never have it change. To return
to the sibling class example, you'd want to set a property called species to the const value "Human".const value in
your code. This way no matter where
that value is used, one look will inform you and other programmers that
the value of that property is a constant.The next item we will review is the constructor:
public function ShowContactEvent(type:String, selectedItem:ContactInfo,
showContactDetails:Boolean, bubbles:Boolean=true, cancelable:Boolean=false)
What's unique about this constructor is
that there are both required and optional parameters. You can differentiate them
by looking to see if they have default values or not. In this case, we see that type, selectedItem and showContactDetails are required, while bubbles and cancelable are optional. The rule to remember when mixing required and optional parameters
in a function call is that the required parameters must always come first.
The last item
of code I'd like to point out
is the super call inside of the
constructor body:
super(type,
bubbles, cancelable);
The super call is just a fancy
way of saying, "Go ahead
and call this exact same function in the class that I'm extending." You can use super for any method that your custom class and base class
share. Typically, you do this by overriding the method in your custom class;
however, we have no examples of that in our code. Using super inside a constructor is a bit different because rather
than calling the function with the same name in the base class (which won't exist), it just calls that base
class's constructor instead.
Now, we can go
back to the code inside our itemClicked function inside of contactListComponent.mxml:
dispatchEvent( new ShowContactEvent( ShowContactEvent.SHOW_CONTACT_TYPE, selectedItem, true) );
We now see that
this line fires off a ShowContactEvent when an item is
clicked. We put the selected item inside the event as well as set the showContactDetails boolean flag to true.
Because we set the default value of the bubbles property to true,
this event will flow upward through the parent chain until it reaches the
end. To find out more about events, read
the Handling
events Quick Start in the Flex Developer Center.
In the file main.mxml, see the
following line of code in the initFunc function:
addEventListener( ShowContactEvent.SHOW_CONTACT_TYPE,contactSelectedFunc);
What this does
is listen for the ShowContactEvent.SHOW_CONTACT_TYPE to come through. When it does, it then calls the contactSelectedFunc function and passes
in the ShowContactEvent object. The beauty of using this versus the this.parentDocument.contactSelectedFunc hack in Part
1 is that you can now move contactListComponent.mxml as far deep as you want in the
program and it will always work. With the hack, it only works if contactListComponent.mxml is a child of main.mxml.
Now if we take
a look at contactSelectedFunc in main.mxml, you'll see the code is pretty much the same
only now we use the event instead of individual parameters, plus we see that contactDetailsComp.selectedItem is also now using the ContactInfo class:
public function contactSelectedFunc(event:ShowContactEvent):void
{
if (event.showContactDetails)
{
currentState="ShowContact";
if (event.selectedItem == null)
{
contactDetailsComp.selectedItem
= new ContactInfo();
contactDetailsComp.currentState
= "AddContact";
}
else
{
contactDetailsComp.selectedItem
= event.selectedItem;
contactDetailsComp.currentState
= "";
}
}
else
{
currentState="";
}
}
Review Part 1 to see how editing works. The only line that is changed in contactDetailsComponent is in the editSubmit function that's called when the button is clicked:
this.parentDocument.updateContactListFunc(selectedItem,addNew);
In this version, I fire another custom event:
dispatchEvent( new UpdateContactEvent( UpdateContactEvent.UPDATE_CONTACT_TYPE,selectedItem,addNew) );
Take a look at
the UpdateContactEvent.as file. It is in the same package (directory) as ShowContactEvent.as and is very similar in
content. Therefore, I won't
go over the UpdateContactEvent class file in depth.
In the file main.mxml, we
see the following line of code in the initFunc function:
addEventListener( UpdateContactEvent.UPDATE_CONTACT_TYPE,updateContactListFunc);
Like the previous
event listener, this lets you know that when the UpdateContactEvent.UPDATE_CONTACT_TYPE comes through main.mxml,
it calls the updateContactListFunc function. Besides now utilizing the event instead of numerous parameters, the
biggest change is this:
contactListComp.dispatchEvent( new UpdateContactEvent( UpdateContactEvent.UPDATE_CONTACT_TYPE, event.item, event.newItem, false) );
What this does
is take the event and redispatches it inside of the contactListComponent.mxml file. In there, we
see the following eventListener in the initFunc function:
addEventListener( UpdateContactEvent.UPDATE_CONTACT_TYPE,updateTable );
Looking at the updateTable function in the contactListComponent.mxml file, notice that the function is rewritten to utilize the event:
public function updateTable(event:UpdateContactEvent):void
{
mainList.selectedItem =
event.item;
}
The logic here
is pretty much the same as it was in Part 1.
The major difference is what happens when the "Add New" button is clicked. The button's
click event calls the addNewClick() function. In Part 1,
this function had this horrible hack:
public function addNewClick():void
{
this.parentDocument.contactSelectedFunc(null,true);
}
Now, we use a much more robust custom event solution:
public function addNewClick():void
{
dispatchEvent(
new ShowContactEvent(
ShowContactEvent.SHOW_CONTACT_TYPE,null,true)
);
}
Using your new found logic about events, you should review main.mxml and follow the flow on what happens after this.
Hint: Refer to the edit steps above and the Adding a new contact's details section in Part 1 to help you if you get lost.
Once again the only change is that I drop the hack and use a more robust custom event solution. The following is the code lines used in Part 1:
public function hideDetailsClick():void
{
this.parentDocument.contactSelectedFunc(null,false);
}
Here are my new code lines:
public function hideDetailsClick():void
{
dispatchEvent(
new ShowContactEvent(
ShowContactEvent.SHOW_CONTACT_TYPE,selectedItem,false)
);
}
As you can see,
moving from hacks to a more architected development mentality is not a simple
step. It is very easy to find yourself using the this.parentDocument hack. It will take some time
before you get used to architecting your application to utilize custom classes
such as value objects and custom events. However, the benefits of a more robust
solution far outweigh the initial pains of changing your mentality.
While this part of the series is quite long, it is necessary to have you understand the new concepts that were introduced. It may take a couple of reads and walkthroughs of the series to fully grasp all the concepts. Take the time to learn these concepts. Part 3: Revising code within the Cairngorm framework takes this same application and implement it in the Cairngorm framework. Without a solid understanding of the concepts introduced in this part of the series, you may find yourself even more confused by trying to learn Cairngorm. If you find yourself stuck with some of these concepts, feel free to send me your questions.
Thomas Ortega II is an active member of the Flex community and works for Workday, Inc. He runs the Silicon Valley Flex User Group where he gives free Flex training. He also is a coordinator of the 360|Flex conference. You can find Tom on his blog.