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.
Figure 1. Detail view of a product in Cairngorm Store
The implementation of the product details component uses data binding to bind the product details that are held in the selectedItem property (type ProductVO) of ShopModelLocator, the Model Locator. The ProductVO is ‘injected’ to this view in the following code:
<details:ProductDetails
id="productDetailsComp"
width="100%"
height="325"
selectedItem="{model.selectedItem}"
currencyFormatter="{model.currencyFormatter}" />
Drilling down into ProductDetails, note the use of data binding to render the individual elements of the selectedItem instance:
<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 );" />
Meanwhile, the TextualProductList component does nothing more than assign the list of ProductVOs to the dataProvider of a DataGrid, which renders the list of ProductVO instances accordingly:
<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>
This example demonstrates the Model Locator strategy in action. Each view component declares and renders its own local model or property and each waits for its data or model to be ‘injected’ from a parent, therefore each is completely oblivious to the application running within a Cairngorm architecture. Only at the very highest level of the component's usage—most often in the entry point MXML file—do you couple the component's usage to Cairngorm by populating and binding its local model to a model residing on ShopModelLocator.