Home  >  

Building an Enterprise Framework - Enterprise Development with Flex, Part 1

Author photo
AddThis Social Bookmark Button

Note: This is excerpted from Chapter 3 of the Rough Cuts version of Enterprise Development with Flex. This book is still in progress, and you can get access to it now.

Rough Cuts is a service from Safari Books Online that gives you early access to content on cutting-edge technologies -- before it's published. It lets you literally read the book as it is being written.

Chapter 3. Building an Enterprise Framework

Table of Contents, Part 1

Introduction

Upgrading Existing Flex Components

Introducing Component Library clear.swc

Creating a Value-Aware CheckBox

Creating Centered CheckBox

Creating Protected CheckBox

Upgrading ComboBox

Resources as Properties of UI Controls

Styles vs. Properties

Programming today is a race between software engineers striving to build bigger and better idiot-proof programs, and the Universe trying to produce bigger and better idiots. So far, the Universe is winning.

--Rich Cook

Introduction

There is no such thing as perfect design. Flex framework is evolving, and we are grateful that software engineers from Flex team made this framework extendable. Because this book covers the use of Flex framework in enterprise software development, we will identify and enhance those components that are widely used in business RIA.

For the majority of the enterprise applications, development comes down to a few major activities:

  • Creating data grids

  • Working with forms

  • Validating data

  • Printing

If you, the architect, can achieve improvements in each of these areas by automating common tasks, application developers will spend less time writing the same mundane code over and over again. The key is to encapsulate such code inside reusable Flex components, to create smarter components that can be collected into libraries.

Chapter 1, Comparing Selected Flex Frameworks reviewed such architectural frameworks as Cairngorm, PureMVC and Mate, which mainly helped with separating the code into tiers, but now you'll learn how to build another type of a framework by enhancing existing Flex components. Specifically, this chapter demonstrates how to build a framework that radically simplifies creation of data entry applications by:

  • Identifying common reusable components, which in turn reduces the number of errors inevitably introduced during manual coding

  • Encapsulating implementation of architectural patterns inside selected components

  • Defining best practices and implement them in concrete components rather than just describing them on paper

You'll learn how to inherit your components from the existing ones, starting with the basic techniques while extending a simple CheckBox, then approaching more complex ComboBox component. The remainder of the chapter is devoted to extending components that every enterprise application relies on, namely DataGrid, Form and Validator.

By providing a framework that integrates the work of programmers, business analysts, designers, and advanced users, you can drastically simplify the development of enterprise applications.

Every Web developer is familiar with Cascading Style Sheets (CSS) that allow designers define and change the look and feel of the applications without the need to learn programming. As you'll learn in this chapter, Business Style Sheets (BSS) serve a similar role for enterprise application developers, enabling software developers to attach a remote data set to a component with minimum coding. For example, you'll see how a simple resource file can instruct a ComboBox (or any other component) where to get and how to display the data. Think of it as a data skinning. With BSS you can develop artifacts that are highly reusable across enterprise applications.

Along the way, you'll learn more about BSS and other techniques for enhancing and automating Flex components. Although you won't be able to build an entire framework here (the challenges of printing and reporting are covered in the last chapter), you'll get a good start in mastering valuable skills that any Flex architect and component developer must have.

Upgrading Existing Flex Components

Flex evolved as Flash framework from HTML object model, and the base set of Flex controls capitalized on simplicity of HTML. The price that Flex developers have to pay for this is that each control has its own (different) set of properties and behaviors. This can make building an enterprise framework a challenge. Consider a CheckBox control as an example.

To quickly and easily integrate CheckBox into a variety of frameworks, developers would prefer the component to have a unified property value (on or off ) that's easily bindable to application data. Currently, Flex's CheckBox has a property called selected and developers need to write code converting Yes/No the data into the true or false that the selected property expects. If you later use another control, you must then convert these Yes/No values into the form that the new control requires. Clearly some common ground would reduce the amount of redundant coding.

The sections that follow will take a closer look at the CheckBox as well as other major Flex components that every application needs, identify what are they missing, and how to enhance them.

Introducing Component Library clear.swc

