views:

458

answers:

4

To illustrate the Q. I'll really over-simplify the example (in reality the code is much more convoluted).

Say you have a flex control, which underneath contains a datagrid. Something like

<mx:DataGrid id="grid" dataProvider="{document.items}">
    <mx:columns>
     <mx:DataGridColumn headerText="Column 1" dataField="@name"/>
     <mx:DataGridColumn headerText="Column 2" dataField="@value"/>
    </mx:columns>
</mx:DataGrid>

Where document is a Model object, containing some data. You provide a selection setter on the control, as the clients don't want to know anything about the underlying datamodel :

public function set selectedItem(title:String):Allocation
{
   grid.selectedItem  = null;

   for each(var o:Object in grid.dataProvider)
   {
      var t:String = o.@title;
      if( t == title )
      {
         grid.selectedItem = o;
         return;
      }
   }
}

So far, so good. Provided document.items is prepopulated, the selection will work correctly. However. What to do if you already know, at the startup of the application, what the selection ought to be - it's been passed (for example) on the URL? So, in the flex you might have something like

// Initialising now...
mycontrol.document = this.document; // give the control the document

// Fetch titles
new FetchTitlesEvent().dispatch(); // cairngorm-style event

// Set the selection 
mycontrol.selectedItem = Application.application.parameters.title;

OOps. Because FetchTitlesEvent operates asynchronously, at the time mycontrol.selectedItem is unable to work. Somehow we need to (re)trigger that code to set the selection on the control. Now, there's several ways I can think to do this, but all have code smell stenches:

1) Do it in the FetchTitlesCommand, after the fetch has been completed - This pollutes the command with knowledge of the view (or view*s*) that need to know this. Feels like a maintenance nightmare waiting to happen, and means the views are totally bound to commands, and those commands aren't re-usable. Blech.

2) Have a callback from the event when it's complete that does it (either make some composite command that starts in FetchTitlesEvent and ends in a new command to do the set). Seems fragile to me - how does the maintainer know which callbacks are neccesarily required? And it's still binding UI control knowledge into commands. Badness.

3) Have some kind of timer, waiting for the event queue to have been quiescent for a number of seconds. Hackity hackhack.

4) Do it in the control. Bind to the collectionevents in mycontrol on document.items, monitor for changes. Once the row arrives that matches the selection, select it, and stop monitoring for changes. - Control feels the right place to do it - collection events sometimes throw exciting CHANGE or REFRESH events though - seems an expensive monitor to have lying around

I'm pretty much leaning to (4). Are there any other options that flex-ers have used before to trap this issue - particularly are there any codified into libraries that I might use, as it must be a fairly general-purpose problem?

+1  A: 

The correct way to do this is with the creationComplete event if I am understanding your question correctly. All UI objects in flex broadcast a creationComplete event that lets you know when everything is done and it is ready to go. You can either listen for the creationComplete event on the grid itself or you can listen for it on the application since it will not broadcast until all of it's children (which is everything) is created.

Ryan Guill
A: 

Unfortunately it's not a creationcomplete issue - it's not the initialization of the control that's the problem, it's the initialization of the data. Since it's asynchronous, you don't know when it will eventually arrive.

+2  A: 

You could set the selectedItem property of the control to a Bindable Object variable:

<mx:Script>
    <![CDATA[
        [Bindable]
        private var mySelectedItem:Object;
    ]]>
</mx:Script>

<mx:DataGrid id="grid" dataProvider="{document.items}" selectedItem="{mySelectedItem}">
    <mx:columns>
        <mx:DataGridColumn headerText="Column 1" dataField="@name"/>
        <mx:DataGridColumn headerText="Column 2" dataField="@value"/>
    </mx:columns>
</mx:DataGrid>

This way can you set or reset the selectedItem at any time, and the control will update the selectedItem based on the Object. For instance, you could set in a Handler for the initialize Event, or creationComplete Event, or in any other function. You may need to call validateNow() on the control when you set the variable, or on creationComplete of the control if you set the variable during initialization.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" initialize="initializeHandler();">
    <mx:Script>
        <![CDATA[
            [Bindable]
            private var mySelectedItem:Object;

            private function initializeHandler():void
            {
                mySelectedItem = getSelectedItem(); // your logic to determine the initial item to select
            }

            ....

            private function grid_creationCompleteHandler():void
            {
                grid.validateNow();
            }
        ]]>
    </mx:Script>

    <mx:DataGrid id="grid" dataProvider="{document.items}" selectedItem="{mySelectedItem}" creationComplete="grid_creationCompleteHandler();">
        <mx:columns>
            <mx:DataGridColumn headerText="Column 1" dataField="@name"/>
            <mx:DataGridColumn headerText="Column 2" dataField="@value"/>
        </mx:columns>
    </mx:DataGrid>
</mx:Application>
Eric Belair
A: 

Try using a ChangeWatcher

var cw:ChangeWatcher = ChangeWather.watch(this, ["document","items"], 
function(e:Event):void
{
    //check to see if the data we want to select has arrived

    //once we've selected the data, we don't need this handler anymore
    cw.unwatch();
});

Pardon any syntactical bugs, I don't have FB in front of me.

rogueg