FlashBuilder and PureMVC Part VII

Continuing from Part VI of this series, I did make some other minor changes from the original tutorial such as removal of the Dictionary. This seemed like a bit of overkill given that it’s easy to assign a String ID to an item and use the Object hash to quickly look up items by id. There are a few other minor changes and probably some issues you might like to address to suit your personal tastes. That’s what a tutorial is all about; illustration of key points and opportunity for hands-on experience to enhance the application.

I hope this has helped people looking to migrate from Flex 3 to Flash Builder – arghh, make that Flex 4, using PureMVC. You may download an FXP file of the project here, minus the data.xml file as that may be obtained from the original tutorial url. Substitute your own flicker API key.

Good luck with your Flex/PureMVC efforts!

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 );  
}

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.

Creating Actionscript Components – FlashBuilder and PureMVC Part IV

Continuing from part III of this series, this section begins the discussion of the core views.  The ApplicationFacade, ApplicationMediator, and low-level proxies are all essentially the same as the original tutorial.  That tutorial used the stage as the core viewComponent.  All views were constructed from Sprites.  The current Flex makeover uses custom Flex components built in Actionscript, all of which are based off UIComponent.

This is a good opportunity to review Flex component development in Actionscript.  The fundamental life cycle of a component includes initialization, updating, and destruction.  The initialization phase itself consists of four sub-phases.  These include creation (creating the component via the new operator), construction (internal property assignment), attachment (addition to display list), and initialization (full invalidation/validation sweep).

Component updating occurs via user interaction and method calls using an invalidation/validation model, sometimes called 3-phase validation.  The 3-phase model corresponds to individual phases in a marshaled time-slice inside a frame in ASVM-2.  Details are beyond the scope of this post, but I’ll point out some references if there is sufficient interest in a subsequent post.  The 3-phase validation consists of

Update properties ( invalidateProperties() -> commitProperties() )

Update size ( invalidateSize() -> measure() )

Update drawing ( invalidateDisplayList() -> updateDisplayList() )

This whole process of lazy validation essentially offloads tasks that could be done immediately to a later time where processing of that task might be more optimal.  The reason that deference may be more efficient is due to the way time slices (not frames as there are usually multiple time-slices inside a frame) are processed in the flash player.

The process works by using a store -> mark -> invalidate procedure.  Take a text field property, for example.  Instead of updating the text field immediately, the property is stored.  It is marked as changed (by a Boolean variable, for instance).  Properties are invalidated.  If updating the property causes the size of the component to change, then size is invalidated.  The invalidation process informs the Flex framework that one or more phases of the component update cycle are invalid.  The component architecture decides on the best time to perform those updates and calls component methods to perform the updates.

Destruction of the component includes detachment and preparation for garbage collection.  This process is not illustrated in the current tutorial as all components exist for the life of the application.  Usually, this is performed by adding a dispose() or destroy() method to the component.

This is best learned by practicing with very simple components and the current tutorial makeover is a perfect opportunity for such practice.  Let’s begin with the ProgressView.  This view is activated any time something is loaded during the application.  The original tutorial used this view for handling preloading inside the PureMVC application.  That process was replaced in the makeover with a custom preloader.  The ProgressView is now used for preloading image data and images themselves.  The display is very simple; just a text field with the text “Loading x%”.  Perfect for your very first Flex Actionscript component.

A custom Actionscript component extends UIComponent or another existing component.  Four UIComponent methods are overridden to implement the initialization and updating parts of the component life cycle.  These are createChildren, commitProperties, measure, and updateDisplayList.  The createChildren method is called as part of the initialization phase of the component, whenever it is added to the display list.  Since a component could be reparented, some care should be taken to only create children if they do not already exist.  This is the place to create component children that persist through the entire life of the component.  The commitProperties method is called during the first full validation pass during initialization and whenever invalidateProperties is called thereafter.  This method handles any property updates and creates/manages children that persist only temporarily in the component life cycle.  The measure method is called during the first validation pass and whenever invalidateSize is called thereafter.  It ensures the component is properly measured or sized.  Subsequent accesses to the component’s width and height should return the correct size of the component.  This method is not called, however, if width and height are explicitly set.  UpdateDisplayList does just that.  It ensures all children are properly positioned and ready to render 🙂

This is a very simplistic overview, just sufficient to make this tutorial self-contained.  A wealth of information is available in the Flex help and online if you wish to purse these topics in more detail. The question at hand is how to implement this component model for the ProgressView.  We could easily use a label, but it is helpful to show how low-level items such as text fields can be implemented in a component, so let’s stick with the text field used in the original tutorial.

