FlashBuilder and PureMVC Part V

Continuing from Part IV of this series, this post examines the URLView and its Mediator.  The URLViewMediator in the FlashBuilder makeover is close to the implementation in the original tutorial, with a couple exceptions.  The URLView itself has been modified to work within the Flex component architecture.

The URLView children that persist for the life of the view are the URL buttons, implemented as spark Buttons.  These are created in the URLView createChildren() method.  The URLView init() method was retained from the original tutorial.  Since its contents must be set in order for createChildren to work, it is necessary for the Mediator to call the init method before adding URLView to the display list.  That was done already in the prior tutorial.

I like to show different ways of thinking about process in a tutorial.  Let’s think about the button click.  Inside the MouseEvent handler, an event is dispatched that is handled by URLViewMediator.  The Mediator sends a notification to initiate some action based on the button click.  Suppose that processing needs to be delayed until after the buttons animate out of view?  Or, we all know how it goes.  The button click is processed every time in the initial spec.  By the time the application is released and we’ve done the dance of a thousand CR’s, the button click is only processed on the second tuesday of each month, provided it’s not in the morning on the client’s system and it’s not raining outside where the server is located, AND the client is running MAC OSX 🙂

We could hack in all the required logic into a combination of MouseEvent and animation handlers.  Or, we could use the built-in 3-phase component validation process discussed in the previous section.  Use mark -> store -> invalidate and allow the validation methods to handle the eventual event dispatch.  This isolates the handling logic to commitProperties().

For purposes of illustrating use of the component architecture, the MouseEvent handler in URLView simply marks that the button was clicked and marks the URL index.  The intent of clicking on a URL button is to identify the index that was clicked, hide the URL view, then notify the Mediator that the button was clicked.  The button handler calls the hide() method, which in turn calls invalidateProperties() and invalidateDisplayList().  The component size is not altered, so invalidateSize() is not called.  Whether or not the event is to be dispatched is decided upon in commitProperties().  A flag is set that is tested in updateDisplayList() after the view’s visibility is set.  This ensures that the button click event will never be dispatched to the Mediator until after updateDisplayList() is called and its work is finished, which may include animation at some later time.  If the choreography is adjusted so that the Mediator is not to send any notifications until after animation is finished, such changes are easily handled by having updateDisplayList() initiate the animation and not dispatch the event until the animation is complete.

Again, this seems like multiple levels of overkill for something simple, but the point is to illustrate the component architecture on relatively simple concepts.  As component and application complexity increases, understanding this process can help you write code that isolates logic likely to be changed to a single area using a common framework.  This also allows other developers familiar with the invalidation/validation process to more quickly understand and make modifications to views.

The modified URLView is provided below

package view
{
  import flash.events.DataEvent;
  import flash.events.MouseEvent;  

  import mx.core.UIComponent;
  import spark.components.Button;

  [Event(name=DataEvent.CLICKED, type="flash.events.DataEvent")]
  public class URLView extends UIComponent
  {
    public static const NAME:String       = 'URLView';
    public static const GET_DATA:String   = NAME + 'GetData';
    public static const DATA_READY:String = NAME + 'DataReady';
    public static const SHOW:String       = NAME + 'Show';
    public static const HIDE:String       = NAME + 'Hide';
    public static const CLICKED:String    = NAME + 'Clicked';  

    // default button dimensions
    private static const BUTTON_WIDTH:Number  = 120;
    private static const BUTTON_HEIGHT:Number = 20;

    private var _urls:Array;
    private var _baselineY:Number;
    private var _isShowing:Boolean;
    private var _clicked:Boolean;
    private var _dispatch:Boolean;
    private var _index:String;

    public function URLView():void
    {
      super();

      _urls      = [];
      _baselineY = 0;
      _index     = "";
      _isShowing = false;
      _clicked   = false;
      _dispatch  = false;
    }

    public function init(urls:Array):void
    {
      // allow this view's Mediator to initialize any view properties here; only one for this example
      _urls = urls.slice(0);
    } 

    override protected function createChildren():void
    {
      super.createChildren();

      if( numChildren == 0 )
      {
        var i:Number = 0;

        for each( var url:String in _urls )
        {
          var urlButton:Button = new Button();
          urlButton.id         = i.toString();
          urlButton.y          = _baselineY = i*40;

          urlButton.label  = 'Select URL ' + ( ++i );
          urlButton.width  = BUTTON_WIDTH;
          urlButton.height = BUTTON_HEIGHT;

          urlButton.addEventListener( MouseEvent.CLICK, handleContainerClick );
          addChild( urlButton );
        }
      } 

      _isShowing = true;
    }

    override protected function commitProperties():void
    {
      super.commitProperties();

      // rules for dispatching click event (isolates all future CR's to this one area)
      _dispatch = _clicked && !_isShowing && _index != "";

      if( _dispatch )
        _clicked = false;
    }

    override protected function measure():void
    {
      measuredWidth  = measuredMinWidth  = BUTTON_WIDTH;
      measuredHeight = measuredMinHeight = _baselineY + BUTTON_HEIGHT;
    }

    override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
    {
      super.updateDisplayList(unscaledWidth, unscaledHeight);

      visible = _isShowing;  // could do animation or other display stuff here

      // component display is complete, dispatch event if necessary - this could be delayed until after an animation was completed
      if( _dispatch )
        dispatchEvent( new DataEvent(CLICKED, true, false, _index) );
    }

    public function show():void
    {
      _isShowing = true;
      invalidateProperties();
      invalidateDisplayList();
    }  

    public function hide():void
    {
      _isShowing = false;
      invalidateProperties();
      invalidateDisplayList();
    }

    private function handleContainerClick(e:MouseEvent):void
    {
      _clicked = true;
      _index   = (e.target as Button).id;
      hide();
    }
  }
}

The URLViewMediator was modified not to position the URLView until after its creation is complete. During the initialization phase of a component, a preinitialize event is dispatched on creation of the component. The createChildren() method is called when the component is attached. Invalidation methods are called, then an initialize event is dispatched. There is a full validation pass, followed by a creationComplete event (that was a very rough overview). When the creationComplete event is dispatched, the component is fully measured. So, the Mediator adds a handler

override public function onRegister():void
  {
    urlView = new URLView();
    urlView.addEventListener( URLView.CLICKED, handleURLViewClicked );
    urlView.addEventListener(FlexEvent.CREATION_COMPLETE, onCreationComplete);

    urlView.hide();
    urlView.init( proxy.vo.urlsArray );

    viewComponent.addElement( urlView );
  }

The onCreationComplete() method handles the positioning, which is only done once as the button sizes and number of buttons are fixed.

private function onCreationComplete(e:FlexEvent):void
  {
    sendNotification( ProgressView.HIDE );

    urlView.x = Math.round( 0.5*(viewComponent.width  - urlView.width ) );
    urlView.y = Math.round( 0.5*(viewComponent.height - urlView.height) );

    urlView.show();
  }

Again, I’ll point out that nothing *must* be done this way; it’s an opportunity to illustrate how the Flex component architecture works and how the component life cycle works inside a PureMVC application. I hope you find some ways to use these techniques in actual projects.

One thought on “FlashBuilder and PureMVC Part V

Comments are closed.