Accessibility
Thomas Ortega II

Thomas Ortega II

Silicon Valley Flex User Group
360|Flex conference
Blog

Created:
6 August 2007
User Level:
Intermediate
Products:
Flex

Graduating from hack to architected development – Part 1: Identifying code hacks

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.

When developers start building web applications, they tend to make their coding choices to solve a particular problem rather than planning the structure of the application. You might get stuck on a problem, search for a solution, and implement the code. Oftentimes this leads to spaghetti code and hacks that get the job done but do little to ensure the scalability of your application.

In this three-part series, I describe the development of a sample contact manager application.

  • In Part 1: Identifying code hacks, I show a working application completed with little or no planning. It will be filled with the hacks mentioned above.
  • In Part 2: Adding architecture to your code design, I take the exact same application and remove the hacks, replacing them with a more advanced approach to programming the application. Part 2 introduces concepts such as custom events and value objects.
  • Lastly, in Part 3: Revising code within the Cairngorm framework, I take the application development to the next level, as we implement the application within the Cairngorm Framework. Part 3 shows you how to organize your classes (events and value objects) and use advanced methodologies (controllers and locators), all of which will make your code easier to read, maintain, and extend.

This series is not a step-by-step building tutorial. In each article, I will give you the source code for each version. I will then step through the code and point out the unique aspects of each version. Through this process, I hope that you will come to recognize and see how the code evolves and moves to the "next level" in each iteration. I use the same application each time so that you can focus more on what has changed in the code and why it's changed. All too often, an article might introduce new concepts but use different application code in each of those articles, leaving you to figure out the conceptual changes. Because you'll be familiar with the app in this series, you'll be able to extrapolate the conceptual changes in each version more clearly.

Requirements

To complete this tutorial you will need to install the following software and files:

Flex Builder 2 (SDK included)

Sample files:

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).

Prerequisite knowledge

To benefit most from this series, it is best if you:

  • Have already programmed a few Flex applications
  • Can distinguish between MXML and ActionScript 3.0 code at a glance

Checking out the contact manager sample application

Let's take a look at the completed application. The contact manager application is comprised of three screens. First is the main screen with the DataGrid that lists all the contacts and has an Add New button. Second is the view contact details screen which is exposed when you click a contact entry in the DataGrid. Third is the enter contact details screen which is exposed when you click the Add New or Edit Details button.

Note: There are three files for the application. However, they do not map one-to-one to the screens mentioned above. Instead, the application is broken into three distinct components that you can reuse later theoretically.

The file main.mxml contains the central component that holds the other two components. This file also contains the main transition that shows and hides the detail level of the component. The file contactListComponent.mxml has the contact list DataGrid and the Add New button. Lastly, the contactDetailsComponent.mxml file contains the contact details form with its two states (read and write) and transitions. All three files have mx:Script blocks to house the ActionScript 3.0 code for variables and functions.

The following sections explain the logic in the application, pointing out the supporting code where appropriate. One thing to note, however, is that I will not cover the transitions—it is beyond the scope of this article. While the transitions are fun to look at, the transition code stays exactly the same in the three versions of the application shown in this series.

Where does the contact list data live?

First, I declare the main variable to hold the contact list in main.mxml. I choose to use an ArrayCollection mainly because of its manipulation methods (i.e. addItem) and I prefer Objects over XML:

[Bindable] public var contactListAC:ArrayCollection = new ArrayCollection();

You'll need access to this same variable in the contact list DataGrid. Otherwise, how else would the list know which contacts to show? Therefore, in the file contactListComponent.mxml, notice that the code sets up a matching variable declaration:

[Bindable] public var contactListAC:ArrayCollection;

The code then binds the DataGrid's DataProvider to this variable:

<mx:DataGrid id="mainList"
    dataProvider="{contactListAC}" itemClick="itemClicked(event)" width="100%" height="100%">

Note: Unlike the real declaration in main.mxml, I do not add the new ArrayCollection() because you will pass a reference of the main variable to the subcomponent. I pass the reference as a parameter in the component declaration in main.mxml, binding the subcomponent contactListAC to the main contactListAC:

<local:contactListComponent id="contactListComp"
    contactListAC="{contactListAC}" width="100%"/>