In addition to the symbolic constants provided in the original tutorial, the following variables are used to implement the store -> mark -> invalidate procedure,

protected var_textField:TextField;

protected var _text:String;

protected var _textChanged:Boolean;

protected var _isShowing:Boolean;

The actual text is stored in the _text variable.  The _textChanged variable marks whether or not the text has been changed.  Instead of directly setting visibility, the _isShowing variable marks whether or not the component should the showing itself.

The text field is not created on construction.  Its creation is delayed until Flex calls the createChildren method, which happens whenever ProgressView is added to the display list.  As an aside, I’m always wondering if a font is truly embedded.  A TextFormat object is created as part of controlling text display.  Given that object, we can test for embedding with the statements

import mx.core.FlexGlobals;

FlexGlobals.topLevelApplication.systemManager.isFontFaceEmbedded(textFormat)

where textFormat is the reference to the TextFormat object.

The commitProperties method updates the text field’s text if it has changed.  The measure method sets the appropriate measure properties to the text field’s width and height.  The updateDisplayList method centers the text field inside the parent container and adjusts visibility based on the current value of the _isShowing variable.

The show(), hide(), and update() methods from the original tutorial remove the tweening animation, although that could be added as an exercise.  The appropriate properties are updated, then the invalidation methods are called to cause Flex to properly update the component via later calls to corresponding validation methods.  This completes the updating through the invalidate/validate model.

The source for ProgressView is provided below.

package view
{
  import flash.text.TextField;
  import flash.text.TextFieldAutoSize;
  import flash.text.AntiAliasType;
  import flash.text.TextFormat;
  import mx.core.FlexGlobals;
  import mx.core.UIComponent;
  public class ProgressView extends UIComponent
  {
    public static const NAME:String   = 'ProgressView';
    public static const SHOW:String = NAME+'Show';
    public static const HIDE:String = NAME + 'Hide'
    public static const UPDATE:String=NAME + 'Update';
    protected var _textField:TextField;
    protected var _text:String;
    protected var _textChanged:Boolean;
    protected var _isShowing:Boolean;
    public function ProgressView()
    {
      super();    // yeah, it's unnecessary, but old habits die hard :)
      _textChanged = false;
      _isShowing   = false;
      _text        = "";
    }
    override protected function createChildren():void
    {
      super.createChildren();
      if( !_textField )
      {
        var textFormat:TextFormat = new TextFormat();
        textFormat.color         = 0x000000;
        textFormat.font         = "arialEmbedded";
        textFormat.size            = 20;
        _textField                 = new TextField();
        _textField.embedFonts      = true;
        _textField.autoSize        = TextFieldAutoSize.LEFT;
        _textField.antiAliasType   = AntiAliasType.ADVANCED;
        _textField.defaultTextFormat=textFormat;
        _text                   = 'Please wait...';
        _textChanged   = true;
        addChild(_textField);
      }
    }
    override protected function commitProperties():void
    {
      super.commitProperties();
      if( _textChanged )
      {
        _textField.text = _text;
        _textChanged    = false;
      }
    }
    override protected function measure():void
    {
      super.measure();
      measuredWidth  = measuredMinWidth  = _textField.width;
      measuredHeight = measuredMinHeight = _textField.height;
    }
    override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
    {
      super.updateDisplayList(unscaledWidth, unscaledHeight);
      _textField.x = Math.round( 0.5*(parent.width  - _textField.width ) );
      _textField.y = Math.round( 0.5*(parent.height - _textField.height) );
      visible = _isShowing;  // could apply animation or whatever here
    }
    public function show():void
    {
      _text ='Please wait...';
      _textChanged = true;
      _isShowing   = true;
      invalidateProperties();
      invalidateSize();
      invalidateDisplayList();
    }
    public function hide():void
    {
      _isShowing = false;
      invalidateProperties();
      invalidateSize();
      invalidateDisplayList();
    }
    public function update(percent:Number):void
    {
      _text        = 'Loaded'+percent+'%';
      _textChanged = true;
      invalidateProperties();
      invalidateSize();
      invalidateDisplayList();
    }
  }
}

This seems like several levels of overkill just for a text field and in a way, that’s true. The purpose of this section of the tutorial is to introduce Flex component development in AS using very simple components. The benefit of using the 3-phase validation model is that it offloads processing of each phase to the most convenient or efficient time as determined by Flex . Every developer that is familiar with the invalidation/validation model can work on another developer’s component without having to learn a new layout or updating model.

One final note is that while addChild() is used to add a text field to a UIComponent, components are added to the display list of other spark components using addElement() in F4. This is illustrated in the ProgressViewMediator,

override public function onRegister():void

