Tom Sugden: Patterns Archives

December 10, 2009

Writing Genuinely Reusable Flex Components

On larger projects and within enterprises, there's often a case for extracting a set of reusable components into a Flex library project. In theory, the same components can be reused across modules and sub-applications of multiple Flex or AIR clients, bringing greater consistency and more rapid development. However, in practice there are some common mistakes that limit the reusability of components. This post explains what makes a component genuinely reusable and highlights some techniques from the Flex SDK that can be applied to your own components to make them more reusable.

What Makes a Component Genuinely Reusable?

There are different levels of reusability, but a fully reusable component should be able to render any kind of data. It should be equally comfortable with an array of basic, dynamic Objects or a collection of concrete Kangaroos. The Flex DataGrid has this quality:

<mx:DataGrid dataProvider="{ kangaroos }">
   <mx:columns>
      <mx:DataGridColumn headerText="Name" dataField="name"/>
      <mx:DataGridColumn headerText="Weight" labelFunction="calculateWeight"/>
   </mx:columns>
</mx:DataGrid>

Notice how the dataField and labelFunction properties tell the component how to get its data from the Kanagoo objects without imposing any dependency. These are two of the mechanisms available to make a component genuinely reusable. Even if the developer has no control over the Kangaroo class itself, perhaps it's part of a 3rd-party library, they can still easily render these objects within a DataGrid.

The Data Interface Anti-Pattern

One common mistake is to insist that the data being rendered by a component implements a specific interface. For example, consider a DistributionBar component that renders a simple graph, like that shown in Figure 1.

DistributionBar.png

Figure 1 - A Distribution Bar Component

The distribution bar shows a number of regions with different sizes, each containing a label. It's tempting to configure this using an array of IRegion objects:

public interface IRegion
{
   function get label() : String;
   function get size() : int;
}

The distribution bar can then extract the size and label information for each region through this interface. The rationale for this is that the interface decouples the component from the concrete objects it renders. Anything can be rendered so long as it implements IRegion, but that so long is in fact the design flaw. By imposing the use of the IRegion interface the reusability is limited. The interface needs to be added to existing model classes before they can be rendered in a distribution bar, and worse, if the models were produced by another library or separate team, it might not be an option to change them, so they'd need to be wrapped. For these reasons, the component is not genuinely reusable.

Reusable Components of the Flex SDK

The Flex SDK contains many reusable components and this is achieved by applying a few standard approaches:

  1. Data Fields
  2. Data Functions
  3. Data Descriptors
  4. Factory Objects

These approaches are now described and the same techniques can be applied to your own components to make them reusable.

Data Fields

A data field is a String property that specifies the name of another property. For example, the labelField property of the ComboBox or the dataField and dataTipField properties of the DataGridColumn:

<mx:ComboBox dataProvider="{ items }" labelField="name"/>

The component implementation uses the data field to read the data values from the items it renders. For example:

for each (var item:Object in dataProvider)
{
    var value:Object = item[dataField];
    // do something with the value
}

This is a simple approach but it offers great flexibility. The component can render any readable property of any class of object.

Data Functions

A data function is a property of type Function that is used to specify a reference to another function. For example, the labelFunction property of the ComboBox or the dataFunction property of the DataGridColumn.

<mx:DataGridColumn headerText="weight" dataFunction="calculateWeight"/>

The component then invokes the data function, typically passing through an item of data as a parameter. For example:

for each (var item:Object in dataProvider)
{
    var value:Object = dataFunction(item);
    // do something with the value
}

This approach is similar to using a data field, but offers even more flexibility, since the function can perform calculations or formatting before returning a value to the component for rendering.

Data Descriptors

A data descriptor is an interface through which a component can analyze the items of data it renders. The developer can then pass their own implementation of the interface to the component in order to configure it. An example can be seen in the Tree component of the Flex SDK:

<mx:Tree dataProvider="{ items }">
    <mx:dataDescriptor><my:MyDataDescriptor/></mx:dataDescriptor>
</mx:Tree>

The tree can then discover characteristics of the data by querying its data descriptor interface. For example:

for each (var item:Object in dataProvider)
{
    var isBranch:Boolean = dataDescriptor.isBranch(item, dataProvider);
    // do something with the outcome
}

