May
06

2

Cairngorm - 1 Data Binding

In my introduction to Cairngorm I covered a list of features that I have found very handy. One of the most useful, and easiest to understand, is Cairngorm’s ability to maintain application state. Before adopting Cairngorm, I found myself passing data all over my applications. Not only was it hard to track down bugs, it made the application tightly bound. Cairngorm uses a singleton to keep application state. This is achieved though an empty interface called ModelLocator. Empty Interface? Yes, that’s right, it’s an empty interface that isn’t referenced anywhere. At first I was puzzled by this, and left it out of my code. After several projects, I decided to add the ModelLocator reference back into my classes. Although it is useless and not needed, it does let you know right away that the class is a singleton.


So what should your model classes look like? Here’s a singleton pattern with an interface reference:

// SampleModel.as

    
package ca.model
    {
        import com
.adobe.cairngorm.model.ModelLocator;
        
import mx.collections.ArrayCollection;
        
        
[Bindable]
        
public class SampleModel implements ModelLocator
        {
            
protected static var instance SampleModel ;        
            
            public var 
listData : Array;
            
            public static function 
getInstance() : SampleModel
            { 
                
if ( instance == null )
                    
instance = new SampleModel (); 
                
                return 
instance
            
}                    
            
            
public function SampleModel()
            
{
                
if ( instance != null 
                    throw new 
Error"Only one SampleModel instance should be created" );
                
                
listData = new Array();
            
}
        }    
    } 


 

A bit of a tangent, but worth covering: You might notice is that the constructor is public. ActionScript doesn’t allow for any constructor modifier other than “public.” Also the class throws an error if the constructor is called more than once. In fact, you should never call the constructor. Always use the getInstance() method. This ensures that the constructor is only called on time.

The following example demonstrates passing data via bindings as well as using the ModelLocator. As I mentioned before, the bindings method forces your application to be tightly bound and that is a bad thing.

// Tutorial_1_Bindings.mxml

    // Some Random Data
    
[Bindable]
    
public var listData : Array = [
        {id
:0,title:"Apple"},
        
{id:1,title:"Banana"},
        
{id:2,title:"Kiwi"},
        
{id:3,title:"Lemon"},
        
{id:4,title:"Mango"},
        
{id:5,title:"Strawberry"} ]


When using the bindings method to pass data, you assign the listData Array to the listData parameter in your AngryBindingPanelL1:

// Tutorial_1_Bindings.mxml
    
    
<AngryBindingPanelL1 listData="{listData}" /> 


The parameter listData is declared in AngryBindingPanelL1 as a public bindable property:

// AngryBindingPanelL1.mxml
    
    
[Bindable]           
    
public var listData : Array; 


Then this data is passed to the AngryBindingPanelL2 class. Yet another bindable property. As you can see, these bindable variables are just conduits for data. The classes declaring them don’t use them, and the classes’ knowledge of their children is a bit suspect, causing the application to become tightly bound:

// AngryBindingPanelL1.mxml
    
   
<containers:AngryBindingPanelL2 listData="{listData}" /> 


My apologies for showing you this again, but this is the code found in AngryBindingPanelL2. If you think it looks like what is in AngryBindingPanelL1, that’s because it does:

// AngryBindingPanelL2.mxml
    
    
[Bindable]           
    
public var listData : Array; 


Lastly in order to get our data to the menu, we just bind it to the ComboBox:

// AngryBindingPanelL2.mxml

    
<mx:ComboBox dataProvider="{ listData }" 
                 
labelField="title" /> 


In case you weren’t able to follow all that, there is a binding chain that looks like the following Application.listData -> AngryBindingPanelL1.listData -> AngryBindingPanelL2.listData -> ComboBox.dataProvider. We had to add a listData parameter in AngryBindingPanelL1 and in AngryBindingPanelL2 even though those classes never used it. 

Now let’s see what the ModelLocator method brings to the table. There is an additional step in the Application, we set the data in the model. Later you will see us setting data from anywhere we want, but for this example we are going to set the data in the init function. This is the same Array from before, but this time it has the init function below it:

// Tutorial_1_Bindings.mxml
    
    
public function init() : void
    {
        SampleModel
.getInstance().listData = new ArrayCollectionlistData );
    


Instead of passing the listData to the AngryBindingPanelL1, and then the AngryBindingPanelL2, we are simply setting the data in the init function and forgetting about it. This is nice because it frees you to set and get data where it is relevant in the application, and keep classes not involved with the data out of the mix. Our data is set so now all we have to do is access it in the right spot. In our case that spot is the dataProvider on the ComboBox:

//  Tutorial_1_Bindings.mxml
    
    
<mx:ComboBox dataProvider="{ SampleModel.getInstance().listData }"   
                 
labelField="title" /> 



Conclusion

That’s it! Next time you find yourself passing data through objects that could care less about the data, think about using the ModelLocator. Happy Coding!

Next lesson - Events

Comments

Thanks for sahrnig. Always good to find a real expert.

Posted by Kenelm on Aug 23, 2011

OK, think I got this one figured out too.When spnwpiag dataproviders, then its necessary to use the callLater method before the ceil and floor is calculated. If you dont do this, the the ceiling and floor is based on the old data, and not the new data - so the intervals may be totally wrong. So, we have to let the chart calculate the new intervals first, and then increment them using ceil and floor.private function updateVerticalAxisMinMax():void {  var max:Number = 0;  var min:Number = 0;  var count:int = 0;  for each (var series:LineSeries in linechart.series) {    if (series.visible) {    // contains LineSeriesItem objects     var items:Array = series.items;    // Get the min and max y values for each item from the yNumber property     var minMax:Array = getMinMax(items, “yNumber”);    min = Math.min(min, minMax[0]);    max = Math.max(max, minMax[1]);    count++;    }  }  if (count > 0) {      var vaxis:LinearAxis = (linechart.verticalAxis as LinearAxis);              vaxis.maximum = max;              vaxis.minimum = min;              callLater(setInterval);  }  }    private function setInterval():void {          var vaxis:LinearAxis = (linechart.verticalAxis as LinearAxis);                      vaxis.maximum = Math.ceil(vaxis.maximum / vaxis.interval) * vaxis.interval;;          vaxis.minimum = Math.floor(vaxis.minimum / vaxis.interval) * vaxis.interval;;    }

Posted by Geetha on Oct 12, 2012

Enter a comment - moderated

Add Comment