{

progressView = new ProgressView();

viewComponent.addElement(progressView);

sendNotification(URLView.GET_DATA);

}

Note that addElement() causes FlashBuilder to call the ProgressView createChildren() method and kicks off the initialization phase of the ProgressView life cycle.

The next section of the tutorial will discuss the URLView.

FlashBuilder and PureMVC Part III

Continuing from part II of this series, this post finishes discussion of the Flex layout, styling and skinning.  The original tutorial created buttons in Actionscript using the graphics API and text fields.  The makeover uses Flex UI components with CSS styles and skins.  To review, let’s look once again at the PureMVCLayout.mxml file and its ‘begin’ button.  Note the skinning applied to the button.

<?xml version="1.0" encoding="utf-8"?>
<custom:PureMVCLogic xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" width="700" height="500"
xmlns:custom="components.*" >
<fx:Declarations>
<!-- Place non-visual elements (e.g., services, value objects) here -->
</fx:Declarations>
<s:Button id="begin" x="{0.5*(width-begin.width)}" y="{0.5*(height-begin.height)}" width="100" height="20" label="Begin"
skinClass="skins.MinimalTriangleButton"/>
</custom:PureMVCLogic>

This gives me an opportunity to throw some props to local Flex developer and guru, Jonathan Campos.  Take some time to review his Spark skinning tutorial.  I created a ‘minimal’ triangle button skin based off of his example, with some small modifications to the reflect the production FlashBuilder release.  I say minimal in the sense that if you want to see the full extent of everything that might go in a skin file, then right-click on say a Button in design view.  Select ‘create skin’.  Select the package and skin name and make sure ‘create as copy of’ is checked with the source as spark.skins.spark.ButtonSkin.  Look at the generated skin file to see the big Kahuna 🙂

The skin is assigned via the skinClass attribute in MXML, but we could have just as easily used CSS, creating something like

s|Button.triangle {

skinClass: ClassReference(“skins.MinimalTriangleButton”);
}

then in MXML, use styleName=”triangle” .

The ‘minimal’ example for the triangular button used in the makeover is shown below (thanks Jonathan).

<?xml version="1.0" encoding="utf-8"?>
<s:SparkSkin xmlns:fx="http://ns.adobe.com/mxml/2009" xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:fb="http://ns.adobe.com/flashbuilder/2009" minWidth="50" minHeight="50" alpha.disabled="0.5">
<fx:Metadata>
<![CDATA[
/**
* @copy spark.skins.spark.ApplicationSkin#hostComponent
*/
[HostComponent("spark.components.Button")]
]]>
</fx:Metadata>
<s:states>
<s:State name="up" />
<s:State name="over" />
<s:State name="down" />
<s:State name="disabled" />
</s:states>
<s:Path data="H 100 L 50 75 L 0 0" >
<s:filters>
<s:DropShadowFilter blurX="20" blurY="20" alpha="0.5" distance="11" angle="90"/>
</s:filters>
<s:stroke>
<s:LinearGradientStroke weight="1">
<s:GradientEntry color="0x0000FF" ratio=".25"/>
<s:GradientEntry color="0xFFFFFF"/>
</s:LinearGradientStroke>
</s:stroke>
<s:fill>
<s:LinearGradient rotation="90">
<s:GradientEntry color="0xFFFFFF"/>
<s:GradientEntry color="0x0000FF"/>
</s:LinearGradient>
</s:fill>
</s:Path>
<s:Path data="H 100 L 50 75 L 0 0" includeIn="over">
<s:stroke>
<s:LinearGradientStroke weight="1">
<s:GradientEntry color="0x0000FF" ratio=".25"/>
<s:GradientEntry color="0xFFFFFF"/>
</s:LinearGradientStroke>
</s:stroke>
<s:fill>
<s:LinearGradient rotation="90">
<s:GradientEntry color="0xFFFFFF"/>
<s:GradientEntry color="0x0000FF" ratio=".25"/>
</s:LinearGradient>
</s:fill>
</s:Path>
<s:Label id="labelDisplay" horizontalCenter="0" verticalCenter="10"/>
</s:SparkSkin>

If you are copying and pasting from an online example, beware of extra characters at the end of tags.  These can produce some strange compiler errors.  If you get something about an item needs to be part of the Declarations tag, extra characters might be the culprit, as I found out.

This produces the following button centered inside the styled BorderContainer.  The BorderContainer styling eliminated the need for custom gradient drawing that was used in the original tutorial.

Spark Skin Applied to Begin Button

The button label is styled via CSS.  This process places the look and feel of UI elements up front in the application in MXML that is easier to change at a later point.

