FlashBuilder and PureMVC Part VI

Continuing from Part V of this series, the last view in the application is the ImagesView. In the original tutorial, images were displayed in a small grid with a back button. Clicking the back button returned the viewer to the URLView. There are too many images to display in a small space, so the original tutorial set a limit on the number of images displayed. They were displayed inside a 120×120 bounding area in a 2D, non-scrollable grid. The Flex makeover preserves this layout. While the column and maximum-image count are adjustable, no consideration is provided for scrolling if the real estate consumed by image display is too large. This keeps the makeover in line with the original tutorial, although handling an arbitrary number of images is a useful exercise for motivated readers 🙂

In the current implementation, the ImagesView consists of three items, a title area, a content area (where images are displayed) and a back button.  The content area dimensions are determined by the column and maximum-image count and the bounding-box size of 120×120.  Both width and height of this area are known in advance.  On component creation, the height of the title area and back button are also known.  This means the component may be sized on creation and that size remains fixed during the update phase.

Selection of the base component to build the ImagesView is arbitrary.  For illustration purposes, a spark SkinnableContainer was selected.  This container already has a background rectangle.  While the color may be styled, the general appearance is obtained through skinning, which has already been discussed to some extent.  The specific skinning is left as an exercise.

A natural layout for the view is vertical.  This is accomplished by creating a new VerticalLayout (spark.layouts.VerticalLayout).  Parameters are assigned in the ImagesView constructor and the VerticalLayout is assigned to the layout setter of the SkinnableContainer.

ImagesView contains three children, a title area (spark Label), content pane (UIComponent), and a back button (spark Button).  The init() method from the original tutorial was preserved.  This method loads a new photo list and calls invalidateProperties().  During validation, commitProperties()  first disposes of any prior images then causes a new image set to be loaded.  The updateDisplayList() method adds all newly loaded images to the display list of the contentPane if required and sets visibility of the ImagesView based on prior marking.  If a new set of images is loaded, a DISPLAY event is dispatched.  This event informs the ImagesView Mediator that a new image set has been loaded and displayed.

In the prior post, processing of clicks on any URL Button were delayed.  As mentioned in that post, that was one means for illustrating usage of the Flex component framework.  In the ImagesView, the GO_BACK_CLICKED event is dispatched after the display list is invalidated.  This allows the ImagesView and its mediator to operate on that action independently.  You may contrast the two approaches and are invited to experiment.

The complete ImagesView source is provided below.