This approach is very powerful, but is only necessary for complex components such as the Tree. The effort of using the component is more than a simple List or ComboBox, but the component is still completely decoupled from the data it renders. If a developer needs to render a new class of object in a tree, they typically write a new implementation of the ITreeDataDescriptor interface.

Factory Objects

A factory object, in terms of component developent, is a property of type IFactory that is used to instantiate children at runtime. For example, the itemRenderer property of the List and DataGrid or the dropdownFactory property of the ComboBox.

<mx:List dataProvider="{ items }" itemRenderer="my.package.MyItemRenderer"/>

The component uses the standard IFactory interface of the Flex SDK to create new objects at runtime:

var itemRenderer:Object = itemRenderer.newInstance();

Then it passes data items into the new object through the IDataRenderer interface:

if (itemRenderer is IDataRenderer)
{
    IDataRenderer(itemRenderer).data = item;
}

This approach gives great control over the visual appearance of parts of the component. By providing custom item renderers, completely different results can be achieved. The logic for processing the item of data can be as simple or complex as needed and it can be encapsulated inside the item renderer class. Having said that, components that use factories should define sensible default values, so the component is easy to use in simple cases without setting special factories. This is the case for all the ListBase components, such as DataGrid, which uses the general purpose DataGridItemRenderer by default.

It's worth noting here that the Flex compiler has a special relationship with properties of type IFactory. When it notices such a property being set in MXML, it will automatically generate code to convert class names and in-line components into instances of ClassFactory. This makes the components easier to use, so developers don't usually need to instantiate class factories manually, but instead just specify a class name or declare an in-line component.

Conclusion

When fully reusable components are needed, it's good to remember the simple rule: a reusable component should be able to render any kind of data. This is best achieved by following the conventions laid out in the Flex SDK, such as data fields, data functions, data descriptors and object factories. It's important to resist the urge to introduce new interfaces that impose unnecessary obligations on users of your component, because to do so limits its reusability.

Postscript: Since fully reusable components tend to use mechanisms such as dynamic property lookups and function references, there are some trade-offs to consider. These techniques are slower than accessing properties of strongly-typed objects and they're not resiliant to compile-time type checking. However, the advantages of flexibility and reduced dependencies can outweigh these drawbacks for larger projects and enterprises.

Posted by tsugden at 9:18 AM | Comments (2)

August 24, 2009

Applying the Presentation Model in Flex

Introduction

The purpose of the Presentation Model design pattern is simple to grasp: it’s a way of moving state and logic out of a view component and into another class, where it can be developed, comprehended and unit tested more easily. The pattern promotes a clean separation of concerns, helping to keep MXML views as structural definitions, free from Script-block logic. However, there are several ways to apply presentation models in Flex. Some of these can help us to build simpler, more testable systems, while others may lead towards entanglement and maintenance difficulties.

For a comprehensive introduction to the Presentation Model pattern and a comparison between it and other presentation patterns, please refer to Paul Williams' blog series on the topic. At Adobe Professional Services, we have found the Presentation Model to be well suited to Flex, since it takes advantage of language features like bindings and in-line event handlers, while side-stepping some of the difficulties of unit testing display objects.

This article discusses two approaches to applying the Presentation Model -- hierarchical and componentized -- and makes a recommendation in favour of the latter.

Two Approaches: Hierarchical or Componentized

Hierarchical

With the hierarchical approach to the Presentation Model (PM) pattern, a hierarchy of PMs is developed that reflects the hierarchy of view components, as shown in the Figure 1.

PM1.png

Figure 1 - A hierarchy of views and presentation models.

Each MXML view component has a corresponding PM. The parent view contains the child views, and likewise, the parent PM contains the child PMs. Below these View-to-PM pairs, there are only basic UI controls, implemented in ActionScript without corresponding PMs.

With this approach, the top-level PM is usually instantiated in the top level view. It may be a singleton to allow other parts of the application, such as commands to access it as required. The child PMs are then passed down manually into the child views, as shown below:

<example:MyComponent ... >
<mx:Script>
[Bindable] public var model:MyComponentPM;
</mx:Script>
<example:MyChildComponent model="{ model.myChildComponentPM }" ... />
...
</example:MyComponent/>