Although the begin button is positioned in MXML, there is no interactivity assigned to the button.  MXML is used purely for layout.  Logic is performed in the code-behind, PureMVCLogic.as.  We looked at part of that file in the previous post.  The full listing is provided below.

package components
{
  import spark.components.BorderContainer;
  import spark.components.Button;
  import mx.events.FlexEvent;
  import flash.events.MouseEvent;
  import facade.ApplicationFacade;
  public class PureMVCLogic extends BorderContainer
  {
    [Bindable]
    public var begin:Button;
    public function PureMVCLogic()
    {
      super();
      addEventListener(FlexEvent.CREATION_COMPLETE, init);
    }
    protected function init(_event:FlexEvent):void
    {
      begin.addEventListener(MouseEvent.CLICK, start);
    }
    protected function start(event:Event=null):void
    {
      begin.visible = false;
      ApplicationFacade.getInstance().startup(this);
    }
  }
}

The PureMVC application is started by invoking the ApplicationFacade startup on clicking the ‘begin’ button.  A reference to the BorderContainer is provided that serves as the core viewComponent inside PureMVC.  As the ApplicationFacade remains unchanged from the original tutorial, the next segment covers the ProgressView and its Mediator.

FlashBuilder and PureMVC Part II

The previous post in this series introduced a Flex 4 ‘makeover’ of an existing PureMVC tutorial.  Custom preloading of a Flex app. has already been discussed.  The main MXML file contains the styling for the entire application.  The prior tutorial embedded fonts in Actionscript and created many low-level text fields dynamically.  Custom buttons were constructed directly in Actionscript.  The makeover still shows how dynamic text fields are created and formatted, although Flex components are used for basic UI items.  My preference is to place application-level styling in one, high-level location.  This isolates future font changes, for example, to a single file.  Otherwise, we would have to search for every view class that created a text field and change font specifications one at a time.

Styling in Flex 4 is a bit different.  Note the use of namespaces.

<fx:Style>

@namespace s “library://ns.adobe.com/flex/spark”;

@namespace mx “library://ns.adobe.com/flex/mx”;

@namespace views “view.*”;

@font-face {

src: url(“/Library/Fonts/Arial.ttf”);

fontFamily: arialEmbedded;

fontWeight: normal;

embedAsCFF: false;

}

The remainder of the <fx:Style> tag is provided in the prior post.  I had a bit of trouble with the local() syntax, so I provided the direct url for the font   When embedding fonts in a Flex 4 application, take care to note whether the component using the font is Halo or Spark or a dynamically created text field. DefineType4 is now in the Text Layout Framework, which is documented here.  The implication is whether or not embedAsCFF is true (Spark) or false (otherwise).  Keith Peters has also blogged on this topic.

I left out the unicodeRange attribute to conserve space.  When I provide the complete set of source files at the end of the tutorial series, add it as an exercise.  This is used to specify the exact range of characters to embed.

The main application MXML ends with

<comp:PureMVCLayout id=”App” />

This is the layout of the PureMVC application.  The logic is implemented via code-behind.  The complete source of components.PureMVCLayout is

<?xml version=”1.0″ encoding=”utf-8″?>

<custom:PureMVCLogic xmlns:fx=”http://ns.adobe.com/mxml/2009&#8243;

xmlns:s=”library://ns.adobe.com/flex/spark”

xmlns:mx=”library://ns.adobe.com/flex/mx” width=”700″ height=”500″

xmlns:custom=”components.*” >

<s:Button id=”begin” x=”{0.5*(width-begin.width)}” y=”{0.5*(height-begin.height)}” width=”100″ height=”20″ label=”Begin”

skinClass=”skins.MinimalTriangleButton”/>

</custom:PureMVCLogic>

This provides only the visual layout of the application.  One thing to decide in the app. is what is controlled by PureMVC and what is outside PureMVC.  Not everything has to be in the PureMVC application.  Inclusion decisions are largely a matter of personal preference.  This tutorial illustrates using a Flex button to begin the application.  The button is in the UI, but not controlled by PureMVC.  So, it is contained in the PureMVCLayout.MXML file and skinned via MXML.

The entire application logic is contained in the components.PureMVCLogic.as file.  This is the code-behind for PureMVCLayout.  A Spark BorderContainer is used as the primary viewComponent for the PureMVC application as shown in the code segment below.

package components

{

import spark.components.BorderContainer;

import mx.events.FlexEvent;

import flash.events.MouseEvent;

import facade.ApplicationFacade;

public class PureMVCLogic extends BorderContainer

{

[Bindable]

public var begin:Button;

Notice that layout items in the UI (in PureMVCLayout.MXML) are public variables in the code-behind.  The remainder of the application logic will be discussed tomorrow.