While I could use a remote object or web service call to populate the contactListAC variable, I chose not to for brevity and simplicity. Therefore, in the initFunc that's called on creationComplete for main.mxml, I hard coded the first entry into the system:

public function initFunc():void {
   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"});
   currentState="";
}

Viewing, editing, adding, and hiding contact details

When creating this functionality, I know they are going to share a lot of code. In this section, I'll show you how seven functions spread over the three component files accomplish all of these tasks.

Viewing a contact's details

To view a contact's details is very simple. The user merely needs to click an entry in the DataGrid. As shown above (where I showed that DataGrid's dataprovider being bound to the contactListAC), you see that I have set up an itemClick event handler. Whenever a user clicks the DataGrid, I call the following function in the file contactListComponent.mxml:

public function itemClicked(event:ListEvent):void
{
    if (event.rowIndex != 0)
    {
          this.parentDocument.contactSelectedFunc(
          mainList.selectedItem ,true);
    }
}

The first if statement ensures that the item clicked wasn't a header. Once that's verified, I call the contactSelectedFunc in the parentDocument. Using this.parentDocument is just a hack to get at the parent component. Take a look at that function in the file main.mxml:

public function contactSelectedFunc(
selectedItem:Object,showContactDetails:Boolean):void
{
    contactDetailsComp.selectedItem = new Object();
    if (showContactDetails)
    {
          currentState="ShowContact";
          if (selectedItem == null)
          {
          contactDetailsComp.selectedItem = new Object();
          contactDetailsComp.currentState = "AddContact";
          }
          else
          {
          contactDetailsComp.selectedItem =
    selectedItem;
          contactDetailsComp.currentState = "";
          }
    }   
    else
    {
          currentState="";
    }
}

First, the function sets the selectedItem property in the contact details component to a new object. Doing so clears any remnants of the last selected item. Next, the code checks to see if the showContactDetails parameter is true. Since it is, the the currentState is set to ShowContact, which specifies that the application should show the contact details form. Next, the code checks whether the selectedItem parameter is null. Since it is not null (I have passed in the selectedItem from the mainList DataGrid), the selectedItem in the contact details component is set to the passed in selectedItem. Lastly, the contact details' component state is set to nothing. This sets the details form to the base read-only state.

Editing a contact's details

Once the contact details form is open, the user has the ability to click the Edit Details button. This changes the form from a read-only form to an editable version. In the file contactDetailsComponent.mxml, you can see the button declaration:

<mx:Button label="Edit Details"
    click="editSubmit()" id="EditSubmitButton"/>

The button's click event calls the editSubmit function:

public function editSubmit():void
{
    if (currentState=="EditContact" || 
       currentState=="AddContact")
    {
          selectedItem.First = firstNameInput.text;
          selectedItem.Middle = middleNameInput.text;
          selectedItem.Last = lastNameInput.text;
          selectedItem.Cell = cellPhoneInput.text;
          selectedItem.Home = homePhoneInput.text;
          selectedItem.Street = streetAddressInput.text;
          selectedItem.City = cityInput.text;
          selectedItem.State = stateInput.text;
          selectedItem.Zip = zipInput.text;
          this.parentDocument.updateContactListFunc(
          selectedItem,addNew);
    }
    else
    {
          currentState="EditContact";
    }
}

First, the code checks the currentState of the contact details component, which is currently blank. Therefore, the code skips the first block and runs the else block code, where the currentState is set to EditContact. This calls a transition and coverts all the labels of the form into text inputs, allowing the user to edit the details. It also sets the addNew value to false as you are not adding a new contact. Another thing that the transition sets is the button's label from "Edit Details" to "Submit Updates." The click event still calls the same function.

Once the user completes editing and clicks the button, the application calls the editSubmit function again. Only this time, the currentState is EditContact, so the application calls the first block in the if/else code. In short, I have created a selectedItem that houses all the data from the form, changed and unchanged. I then call the updateContactListFunc in the main.mxml using the this.parentDocument method again:

public function updateContactListFunc(
selectedItem:Object,newItem:Boolean):void
{
    contactDetailsComp.selectedItem = new Object();
    if (newItem)
    {
          contactListAC.addItem(selectedItem);
          currentState="";
    }
    contactListComp.updateTable(selectedItem);
    contactDetailsComp.selectedItem =
    selectedItem;
    contactDetailsComp.currentState = "";
}