When a hierarchy of presentation models is established, coordination takes place by method calls between the PMs, sharing objects amongst them, and dispatching events up the hierarchy. So if a user selects an item from a DataGrid in View 2, a selectedItem property may be updated on PM 2 and an event dispatched to announce the selection. PM 1 may listen to this event and perform its own logic in response.

Componentized

With the componentized approach to the Presentation Model pattern, there is a single hierarchy of view components but no hierarchy of presentation models. Each view component has a corresponding presentation model, but these models are independent of one another, as shown in Figure 2.

PM3.png

Figure 2 - A hierarchy of components, each with a presentation model

With this approach, the PMs are usually injected into their corresponding views using the view-wiring mechanism of an IoC framework. For example, the following code could be used with Parsley 2:

<example:MyComponent ... 
   addedToStage="dispatchEvent(new Event('configureIOC', true'))">
<mx:Script>
[Inject] [Bindable] public var model:MyComponentPM;
</mx:Script>
<example:MyChildComponent/>
...
</example:MyComponent/>

Here the configureIOC event instructs Parsley to inject a presentation model instance, declared in a configuration file, into the model property of the view. Notice that there is no need to pass a model into the child component. Each component is self-contained and takes care of its own business.

A variation of this approach is to declare the presentation model directly in the view, as shown below:

<example:MyComponent ... >
<example:MyComponentPM id="model"/>
<MyChildComponent/>
...
</example:MyComponent/>

Although the presentation models are kept independent of one another with the componentized approach, there remains a need to coordinate the components in some way. This can best be achieved using an Inversion-of-Control (IoC) framework, such as Parsley, Swiz or Spring ActionScript. There are ways to do so:

  • Messaging - route messages directly between the models using whatever mechanism your preferred framework provides. Parsley 2 includes a loosely-coupled messaging framework; Swiz has a notion of mediated events; while Cairngorm MVC has a singleton event dispatcher that can serve a similar purpose.
  • Domain Interfaces - inject shared domain models into multiple PMs. Coordination then takes place when the PMs call methods on the interfaces to these models, and listen for events dispatched by them. All IoC frameworks support this feature.
  • Controller/Presenter - use separate classes, known as controllers or presenters, to coordinate multiple PMs. These classes are typically injected with a number of presentation models, using an IoC framework. The controllers then listen for events and invoke methods on the PMs.

The relative merits of these are not discussed here, but will be tackled in another article. Each approach achieves a similar result of de-coupling the components from one another. The two approaches to the Presentation Model pattern are now compared in terms of their responsiveness to change.

Responsiveness to Change

It is common for the visual designs of a user interface to change during development, based on user feedback, client demands, or moments of creative inspiration from the designers. In my experience, this usually happens when I've just put the finishing touches on the implementation, the pixels are perfectly aligned and the unit tests running green! So it's important to write code in such a way as to minimize the cost of change, allowing components to be manoeuvred from place to place and logic to be reused without great effort.

Consider the case where a view component needs to move from one region of the user interface to another. Starting with the simple hierarchy shown earlier in Figure 1, changes are required to four classes, now highlighted in red in Figure 3.

PM2.png

Figure 3 - Moving part of a View-and-PM hierarchy


View 3 needs to be detached from its starting place in View 1 and re-declared in View 2. Similarly, the reference to PM 3 contained in PM 1 needs be removed and introduced to PM 2. Any coordination logic that was in PM 1 also needs to be moved into PM 2.

In contrast, the componentized approach, first shown in Figure 2, responds more easily to change. Only the declaration of View 3 needs to be moved from View 1 to View 2, as highlighted in red in Figure 4.

PM4.png

Figure 4 - Moving a component within a hierarchy

Since the logic for the View-to-PM components is self-contained and coordination takes place externally, though messaging, domain interfaces, or controller/presenters, no further changes are necessary. If test-driven development is being practiced, the unit tests for the PMs also remain intact, whereas they would need to be refactored with the hierarchical approach.

Conclusion

The Presentation Model is a useful pattern for building testable Flex applications. By moving the state and logic used to present data and handle user gestures into PMs, it can be unit tested in isolation and understood more easily than Script-block logic placed directly within views. However, rich user interfaces can be somewhat volatile, changing their shape often during development, so it is recommended to apply a componentized version of the Presentation Model that is easy to adapt, rather than developing and ultimately maintaining a hierarchy of connected presentation models.

Posted by tsugden at 9:32 PM | Comments (6)