23 May 2008
Read Developing Flex RIAs with Cairngorm microarchitecture – Part 1: Introducing Cairngorm before reading this part.
Beginning
Throughout the series you will find references to code taken from an e-commerce application named Cairngorm Store. You may use this sample application to gain a better understanding of Cairngorm, but please consider it only as a guide. Adobe is not responsible for maintaining the Cairngorm Store application.
Part 2 of this series focused on managing state within rich Internet applications using the Value Object and Model Locator patterns. In Part 3, the aim is to reach out to your application's users. While the concept of holding state on the client empowers developers, it means nothing to users unless they can visualize and interact with that state in a meaningful way. Part 3 continues to demonstrate concepts by example, again using samples from the Cairngorm Store.
The Cairngorm Store displays product images so that users can select products, view further details on them, drag and drop them into their shopping cart, and purchase these products in a checkout process. In this article, we'll explore how to architect the view and deliver rich, immersive experiences to users using the Cairngorm framework. We’ll describe some best practices that you can use to structure your MXML code and files, even though Cairngorm does not prescribe any specific way to structure your view.
The first and perhaps most important thing to understand about architecting your view logic with Cairngorm is that Cairngorm doesn't actually care how you do it! One key value of the Cairngorm architecture is that it is lightweight and places little imposition on the Flex developer, so as not to stifle individual technical flair.
Rather, Cairngorm offers clear integration points between the "rich" and the "application" in RIA.
The user interface must represent the application's current state by displaying screens and rendering the model in a specific way. This can include showing the products in Cairngorm Store as graphic thumbnails instead of tabular data, deciding which subset of the products to make visible based on a slider that specifies a product price range that the user selects, or recognizing that the user has finished shopping and guiding them to complete the checkout process. You make decisions on the dynamically rendered views by integrating the view with the model. This is done, in part, with Value Objects and the Model Locator pattern; we will discuss the final piece of the puzzle below.
A rich Internet application enables effective communication between the user and the application. The best form of that communication is bidirectional. While the user interface must "speak" to the user by rendering the view according to the state of the model, it must also "listen" to hear what the user wants to do with the application and respond accordingly. In this case, user requests are called gestures. When users click buttons, drag objects, complete and submit forms, and so on, they are gesturing to the application to indicate what they want to do. Responding to user gestures (by listening to the user), doing something on behalf of the user, and then "speaking" back to the user by updating the view (or, more correctly, updating the model, which you might now recognize automatically updates the view) becomes the flow of conversation between user and RIA. Later in this article, we'll explain the Cairngorm design patterns and architecture that can mediate the conversation between user and RIA.
Also in this article, we'll complete the discussion of the Model Locator pattern by explaining how you build the view to take advantage of data binding in the Cairngorm Store.
Next, even though Cairngorm does not prescribe the view, we'll offer some advice on best practices for structuring your view. You can use much of this advice even if you're not using Cairngorm. If you are using Cairngorm, however, heeding this advice will help you make architectural decisions about your MXML components.
In Part 4, we'll introduce you to a "microarchitecture within a microarchitecture," called the Service to Worker concept. Service to Worker is a collaboration of design patterns that the Adobe Consulting team first advocated within Cairngorm as a way to mediate the essential conversation between the user and the application on a feature-by-feature basis.
Part 2 of this series introduced the Model Locator pattern as a Singleton that can be used to store and retrieve states in your rich Internet application. As much as is appropriate, your application should hold state as Value Objects (VOs) and collections of Value Objects, rather than as primitive types (strings, numbers, and Booleans), which, by comparison, do a poor job of communicating information to fellow application developers.
Now let’s take a more detailed look at the Model Locator strategy, the challenges it can address, and how you can leverage the powerful data binding capabilities of the Flex framework as you work with the Model Locator and Value Object patterns.
When the user selects a product by clicking on it in the summary view, the Cairngorm Store displays detailed product information in a panel, as shown in Figure 1.
<details:ProductDetails
id="productDetailsComp"
width="100%"
height="325"
selectedItem="{model.selectedItem}"
currencyFormatter="{model.currencyFormatter}" />
<mx:Script>
<![CDATA[[
// set during MXML instantiation
public var selectedItem : ProductVO;
]]>
</mx:Script>
<mx:HBox>
<mx:Canvas
width="150" height="140"
clipContent="false">
<mx:Image
id="image"
source="{ selectedItem.image }"
mouseDown="beginDrag();"
mouseOverEffect="big" mouseOutEffect="small" />
</mx:Canvas>
<mx:VBox
width="100%" height="100%"
styleName="productDetailsTitle">
<mx:Label
id="name"
text="{ selectedItem.name }"
styleName="title" />
<mx:Label
id="price"
text="{ selectedItem.price}"
styleName="price" />
</mx:VBox>
</mx:HBox>
<mx:Text
id="description"
width="100%" height="100%"
text="{ selectedItem.description }"/>
</mx:VBox>
With these data bindings in place, any changes to the selectedItem instance held in ShopModelLocator causes the binding to execute in the <details:ProductDetails /> instance, thereby updating the selectedItem attribute of the ProductDetails component. This update causes each binding on selectedItem in ProductDetails to execute, resulting in an update to the image, name, description, and price to reflect the attributes of the newly selected item.
This is the basic premise of Flex data binding. Cairngorm provides an approach for ensuring that dynamic data displayed in your MXML comes from ShopModelLocator, and nowhere else.
The final piece of the puzzle is updating the selectedItem instance in ShopModelLocator. The code below instantiates the two components that are responsible for graphical and textual rendering of the entire product list. In the Cairngorm Store application, a ViewStack navigator container determines which one is shown at any one time.
<mx:Script>
<![CDATA[
[Bindable]
public var selectedItem:ProductVO;
]]>
</mx:Script>
<mx:Canvas clipContent="false" width="150" height="140">
<mx:Image
id="image"
source="{ selectedItem.image }"
mouseDown="beginDrag( event );"
rollOverEffect="zoom"
rollOutEffect="zoomOut"/>
</mx:Canvas>
<mx:VBox width="100%" height="100%" styleName="productDetailsTitle">
<mx:Label
id="itemName"
text="{ selectedItem.name }"
styleName="title" />
<mx:Label id="price" text="{ currencyFormatter.format( selectedItem.price ) }" styleName="price" /> </mx:VBox>
<mx:Text id="description" width="100%" height="100%" text="{ selectedItem.description }"/>
Both components expose select events, which the components dispatch when the user clicks a new product. To handle these events, as shown in the highlighted code above, the code simply updates the selectedItem instance on ShopModelLocator to a ProductVO value object representing the item that the user has just selected.
Because the model notifies the view of this change through data binding, the application developer does not need to worry about implementing the view or updating the many different elements of the view to reflect the model change.
The GraphicalProductList and TextualProductList components provide an example of the "single model, multiple view" strategy. In their instantiation, both of these components bind their individual products attributes to the same model, ShopModelLocator.products. The products property is an ArrayCollection of ProductVOs, as you may recall from Part 2.
In the GraphicalProductList component, this list of products becomes the dataProvider for a TileList, which has its own item renderer (ProductThumbnail) capable of parsing and rendering data from a ProductVO:
<mx:TileList id="tileListComp"
width="100%"
height="100%"
dataProvider="{ products }"
itemRenderer="com.adobe.cairngorm.samples.store.view.productview.ProductThumbnail"
change="updateSelectedProduct( event );" />
<mx:DataGrid id="dataGridComp"
dataProvider="{ products }"
change="updateSelectedProduct( event );"
dragEnabled="true"
width="100%" height="100%">
<mx:columns>
<mx:Array>
<mx:DataGridColumn dataField="name" headerText="Name" width="300" />
<mx:DataGridColumn dataField="price" labelFunction="format" headerText="Price" textAlign="right" />
</mx:Array>
</mx:columns>
</mx:DataGrid>
The preceding discussion touches on best practices that Adobe Consulting urges developers to adopt when implementing their own user experiences with MXML.
One key best practice is "to develop with components in mind." With MXML, it is incredibly easy to create components that extend base tags with application-specific functionality. If you have ever had to review someone else's MXML code and tried to trace your way through hierarchies of VBox controls containing HBox controls containing VBox controls containing TileList controls containing VBox controls, and so forth, you will quickly realize the value in creating components that have semantic meaning.
If you review the Cairngorm Store's code, you will quickly be able to cross-reference MXML components with their visual renderings on the screen. By creating components such as Main, SideArea, ProductDetails, ShoppingCart, ProductDetails, ProductThumbnail, GraphicalProductList, TextualProductList, ProductAndCheckoutViewStack, and so forth, you will be able to navigate your own source code more easily, never mind someone else's code.
When designing with components in mind, learn to use the events infrastructure within Flex to couple your components loosely. For instance, create your own events, which describe what happens at the user level, not at the Flex framework level. Consider the difference between a button within a VBox control having some code within a click="" event handler, versus a component called ShoppingCart that responds to an event called productAdded. When you create your own events, broadcast these events from within your components and handle these events accordingly. Your code base will become much more maintainable.
Again, consider the ProductDetails component in the Cairngorm Store, which is an application-specific rendering of a Panel component that broadcasts an event whenever the user clicks the Add to Cart button:
<mx:Panel
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:details="com.adobe.cairngorm.samples.store.view.productdetails.*"
title="Product Details"
styleName="productDetails">
<mx:Metadata>
[Event(name="addProduct", type="com.adobe.cairngorm.samples.store.event.AddProductEvent")]
</mx:Metadata>
<mx:Script>
<![CDATA[
import com.adobe.cairngorm.samples.store.vo.ProductVO;
import com.adobe.cairngorm.samples.store.event.AddProductEvent;
public function addProductToShoppingCart() : void
{
var event:AddProductEvent = new AddProductEvent();
event.product = selectedItem;
event.quantity = numericStepperComp.value;
dispatchEvent( event );
}
: : :
<mx:Button
label="Add to Cart"
click="addProductToShoppingCart();" />
</mx:Panel>
Here the click event of the button generates an addProduct event out of the component. Furthermore, the event includes the selectedItem (the product to add) and the quantity attribute associated with it on the ProductDetails component, which represents how much of the product the user wishes to add to the shopping cart.
Although this advice is in no way specific to Cairngorm application development, following these best practices for MXML development improves the clarity of your application code and makes it easier for you to recognize integration points between your MXML and ActionScript implementations of the view, as well as with any underlying business logic applied throughout the Cairngorm skeleton.
When developing an enterprise RIA, it is important to understand how to construct a view using the building blocks of MXML and ActionScript. Equally important is understanding how you can build this view on the underlying architecture of the Cairngorm skeleton.
In this series of articles, you have seen how to manage client-side state using the Model Locator and Value Object patterns. Although we've given some guidance and best practices to consider when architecting your view, Cairngorm makes no assumptions and imposes no restrictions on how you choose to architect your view.
Now that you have a better idea of how to implement the view and keep state on the view, it is time to look at how to implement features with the addition of business logic within commands—the "application" in rich Internet application. We cover that topic in Part 4.
Developing Flex RIAs with Cairngorm microarchitecture – Part 4: Feature-Driven Development