As you may remember from Chapter 1, Comparing Selected Flex Frameworks, Clear Toolkit's component library, clear.swc, contains a number of enhanced Flex components (Figure 3.1, “The com.farata.components package from clear.swc”). Specifically, this component library consists of three packages

  • com.farata.components

  • com.farata.grid

  • com.farata.printing

To demonstrate how you can extend components, in the following sections we'll explain how we built some of the components from the package com.farata.component. Later you can use these discussions for reference, if you decide to built a similar (or better) library of components. (Some of the classes from the other two packages will be discussed in Chapter 11 of this book.)

Note

You can find the source code of all components described in this chapter in the clear.swc component library. The code of some of the components explained here was simplified to make explanations of the process of extending Flex components easier. Neither this chapter nor the book as a whole is meant to be a manual for the open source clear.swc library. If you just want to use clear.swc components, refer to https://sourceforge.net/projects/cleartoolkit/ where the asdoc-style API and the source code of each component from clear.swc is available.

Figure 3.1. The com.farata.components package from clear.swc

The com.farata.components package from clear.swc

You can use clear.swc independently by linking it to your Flex project. To help you understand how its components can help you, the following sections examine simplified versions of some of the library's controls.

Creating a Value-Aware CheckBox

The CheckBox in Example 3.1, “CheckBox with value and text properties” has been enhanced with additional value and text properties. You can specify which value should trigger turning this control into the on/off position.

Example 3.1. CheckBox with value and text properties

package com.farata.controls {
     import flash.events.Event;
     import flash.events.KeyboardEvent;
     import flash.events.MouseEvent;
 
     import mx.controls.CheckBox;
     import mx.events.FlexEvent;
 
     public class CheckBox extends mx.controls.CheckBox {
  
            public var onValue:Object=true;
            public var offValue:Object=false;
            private var _value:*;
  
            public function set text(o:Object):void {
                  value = o;
             }
            public function get text():Object {
                  return value;
             }
  
          [Bindable("valueCommit")]
            public function set value(val:*) :void {
                  _value = val;
                  invalidateProperties();
                  dispatchEvent(new FlexEvent (FlexEvent.VALUE_COMMIT));
             }
  
            public function get value():Object  {
                return selected?onValue:offValue;
             }
  
            override protected function commitProperties():void {
                  if (_value!==undefined)
                        selected = (_value == onValue);
                  super.commitProperties();
             }
      }
}

This CheckBox will automatically switch itself into a selected or unselected state: Just add it to your view, set the on and off values, and either assign the String or an Object value to it. Please note that the value setter calls the function invalidateProperties(), which internally schedules the invocation of the function commitProperties() on the next UI refresh cycle.

The commitProperties() function enables you to make changes to all the properties of a component in one shot. That's why we set the value of the selected property based on the result of comparison of _value and onValue in this function.

Example 3.2, “Test application for the value-aware CheckBox” is a test application illustrating how to use this CheckBox with the resulting interface shown in Figure 3.2, “Testing the value-aware CheckBox”. To run a test, click the first Set OnValue= button to teach the CheckBox to turn itself on when the value Male is assigned, and off when its property text has the value of Female. Then, click the first or second cbx_test.text button to assign a value to the newly introduced property text of this CheckBox, and watch how its state changes.

Example 3.2. Test application for the value-aware CheckBox

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:clear="com.farata.controls.*" layout="vertical">

    <clear:CheckBox id="cbx_test" label="Assign me a value" />

    <mx:Button label="Set OnValue='Male' and offValue='Female'"
         click="cbx_test.onValue='Male';cbx_test.offValue='Female';"/>

    <mx:Button label="cbx_test.text='Male'" click="cbx_test.text='Male'" />
    <mx:Button label="cbx_test.text='Female'" click="cbx_test.text='Female'" />

    <mx:Button label="Set OnValue=Number('1') and offValue=Number('0')"
         click="cbx_test.onValue=Number('1');cbx_test.offValue=Number('0');"/>

    <mx:Button label="cbx_test.value='Number('1')'"
              click="cbx_test.value =new Number('1')" />
    <mx:Button label="cbx_test. value='Number('0')"
              click="cbx_test.value =new Number('0')" />