In this code snippet, the first lines "blank out" the selectedItem in the contact details component by setting it to a new object. Next, the application checks to see if this is a new item, which it is not. Then, it calls the updateTable function in the contact list component. After that, it resets the selectedItem in the contact details component to the selectedItem. This is so the contact details form is updated with these new edits instead of the old unedited information. Lastly, I set the contactDetailsComponent's currentState back to blank, or, in other words, the base read-only state.

Looking at the updateTable function in the contactListComponent.mxml file, notice that the function is short and sweet:

public function updateTable(selectedItem:Object):void
{
    mainList.selectedItem = selectedItem;
}

This function simply replaces the DataGrid's currently selectedItem with the item passed into the function as the selectedItem parameter.

Adding a new contact's details

Adding a new contact is very similar to editing an existing contact. The biggest difference is the "Add Contact" click in the contactListComponent.mxml file. Here, you see the button declaration:

<mx:Button label="Add New"
    click="addNewClick()"/>

The button's click event calls the addNewClick function:

public function addNewClick():void
{
    this.parentDocument.contactSelectedFunc(null,true);
}

Note that the addNewClick function calls the exact same contactSelectedFunc function that you called when viewing a contact's details (see Viewing a contact's details section above for the full function code). The only difference is that you set the selectedItem as null. This causes the following code block to get executed:

if (selectedItem == null)
{
    contactDetailsComp.selectedItem = new Object();
    contactDetailsComp.currentState = "AddContact";
}

This code causes the contact detail form to start in the AddContact state, which is the textinput version of the form, ready for data entry. This state also sets the "Edit Contact" button's label to "Add Contact." The button still calls the same editSubmit function (see Editing a contact's details section above for the full function code). Only this time, the AddContact state has set the AddNew property to true in this line:

this.parentDocument.updateContactListFunc(selectedItem,addNew);

This condition causes a different bit of code to execute in the updateContactListFunc of the main.mxml file (see Editing a contact's details section above for the full function code):

if (newItem)
{
    contactListAC.addItem(selectedItem);
    currentState="";
}

As you can see, editing and adding a contact are very similar actions. With just a few extra lines triggered by some flags, they can share a lot of the same code.

Hiding the contact details form

The "Hide Details" button in the contact details form is very simple:

<mx:Button label="Hide Details"
    click="hideDetailsClick()"/>

This click event on this button calls the hideDetailsClick function. As you can see, that in turn just triggers a state change in the main.mxml file through the this.parentDocument syntax again:

public function hideDetailsClick():void
{
    this.parentDocument.contactSelectedFunc(null,false);
}

Where to go from here

As you can see, the above code accomplishes the job and is an application that works perfectly fine. By many people's measures, it's a success. However, in terms of code reuse and scalability, there are many aspects of the application that need to be reworked for scalability and best coding practices.

The biggest violation against good coding practices is the use of this.parentDocument. While this is an easy way to get access to the parent component, it comes at the cost of reusability and flexibility. What if you wanted to use that list in a different application? If you wanted to, the component that housed it would need to declare those functions referenced in this.parentDocument and use the exact same syntax. What if you wanted to nest the component differently? For example, right now the contactListCompoment is nested in the main component. What if you added a new contactApplication component between main and contactListComponent? Your this.parentDocument would fail and you'd have to rework/rewrite all instances of it.

Moreover, the code in this tutorial uses array collection referencing. I want only one copy of contactListAC in our main component, with all other subcomponents using only a reference. However, there must be a better way to do than demonstrated here. Passing a reference as shown in this tutorial is no better than using this.parentDocument. In the same example above, I'd have to create and pass in a reference of contactListAC in the contactApplication component for the sole purpose of passing it to the contactListComponent.

If these methodologies are no good, then why show them? I did so to expose them as the hacks they truly are. If you find yourself coding like this and find yourself working too hard to go back and make changes, then this series is for you. If you have wondered how to achieve certain logic that these hacks have shown, do not take to the hacks lightly.

Check out Part 2: Adding architecture to your code design, which covers concepts such as custom events and value objects to code architected applications for scalability, code reuse, and project maintainability. Next, check out Part 3: Revising code within the Cairngorm framework, where I show how you can achieve this logic with much cleaner code.

About the author

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.