Welcome!

ColdFusion Authors: Maureen O'Gara, Hovhannes Avoyan, Yakov Fain, Pat Romanski, Liz McMillan

Related Topics: ColdFusion, Adobe Flex

ColdFusion: Article

Adobe Flex 2: Advanced DataGrid

Drop-in RadioButtonGroupBox; runtime computed styles vs itemRenderers; masked input and numeric input controls

Runtime Column Styles Unleashed
Here is the plan: we will upgrade the default itemRenderer of the fx:DataGrid from UITextField to our custom fx:Label by extending fx:DataGridColumn with an extra property - runtimeStyles. Finally, we will intercept data changes to an item renderer, whether default or not, to re-assign all runtimeStyles on each.

Here is how the constructor of the standard DataGrid assigns itemRenderer:

package mx.controls {
public class DataGrid extends DataGridBase implements IIMESupport
{
   public function DataGrid()     {
     super();
      itemRenderer = new ClassFactory(DataGridItemRenderer);
      . . . . . .
    }
   }
}

We would have to fight the instant temptation to replace the assignment of the itemRenderer with:

itemRenderer = new ClassFactory(com.theriabook.controls.Label);

Here is why: a ClassFactory instance is a "factory object," which is used to generate instances of another class (aka generator class) with the newInstance() method. According to our plan, we need to intercept data changes to any instance of the generator class item renderer. In other words, we'll need to listen to the event FlexEvent.DATA_CHANGE on every instance of the com.theriabook.controls.Label created by "the factory." Hmm, what could be simpler than adding the needed event listener to the controls? Although we are content with the fx:Label as a default renderer, by no means do we propose to take the power of the custom item renderers away. To make the mechanism of the runtimeStyles control agnostic, we would like to listen to FlexEvent.DATA_CHANGE on the instances of any generator class.

It only sounds difficult. After all, a ClassFactory is nothing but an implementation of the IFactory interface with a single property - properties and single method - newInstance(). So we can easily wrap a standard ClassFactory inside our custom one for the purpose of intercepting the newInstance() call. We will call our wrapping class factory the UIClassFactory:

function DataGrid() {
    super();
    itemRenderer = new UIClassFactory(ClassFactory(com.theriabook.controls.Label));
}

The constructor of the UIClassFactory would simply store the reference to the instance of the real class factory - cf. The newInstance() would delegate the call to the cf.newInstance(). In addition, it would also register the listener to FlexEvent.DATA_CHANGE event, as shown below:

public class UIFactory implements IFactory
{
. . . . .
public function UIClassFactory( cf:ClassFactory ) {
    wrappedClassFactory = cf;
}

public function newInstance():* {
    var obj:* = wrappedClassFactory.newInstance();
    obj.addEventListener(FlexEvent.DATA_CHANGE, onDataChange);
    return obj;
}

private function onDataChange(event:FlexEvent):void{
    . . . . .
}
}

As a reminder, the properties of the factory-manufactured objects get assigned by iterating over properties of the specific property of the factory object. The name of this aggregating property is properties. That way all instances are initialized with the same values. Wrapping up the property properties is quite simple:

public function set properties(v:Object):void {
    wrappedClassFactory.properties = v;
}
public function get properties():* {
    return wrappedClassFactory.properties ;
}

The complete code for UIClassFactory is available in Listing 7.

Let's talk about the onDataChange() handler. The implementations of both IDropInListItemRenderer and IDataRenderer events are sending us DATA_CHANGE events. We start by filtering out the events coming from the IDropInListItemRenderer part and leave only the ones coming from IDataRenderer. Then we single out the runtimeStyles property and treat it as a dynamic object carrying the property names that correspond to the style names. The values of these properties may be literal or, alternatively, the function references. In the latter case, we apply the call operator () prior to setting the style value with the setStyle().

One chore remains. As long as we want to communicate the runtimeStyles to any item renderer, including the ones that are individually set on a per column basis, we need to modify our DataGridColumn, overriding the implementation of the DataGridColumn's itemRenderer setter:

override public function set itemRenderer( val : IFactory ) : void {
    super.itemRenderer = new UIClassFactory(val as ClassFactory);
}

The code for our DataGridColumn is shown in Listing 8, and Listing 9 has our test application RuntimeStyleDemo. Figure 5 depicts the RuntimeStylesDemo running.

What's the cost of our automation? We have replaced the ultralight UITextField with the heavier UIComponent - Label so there must be a potential for performance degradation. On the other end, the DataGrid recycles item renderers by maintaining a pool of them just enough to cover the visible portion of the column, which outright limits possible damage.

o far we have shown that it is possible to control runtime styles via anonymous or explicit functions (backgroundColor versus computedFontWeight in the above demo application). You can take our approach further and completely outsource the dynamic styling to a separate controller object flexibly instantiated via the getDefinitionByName() method. Come to think of it, you would completely shield developers from formatting and styling problems of a particular project.

Masked Input and Numeric Input
The input masking stops a user from entering non-appropriate characters. Stricter masks can prevent users from entering non-complete numbers, lesser than the required text, etc. - details are always implementation-specific. Taken to an extreme, masks can completely obliterate validation programming, albeit at a cost of fixing a specific user experience.

The first class of this section - MaskedInput - has been created by Peter Ent from Adobe. We will be using it for illustrative purposes only; the code walkthrough of MaskedInput is beyond the scope of this article. MaskedInput is a lightweight mask in which you can indicate the maximum number of positions in the mask and prescribe the type of characters the user can type. The movement of the insertion point is controlled by the control. For example, if you set a mask for entering a U.S. phone number as (###) ###-####, in response to the end user typing 6175551212, the control will display (617) 555-1212.

Alpha keys will be blocked, but the completeness of the phone is up to the user. The control is an extension of mx.controls.TextInput and its text returns the "mask-free" data input, i.e., 61755551212, in our use case. The main controlling property of MaskedInput is inputMask, which can consist of any characters except:

  • #: A single digit
  • C: A letter (no digits allowed)
  • c: Forces a letter to lowercase (no digits allowed)
  • A or a: Allows any character.

More Stories By Victor Rasputnis

Dr. Victor Rasputnis is a Managing Principal of Farata Systems. He's responsible for providing architectural design, implementation management and mentoring to companies migrating to XML Internet technologies. He holds a PhD in computer science from the Moscow Institute of Robotics. You can reach him at vrasputnis@faratasystems.com

More Stories By Yakov Fain

Yakov Fain is a Managing Director of Farata Systems, consulting, training and product company. He has authored several Java books, dozens of technical articles. SYS-CON Books released his latest co-authored book , Rich Internet Applications with Adobe Flex and Java: Secrets of the Masters in Spring 2007. Sun Microsystems has nominated and awarded Yakov with the title Java Champion. He leads the Princeton Java Users Group. He is an Adobe Certified Flex Instructor. Currently Yakov works on the book for O'Reilly "Enterprise Application Development with Flex". He twits at twitter.com/yfain.

More Stories By Anatole Tartakovsky

Anatole Tartakovsky is a Managing Principal of Farata Systems. He's responsible for creation of frameworks and reusable components. Anatole authored number of books and articles on AJAX, XML, Internet and client-server technologies. He holds an MS in mathematics. You can reach him at atartakovsky@faratasystems.com

Comments (1) View Comments

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.


Most Recent Comments
CFDJ News Desk 12/17/06 11:15:01 PM EST

In Part 1 (CFDJ, Vol. 8, issue 10) we introduced the destination-aware grid, formatters, and renderers. In this article we are continuing our discussion about datagrid renderers and...