</mx:Application>

Figure 3.2. Testing the value-aware CheckBox

Testing the value-aware CheckBox

Creating Centered CheckBox

This example demonstrates how to create a CheckBox that can center itself horizontally in any container, including a data grid cell.

Although you could introduce an item renderer that uses a CheckBox inside an HBox with the style horizontalAlign set to center, using a container inside the item rendered negatively affect the data grid control's performance.

The better approach is to extend the styling of the CheckBox itself. Here is a code extension that "teaches" a standard Flex CheckBox to respond to the textAlign style if the label property of the CheckBox is not defined:

Example 3.3. Self-centering solution for CheckBox

override protected function updateDisplayList(unscaledWidth:Number,
      unscaledHeight:Number):void {
 
     super.updateDisplayList(unscaledWidth, unscaledHeight);
     if (currentIcon) {
            var style:String = getStyle("textAlign");
            if ((!label) && (style=="center") ) {
                  currentIcon.x = (unscaledWidth - currentIcon.measuredWidth)/2;
       }
                 }
           }

In the code above, the x coordinate of the CheckBox icon will be always located in the center of the enclosing container. Because no additional container is introduced, you can use this approach in the DataGridColumn item renderer, which is a style selector. When you use this enhanced CheckBox as a column item renderer, textAlign automatically becomes a style of this style selector, and you can simply set textAlign=true on DataGridColumn.

Note

While developing enhanced components for the enterprise business framework, concentrate on identifying reusable functionality that application developers often need, program it once and incorporate it in the component itself.

Creating Protected CheckBox

The standard Flex CheckBox has a Boolean property called enabled that is handy when you want to disable the control. Unfortunately, disabled a CheckBox is rendered as grayed out. What if you want to use a CheckBox in some non-editable container, say in a DataGridColumn and you want it to be non-updatable but look normal.

The answer is to use a new class called CheckBoxProtected, which includes an additional property updatable. Its trick is to suppress standard keyboard and mouse click processing. Overriding event handlers by adding

if (!updateable) return;

works like charm! Example 3.4, “Class CheckBoxProtected” lists the complete code.

Example 3.4. Class CheckBoxProtected

package com.farata.controls
{
     import flash.events.Event;
     import flash.events.KeyboardEvent;
     import flash.events.MouseEvent;
     import mx.controls.CheckBox;
 
     public class CheckBoxProtected extends mx.controls.CheckBox {
  
      public var updateable:Boolean = true;
  
      public function CheckBoxProtected() {
             super();
             addEventListener(MouseEvent.CLICK, onClick);
       }
      private function onClick (event:MouseEvent):void {
             dispatchEvent(new Event(Event.CHANGE));
       }
      override protected function keyDownHandler(event:KeyboardEvent):void {
               if (!updateable) return;
               super.keyDownHandler(event);
       }
      override protected function keyUpHandler(event:KeyboardEvent):void {
               if (!updateable) return;
               super.keyUpHandler(event);
       }
      override protected function mouseDownHandler(event:MouseEvent):void {
             if (!updateable)return;
             super.mouseDownHandler(event);
       }
      override protected function mouseUpHandler(event:MouseEvent):void {
               if (!updateable)return;
               super.mouseUpHandler(event);
       }
      override protected function clickHandler(event:MouseEvent):void {
               if (!updateable)return;
               super.clickHandler(event);
           }
      }
}

To test the protected CheckBox use Example 3.5, “Test application for CheckBoxProtected”.

Example 3.5. Test application for CheckBoxProtected

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:clear="com.farata.controls.*" layout="vertical">

 <clear:CheckBoxProtected updateable="false"
                label="I am protected" fontSize="18"/>
 <mx:CheckBox enabled="false"
                label="I am disabled" fontSize="18"/>

</mx:Application>

Running this application produces the results in Figure 3.3, “Running CheckBoxProtectedApp”, which shows the difference between the protected and disabled checkboxes.

Figure 3.3. Running CheckBoxProtectedApp

Running CheckBoxProtectedApp

Why not use extensibility of Flex framework to its fullest? This chapter is about what you can do with Flex components. Armed with this knowledge you'll make your own decisions about what do you want to do with them.