package view
{
  import flash.display.Loader;
  import flash.display.Sprite;
  
  import flash.events.Event;  
  import flash.events.MouseEvent;  
  
  import flash.net.URLRequest;  
 
  import mx.core.UIComponent;
  
  import spark.components.SkinnableContainer;
  import spark.components.Label;
  import spark.components.Button;
  
  import spark.layouts.VerticalLayout;
  
  import mx.events.FlexEvent;
  
  [Event(name=ImagesView.DISPLAY        , type="flash.events.DataEvent")]
  [Event(name=ImagesView.GO_BACK_CLICKED, type="flash.events.Event")    ]
  [Event(name=ImagesView.DISPLAY        ,type="flash.events.Event")     ]
  public class ImagesView extends SkinnableContainer  
  {  
    public static const NAME:String            = 'ImagesView';  	  
    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 GO_BACK_CLICKED:String = NAME + "Back";
    public static const DISPLAY:String         = NAME + "Display";
    	  
    // photo layout properties
    private static const MAX_IMAGES:Number     = 15;  
    private static const COLUMNS:Number        = 5;
    private static const CONTENT_HEIGHT:Number = Math.round(MAX_IMAGES/COLUMNS)*120;
    
    // photo list
    private var _photoList:XMLList;
    private var _images:Object;
    private var _count:int;
    private var _imageSet:int;
    
    // photo layout components
    private var _contentPane:UIComponent;
    private var _title:Label;
    private var _backButton:Button;
    private var _layout:VerticalLayout;
    
    // other properties
    private var _isShowing:Boolean;
    
    public function ImagesView():void
    {
      super();
      
      _count     = -1;
      _isShowing = false;
      _images    = {};
      
      width   = COLUMNS*120;    // measure will not be called
      height  = CONTENT_HEIGHT;
      visible = false;
      
      _layout = new VerticalLayout();
      _layout.gap             = 3;
      _layout.paddingLeft     = 2;
      _layout.paddingRight    = 2;
      _layout.paddingTop      = 2;
      _layout.paddingBottom   = 2;
      _layout.horizontalAlign = "center";
      
      layout = _layout;

      addEventListener(FlexEvent.CREATION_COMPLETE, onCreationComplete);
    }
    
    public function init(images:XML, imgSet:int):void  
    {  
      _photoList = images..photo;
      _imageSet  = imgSet;
      _count     = 0;
      
      invalidateProperties();
    }  
    
    override protected function createChildren():void
    {
      super.createChildren();
      
      if( !_title )
      {
        _title = new Label();
        _title.styleName = "titleStyle";
        
        addElement(_title);
      }
      
      if( !_contentPane )
      {
        _contentPane        = new UIComponent();
        _contentPane.width  = COLUMNS*120;        // content pane is a known, fixed size
        _contentPane.height = CONTENT_HEIGHT;
        
        addElement(_contentPane);  
      }
      
      if( !_backButton )
      {
        _backButton = new Button();
        
        _backButton.addEventListener(MouseEvent.CLICK, onBack); 
        
        _backButton.label = "BACK";
        
        addElement(_backButton);
      }
    }
    
    protected function onCreationComplete(e:FlexEvent):void
    { 
      height = CONTENT_HEIGHT + _title.height + _backButton.height + _layout.paddingTop + _layout.paddingBottom + 2*_layout.gap;
    }
    
    protected function loadImages():void
    {
      // only if initialized ...
      if( !_photoList || _photoList.length() == 0 && _count == 0 )
        return;
      
      var xRowCount:Number = 0;  
      var yRowCount:Number = 0;  
      var element:XML      = null;  
      var url:URLRequest   = null;
      var image:Loader     = null;  
        
      _count = 0;
      for( var i:Number=0; i 0 )
        return;
      else
      {
        dispose();
        loadImages();
      }
    }
    
    override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
    { 
      super.updateDisplayList(unscaledWidth, unscaledHeight);
      
      visible = _count > 0 && _isShowing;
      
      if( _contentPane )
      {
        if( _contentPane.numChildren == 0 && _count == MAX_IMAGES )
        {
          // preserve order
          for( var i:int=0; i<MAX_IMAGES; ++i )
          {
            var image:Loader = _images[i.toString()];
            _contentPane.addChild(image);
          }
        
          if( _isShowing )
            dispatchEvent( new Event(DISPLAY) );
        }
      }
    }
      
    private function onBack(e:MouseEvent=null):void  
    {  
      _isShowing = false;
      invalidateDisplayList();
      
      dispatchEvent( new Event(GO_BACK_CLICKED) );    
    }  

    // dispose of image content and Loader instances only
    public function dispose():void
    {
      if( _contentPane )
      {
        for( var i:int=0; i<MAX_IMAGES; ++i )
        {
          var image:Loader = _images[i.toString()];
          if( image )
          {
            image.unloadAndStop(true);
          
            if( _contentPane.contains(image) )
              _contentPane.removeChild(image);
          
            image = null;
          }
        }
      }
      
      _images = {};
      _count  = 0;
    }
  }  
}

The ImagesViewMediator is essentially the same, except that the Images View is dynamically positioned on handling the ImagesView.DISPLAY event,

override public function onRegister():void  
{  
  imagesView = new ImagesView();
  imagesView.addEventListener( ImagesView.GO_BACK_CLICKED, handleImagesViewGoBackClicked );  
  imagesView.addEventListener( ImagesView.DISPLAY, onDisplay);
      
  viewComponent.addElement( imagesView );  
}  

and the handler

protected function onDisplay(e:Event):void
{
  // center relative to view component
  imagesView.x = Math.round( 0.5*(viewComponent.width  - imagesView.width ) );
  imagesView.y = Math.round( 0.5*(viewComponent.height - imagesView.height) );
      
  sendNotification( ProgressView.HIDE );  
}
Advertisement

One thought on “FlashBuilder and PureMVC Part VI

Comments are closed.