Following on from yesterday’s post about Cairngorm’s ModelLocator, I’ve devised some improvements to the code. This doesn’t alter the technique, but it does remove most of the repetition in the code.

The Model

The model class can be generalised using this technique by Jacob Write to make it dynamic and bindable, so the actual data names and types can be dynamically added by the ModelLocators. The code looks like this.

package model
{
  import flash.events.Event;
  import flash.events.EventDispatcher;
  import flash.events.IEventDispatcher;
  import flash.utils.Proxy;
  import flash.utils.flash_proxy;
  import mx.events.PropertyChangeEvent;
  import mx.events.PropertyChangeEventKind;
  use namespace flash_proxy;
  
  [Bindable("propertyChange")]
  dynamic internal class Model extends Proxy
                                     implements IEventDispatcher
  {
    // do the singleton bit
    private static var _instance:Model;
    
    public static function getInstance() : Model
    {
      if (_instance == null)
      {
        _instance = new Model();
      }
      return _instance;
    }
    
    public function Model()
    {
      if ( _instance != null )
      {
        throw new Error( "The Model class is a singleton." );
      }
      _data = {};
      _eventDispatcher = new EventDispatcher(this);
    }
    
    // do the dynamic bunding bit
    protected var _data:Object;
    protected var _eventDispatcher:EventDispatcher;
    
    flash_proxy override function getProperty(name:*):*
    {
      return _data[name] || null;
    }
     
    flash_proxy override function setProperty(name:*, value:*):void
    {
      var oldValue:* = _data[name];
      _data[name] = value;
      var kind:String = PropertyChangeEventKind.UPDATE;
      dispatchEvent(new PropertyChangeEvent(
                       PropertyChangeEvent.PROPERTY_CHANGE,
                       false, false, kind, name, oldValue, 
                       value, this));
    }
    
    public function hasEventListener(type:String):Boolean
    {
      return _eventDispatcher.hasEventListener(type);
    }
     
    public function willTrigger(type:String):Boolean
    {
      return _eventDispatcher.willTrigger(type);
    }
     
    public function addEventListener(type:String, listener:Function, 
                       useCapture:Boolean=false, 
                       priority:int=0.0, 
                       useWeakReference:Boolean=false):void
    {
      _eventDispatcher.addEventListener(type, listener, 
                       useCapture, priority, useWeakReference);
    }
     
    public function removeEventListener(type:String, 
                       listener:Function,
                       useCapture:Boolean=false):void
    {
      _eventDispatcher.removeEventListener(type,listener,useCapture);
    }
     
    public function dispatchEvent(event:Event):Boolean
    {
      return _eventDispatcher.dispatchEvent(event);
    }
  }
}

I’ve made the class internal because it should only be accessed by the ModelLocator. the assumption is that the ModelLocator is in the same package – if it isn’t then the class should be public.

The ModelLocator

The ModelLocator then uses reflection to inspect its own properties and bind them to dynamically created properties in the Model singleton.

package model
{
  import mx.binding.utils.BindingUtils;
  import flash.utils.describeType;
  
  [Bindable]
  public class ModelLocator
  {
    private var _model:Model;
    
    // model data
    public var username:String


    public function ModelLocator()
    {
      _model = Model.getInstance();

      var description:XML = describeType( this );
      for each( var property:String in description.accessor.@name )
      {
        BindingUtils.bindProperty( this,property,_model,property );
        BindingUtils.bindProperty( _model,property,this,property );
      }
    }
  }
}

The reflection looks for accessors because making the class bindable causes the compiler to replace all the public properties with getter/setters.

The Result

Now, model data need only be declared once, as public properties in the ModelLocator class.

As before, we can make as many instances of the ModelLocator as we like. Setting a property on one will set the property on all. And binding to a property on any one will work as expected. Hence

var model:ModelLocator = new ModelLocator();
model.username = "fred";

and

<model:ModelLocator id="model" xmlns:model="model.*"/>
<mx:TextInput text="{model.username}"/>