For example, think of a CheckBox with a third state. The underlying data can be Yes, No and null. If the value is null (the third state), the CheckBox needs to display a different image, say a little question mark inside. In addition to supporting three states (selected, unselected and null) this control should allow an easy switch from one state to another. Such an enhancement includes a skinning task – create a new skin (with a question mark) in Photoshop and ensure that the control switches to this state based on the underlying data. For a working example, see CheckBox3Stated in the clear.swc component library.

Upgrading ComboBox

The CheckBox is easiest to enhance because it's one of the simplest controls, having only two states (on or off). You can apply the same principles to a more advanced ComboBox, however. Identify reusable functionality, program it once, and incorporate it into the component.

What if, for example, you need to programmatically request a specific value to be selected in a ComboBox? The traditional approach is to write code that loops through the list of items in the ComboBox data provider and manually work with the selectedIndex property. To set Texas as a selected value of a ComboBox that renders states, you could use:

var val:String; val= 'Texas' ;
for (var i: int = 0; i < cbx.dataProdider.length; i++) {
     if ( val == cbx_states.dataProvider[i].label) {
        cbx_states.selectedIndex = i;
        break;
    }
}

The downside of this approach is that if your application has fifty ComboBox controls, several developers will be writing similar loops instead of a single line, such as cbx_states.value="Texas".

Unfortunately, ComboBox does not provide a specific property that contains the selected value. It has such properties as labelField, selectedIndex, and selectedItem. Which one of them is actually a data field? How to search by value? Do you really care what's the number of the selected row in the ComboBox? Not at all you need to know the selected value.

Let's revisit the code snippet above. The labelField of a ComboBox knows the name of the property from the objects stored in backing collection. But what about the data field that corresponds to this label (say, in case of Texas, a good candidate to be considered as the ComboBox data could be TX)? Currently, finding such data is a responsibility of the application programmer.

Even if you are OK with writing these loops, considering an asynchronous nature of populating data providers, this code may need to wait until the data will arrive from the server. It would be nice though if you could simply assign the value to a ComboBox without the need to worry about asynchronous flows of events.

Consider a List control, the brother of the ComboBox. Say, the user selected five items, and then decided to filter the backing data collection. The user's selections will be lost. The List is also crying for another property that remembers selected values and can be used without worrying about the time of data arrival.

Example 3.6, “Class com.farata.control.ComboBoxBase” offers a solution: The class ComboBoxBase, which extends ComboBox by adding the value property (don't confuse it with ). After introducing the value property, it uses the dataField property to tells the ComboBox the name of the data field in the object of its underlying data collection that corresponds to this value. The new dataField property enables you to use any arbitrary object property as ComboBox data.

You'll also notice one more public property: keyField, which is technically a synonym of dataField. You can use keyField to avoid naming conflicts in situations where the ComboBoxBase or its subclasses are used inside other objects (say DataGridColumn) that also have a property called dataField.

Example 3.6. Class com.farata.control.ComboBoxBase

package com.farata.controls {
     import flash.events.Event;
 
     import mx.collections.CursorBookmark;
     import mx.collections.ICollectionView;
     import mx.collections.IViewCursor;
     import mx.controls.ComboBox;
     import mx.controls.dataGridClasses.DataGridListData;
     import mx.controls.listClasses.ListData;
     import mx.core.mx_internal;
     use namespace mx_internal;
 
     public class ComboBoxBase extends ComboBox {
  
      public function ComboBoxBase() {
        super();
        addEventListener("change", onChange);
       }
  
      // Allow control to change dataProvider data on change
      private function onChange(event:Event):void {
            if (listData is DataGridListData) {
                 data[DataGridListData(listData).dataField] = value;
             }else if (listData is ListData && ListData(listData).labelField
    in data) {
                 data[ListData(listData).labelField] = value;
             }
       }
  
      protected function applyValue(value:Object):void {
          if ((value != null) && (dataProvider != null)) {
              var cursor:IViewCursor = (dataProvider as
     ICollectionView).createCursor( );
                   var i:uint = 0;
                    for (cursor.seek( CursorBookmark.FIRST ); !cursor.afterLast;
                                                           cursor.moveNext(), i++) {
                         var entry:Object = cursor.current;
                         if ( !entry ) continue;
                         if ( (dataField in entry && value == entry[dataField])) {
                              selectedIndex = i;
                              return;
                          }
                     }
                }
               selectedIndex = -1;
           }
  
      private var _dataField:String = "data";
      private var _dataFieldChanged:Boolean = false;
  
      [Bindable("dataFieldChanged")]
      [Inspectable(category="Data", defaultValue="data")]
  
      public function get dataField():String { return _dataField; }
      public function set dataField(value:String):void {
             if ( _dataField == value)
             return;
   
             _dataField = value;
             _dataFieldChanged = true;
             dispatchEvent(new Event("dataFieldChanged"));
             invalidateProperties();
       }
  
      public function get keyField():String { return _dataField; }
  
      public function set keyField(value:String):void {
             if ( _dataField == value)
                  return;
             dataField = value;
       }
  
      private var _candidateValue:Object = null;
      private var _valueChanged:Boolean  = false;
  
      [Bindable("change")]
      [Bindable("valueCommit")]
      [Inspectable(defaultValue="0", category="General", verbose="1")]
  
      public function set value(value:Object) : void {
             if (value == this.value)
                  return;
   
            _candidateValue = value;
            _valueChanged = true;
            invalidateProperties();
       }
  
      override public function get value():Object {
            if (editable)
               return text;
   
            var item:Object = selectedItem;
   
            if (item == null )
               return null;
   
            return dataField in item ? item[dataField] : null/*item[labelField]*/;
       }
  
      override public function set dataProvider(value:Object):void {
              if ( !_valueChanged ) {
                   _candidateValue = this.value;
                   _valueChanged = true;
               }
              super.dataProvider = value;
       }
  
      override public function set data(data:Object):void {
            super.data = data;
            if (listData is DataGridListData) {
               _candidateValue = data[DataGridListData(listData).dataField];
               _valueChanged = true;
               invalidateProperties();
             }else if (listData is ListData && ListData(listData).labelField
    in data) {
               _candidateValue = data[ListData(listData).labelField];
               _valueChanged = true;
               invalidateProperties();
             }
           }
  
      override protected function commitProperties():void {
            super.commitProperties();
            if (_dataFieldChanged) {
                   if (!_valueChanged && !editable)
                      dispatchEvent( new Event(Event.CHANGE) );
    
                   _dataFieldChanged = false;
             }
   
            if (_valueChanged) {
                 applyValue(_candidateValue);
                 _candidateValue = null;
                 _valueChanged = false;
             }
       }
  
      public function lookupValue(value:Object, lookupField:String = null):Object {
         var result:Object = null;
         var cursor:IViewCursor = collectionIterator;
         for (cursor.seek(CursorBookmark.FIRST);!cursor.afterLast;cursor.moveNext()) {
               var entry:Object = cursor.current;
               if ( value == entry[dataField] ) {
                   result = !lookupField ? entry[labelField] : entry[lookupField];
                   return result;
                }
          }
         return result;
       }
   }
}

The new property value is assigned in the following setter function:

[Bindable("change")]
   [Bindable("valueCommit")]
   [Inspectable(defaultValue="0", category="General", verbose="1")]
    public function set value(value:Object) : void {
       if (value == this.value)
           return;
 
       _candidateValue = value;
       _valueChanged = true;
       invalidateProperties();
     }

Notice when the function turns on the flag _valueChanged, invalidateProperties()internally schedules a call to the method commitProperties() to ensure that all changes will be applied in the required sequence. In the example's case, the code in the commitProperties() function ensures that the value of the dataField is processed before explicit changes to the value property, if any.

ComboBox is an asynchronous control that can be populated by making server-side call. There is no guarantee that the remote data has arrived by the time when and when you assign some data to the value property. The _candidateValue in the value setter is a temporary variable supporting deferred assignment in the method commitProperties().

The function commitProperties() broadcasts the notification that the value has been changed (in case if some other application object is bound to this value) and passes the _candidateValue to the method applyValue().

override protected function commitProperties():void {
          super.commitProperties();
          if (_dataFieldChanged) {
                 if (!_valueChanged && !editable)
                   dispatchEvent( new Event(Event.CHANGE) );
  
              _dataFieldChanged = false;
           }
 
          if (_valueChanged) {
               applyValue(_candidateValue);
               _candidateValue = null;
               _valueChanged = false;
           }
     }

The method applyValue() loops through the collection in the dataProvider using the IViewCursor iterator. When this code finds the object in the data collection that has a property specified in the dataField with the same value as the argument of this function, it marks this row as selected.

protected function applyValue(value:Object):void {
        if ((value != null) && (dataProvider != null)) {
            var cursor:IViewCursor = (dataProvider as
   ICollectionView).createCursor( );
                 var i:uint = 0;
                  for (cursor.seek( CursorBookmark.FIRST ); !cursor.afterLast;
                                                           cursor.moveNext(), i++) {
                       var entry:Object = cursor.current;
                       if ( !entry ) continue;
                       if ( (dataField in entry && value == entry[dataField])) {
                            selectedIndex = i;
                            return;
                        }
                   }
              }
             selectedIndex = -1;
         }

Tags such as

[Inspectable(defaultValue="0",category="General", verbose="1")]

ensure that corresponding properties will appear in property sheets of ComboBoxBase in Flex Builder's design mode (in this case under the category General with specified initial values in defaultValue and verbose).

Metatags such as [Bindable("dataFieldChanged")] ensure that the dataFieldChange event will be dispatched (to those who care) whenever the value of the dataField changes.

This small application TestComboBoxApp.mxml demonstrates the use of the ComboBoxBase component.

Example 3.7. Using the ComboBoxBase component

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:clear="com.farata.controls.*" layout="vertical">
    <mx:ArrayCollection id="cbData">
          <mx:Array>
               <mx:Object label="Adobe" data="ADBE" taxID="1111"/>
               <mx:Object label="Microsoft" data="MSFT"  taxID="2222"/>
               <mx:Object label="Farata Systems" data="FS"  taxID="3333"/>
          </mx:Array>
    </mx:ArrayCollection>

    <clear:ComboBoxBase  dataProvider="{cbData}" value="FS"/>

    <clear:ComboBoxBase  dataProvider="{cbData}" dataField="taxID" value="3333"/>

</mx:Application>

Both dropdowns use the same dataProvider. When you run Example 3.7, “Using the ComboBoxBase component”'s application, you'll see a window similar to Figure 3.4, “Running an application with two ComboBoxBase components”. window:

Figure 3.4. Running an application with two ComboBoxBase components

Running an application with two ComboBoxBase components

The first ComboBoxBase shows Farata System because of the assignment value="FS" that compares it with values in the data field of the objects from cbData collection.

The second dropdown sets dataField="taxID" that instructs the ComboBox to use the value of taxID property in the underlying data collection. If the code will assign a new value to taxID, i.e. an external data feed, the selection in the ComboBox will change accordingly. This behavior better relates to the real-world situations where a collection of DTO with multiple properties arrives from the server and has to be used with one or more ComboBox controls that may consider different DTO properties as their data.

Resources as Properties of UI Controls

Even more flexible solution for enhancing components to better support your enterprise framework is through the use of a programming technique that we call data styling or Business Style Sheets (BSS). The basic process is to create small files, called resources, and attach them as a property to a regular UI component as well as a DataGrid column.

Example 3.8, “A CheckBox resource” illustrates this BSS technique and contains a small MXML file called YesNoCheckBoxResource.mxml:

Example 3.8. A CheckBox resource

<?xml version="1.0" encoding="utf-8"?>
<fx:CheckBoxResource
    xmlns="com.farata.resources" xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:resources="com.theriabook.resources.*"
    offValue = "N"
    onValue = "Y"
    textAlign="center"
    >

</fx:CheckBoxResource>

Doesn't it look like a style to you? You can easily make it specific to a locale too by, for example changing the on/off values of Y/N to Д./Н, which mean Да/Нет in Russian, or Si/No for Spanish. When you think of such resources as of entities that are separate from the application components, you begin to see the flexibility of the technique. Isn't such functionality similar to what CSS is about?

As a matter of fact, it's more sophisticated than CSS because this resource is a mix of styles and properties, as shown in Example 3.9, “StateComboBoxResource with hard-coded states”. Called StateComboBoxResource.mxml, this resource demonstrates using properties (i.e. dataProvider) in a BSS. Such a resource can contain a list of values such as names and abbreviations of states:

Example 3.9. StateComboBoxResource with hard-coded states

<?xml version="1.0" encoding="utf-8"?>
<fx:ComboBoxResource
    xmlns="com.farata.resources" xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:resources="com.theriabook.resources.*"
    dropdownWidth="160"
    width="160"
    >
    <fx:dataProvider>
          <mx:Array>
               <mx:Object data="AL" label="Alabama" />
               <mx:Object data="AZ" label="Arizona" />
               <mx:Object data="CA" label="California" />
               <mx:Object data="CO" label="Colorado" />
               <mx:Object data="CT" label="Connecticut" />
               <mx:Object data="DE" label="Delaware" />
               <mx:Object data="FL" label="Florida" />
               <mx:Object data="GA" label="Georgia" />
               <mx:Object data="WY" label="Wyoming" />
          </mx:Array>
    </fx:dataProvider>
</fx:ComboBoxResource>

Yet another example of a resource, Example 3.10, “Sample DepartmentComboResource configured for a remote destination” contains a reference to remote destination for automatic retrieval of dynamic data coming from a DBMS:

Example 3.10. Sample DepartmentComboResource configured for a remote destination

<?xml version="1.0" encoding="utf-8"?>
<fx:ComboBoxResource
    xmlns="com.farata.resources" xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:resources="com.theriabook.resources.*"
    width="160"
    dropdownWidth="160"
    destination="Employee"
    keyField="DEPT_ID"
    labelField="DEPT_NAME"
    autoFill="true"
    method="getDepartments"
    >
</fx:ComboBoxResource>

As a matter of fact, you can't say from this code if the data is coming from a DBMS or from somewhere else. That data is cleanly separated from the instances of the ComboBox objects associated with this particular resource and can be cached either globally (if the data needs to be retrieved once) or according to the framework caching specifications. When developing a business framework you may allow, for example, lookup objects to be loaded once per application or once per view. This flexibility doesn't exist in singleton-based architectural frameworks. Frameworks built using the resource technique/BSS, however, do allow the flexibility to lookup objects.

Based on this resource file you can only say that the data comes back from a remote destination called Employee, which is either a name of a class or a class factory. You can also see that the method getDepartments() will return the data containing DEPT_ID and DEPT_NAME that will be used with the enhanced ComboBox described earlier in this chapter (Example 3.6, “Class com.farata.control.ComboBoxBase”).

In addition to such resources, however, you need a mechanism of attaching them to Flex UI components. To teach a ComboBox to work with resources, add a resource property to it:

private var _resource:Object;
          public function get resource():Object
          {
                return _resource;
           }

          public function set resource(value:Object):void {
                _resource = value;
                var objInst:*  = ResourceBase.getResourceInstance(value);
                if(objInst)
                      objInst.apply(this);
           }

The section "the section called “The Base Class for Resources”" will detail the ResourceBase class. For now, concentrate on the fact that the resource property enables you to write something like this:

<fx:ComboBox resource="{DepartmentComboResource}"

Each of the enhanced UI components in your framework should include such property. Because interfaces don't allow default implementation of such setter and getter and because ActionScript does not support multiple inheritance, the easiest way to include this implementation of the resource property to each control is by using the language compile-time directive #include, which includes the contents of the external file, say resource.as, into the code of your components:

#include "resource.as"

Styles vs. Properties

Before going too deep into the BSS and resources approach, you need to understand some key differences between styles and properties. For instance, although simple dot notation (myObject.resource=value) is valid Flex syntax for properties, it is not allowed for styles. Instead, application programmers have to use the function setStyle(). Suffice to say the StyleManager handles styles that can be cascading, while properties can't cascade. From the framework developer's point of view, properties allow defining classes with getters and setters and take advantage of inheritance. With styles, you can't do this. On the other hand, you can't add properties (i.e. value and destination) to styles.

Designers of Flex framework separated styles from properties for easier separation of internal processes – if an application code changes the style, Flex frameworks performs some underground work to ensure that cascading style conventions are properly applied; for example, global style that dictates Verdana font family is properly overridden by the style applied to a Panel or its child.

From the designer of an enterprise framework perspective, this means that if you create a base class for the styles, and some time later, decide to change it, the change may affect all derived classes. Suppose you subclassed ComboBox and defined some new styles in derived MyComboBox and then later you change the style of the ComboBox. For the descendent class this means that now code changes are required to properly (according to the changed rules) apply the overridden and added styles.

All this explains, why every book and product manual keeps warning that styles are expensive and you should limit the use of the setSyle() function during the runtime. With properties, life is a lot easier.

A beneficial framework would allow application programmers to define a small named set of application-specific styles and properties and the ability to govern the work of the UI control with selectors.

To accomplish this, get into the DataGrid state of mind

Have you ever thought of how a DataGridColumn object sets its own width, height and other values? The DataGridColumn class is a descendent of a style selector called CSSStyleSelector, which means it can be used to modify styles but not properties.

DataGrid examines every DataGridColumn and asks itself, "Do I have the same as this column object in my cache?" If it does not, it answers, "Nope, there's nothing I can reuse. I need to create a new class factory to supply a new item renderer." After this is done, the DataGrid code assigns the supplied DataGridColumn to item renderer as a style. (Search for renderer.styleName=c in the code of DataGridBase.as to see for yourself. ) At this point, all the specified column's styles (height, width, color, text alignment) are applied as styles to the item renderer.

Treat DataGridColumn as a CSS style selector that also includes a limited number of properties (i.e. itemRenderer). DataGrid creates on instance of such selector object and then re-applies it to ever cell in this column.

Unfortunately, designing a DataGrid this way makes it next to impossible to externalize this CSS style selector, and you can't extend the properties of the data grid column to make them specific to the item renderer. Say, you wanted to use a CheckBox with a property value (on/off) as an item renderer. Tough luck – DataGridColumn is not a dynamic object and you can't just add this as a new property.

Flex is an extendable framework, however, and what you can add is a new resource class with behaviors more to your liking. In fact, that's exactly what the ResourceBase class does, and it's described next.

Continue to Part 2 of this chapter.

Read more from Yakov Fain. Yakov Fain's Atom feed

Comments

8 Comments

Benji Smith said:

Oops. Your HTMLs are showing :)

Raven said:

Can not understand the lay out . Also the code editor was so terrible that we even could see HTML tags like "span". God , please save me

Rachelj said:

Hey guys, thanks for catching this. Obviously, the producer (me!) wasn't paying enough attention when prepping this excerpt. The extraneous tags have been removed.

Yakov Fain said:

There's more cleanup to be done. All < and > have to be replaced with correspondingly.

Rachelj said:

Sorry about that, our code formatting seems to have gotten things backward here. This is fixed now also.

Yakov Fain said:

Almost there :)

The escaped minus sign seems to be the last thing to fix (just search for the word minus).

yurec said:

"and you can simply set textAlign=true on DataGridColumn." seems to be "and you can simply set textAlign=center on DataGridColumn."
and
var val:String; val= 'Texas' ;
for (var i: int = 0; i if ( val == cbx_states.dataProvider[i].label) {
cbx_states.selectedIndex = i;
break;
}
}
=>
var val:String; val= 'Texas' ;
for (var i: int = 0; i if ( val == cbx_states.dataProvider[i][labelField]) {
cbx_states.selectedIndex = i;
break;
}
}

Human League said:

"We're only human,

Born to make mistakes....!"

Leave a comment


Tag Cloud

iPad

What's your take on the iPad? (Putting aside the Flash/iPad flame war)

Answer

Latest Features

Recommended for You

@InsideRIA on Twitter

Archives

  • Or, visit our complete archive.  

About This Site

Welcome to the premiere community site for all things RIA sponsored by O'Reilly Media and Adobe Systems Incorporated.