Monday, September 27, 2010

Quick Dive into Parsley (Nested Contexts & Object lifecycle methods - Part 6)

This is part of a series of articles that should get you up and started using Spicesfactory's Parsley.
This is part 6.
In Part 1, I explained the reasons as to why we chose to use Parsley in our projects.
In Part 2, I explained what Parsley is and some basic concepts and how they relate to Parsley.
In Part 3, I showed how to set up Parsley and showed a basic injection example.
In Part 4, I showed how to send messages using Parsley that do not extend the Event class.
In Part 5, I showed how to send messages using Flex Events.

This article builds upon what was covered in Part's 2, 3, 4 and 5 so if you haven't seen them, I encourage you to start with at the beginning of this series.

This should be a more interesting example than the previous three.  In this example we cover two important features of Parsley:  Nested Contexts and Parsley Object lifecycle methods.

Nested Contexts

   In Parsley you are not limited to one global context.  You can nest contexts, place one inside of the other, to create context scopes.  This is similar to how you can nest scopes using curly brackets { } in your code.


   When you create a context in Parsley only that view and its view descendants can have access to that context.  You can have as many contexts as you would like, and the same rule applies.  Now, it’s important to note that an object that is part of a nested child context can still have access to objects of ancestor objects, just like it works with regular code scopes.  The concept is very similar to what you are actually used to with scopes.

  If you look at diagram 1 on the right side, Objects that belong to the “Main Context” can’t see objects that belong to any of the sub contexts.  Objects in “Sub Context A” can see objects of itself and objects of the “Main Context”, but can’t see objects of “Sub Context B” nor “Sub Context A-1” or “Sub Context A-2”.  The same applies throughout.

   Another great feature is that nested contexts not only apply to injection, it also can be applied to messaging.  By default, all messaging in Parsley is global, but you can specify on the receiving end if you wish to only receive messages from its local context, sending is always global.  You specify this with the [MessageHandler (scope="local")] tag.

   In regards to injection, you don’t have to specify if it’s global or local, Parsley figures it out by itself, but it is important to note that it does this by the class type.  So you should not use the same class name in multiple nested contexts.  Technically speaking you can, but then you must use ids to help Parsley figure out which one you want, the local or a different one.  Ex. [Inject(id="defaultMyModel")], more on this in the docs.


Parsley Object Lifecycle methods

   In Flex, Flash, and just about every Object Oriented development environment, objects have a lifecycle.  That is; they are created, used, and destroyed.  Parsley objects are no different, regardless of whether Parsley manages them or not.  In Parsley the lifecycle is somewhat tied to the view display list, when the object that holds a context is removed from the display list, the context of it, and its children is destroyed and all the objects that where managed within those contexts are also destroyed.  Also, when a view is added to the context with the <Configure /> tag, it is added to the context when the view is added to the stage, and it is removed from the context when it is removed from the stage.  That said, Flex has a quirky way of re-parenting components when scrollbars are added, which means that they are removed and added from stage while this happens.  Parsley is aware of this, and objects are not destroyed during this process.

   Now Parsley can inject into properties and into methods; that means that those properties will be null during the object creation.  So, how do you know when those properties have been injected?  Well, Parsley has a great feature to help you with that.  You can tag a function within the managed object with [Init] and that function will be called when all injections are done and the object has been successfully added to the context and it is ready to participate in all of Parsley features.  This is somewhat like CreationComplete, but in regards to Parsley.  If anything depends on the objects being injected, this is where you can trigger the execution of those dependencies.

   Parsley also offers a [Destroy] tag, which you can place in a function of a managed object.  That function will get called right before the object is destroyed.  This is a handy feature if you wish to remove event listeners, recall that AS3 does not include destructors, and you avoid the quirkiness of the removedFromStage event firing when your object gets re-parented.

  Now remember that this is tied to the component that holds the context and it last as long as that object is not removed from the display list.  This can be overridden with the <ViewSettings autoremoveComponents="false" /> tag, but that is beyond the scope of this example, and something you probably won’t need much.

   There are more features regarding object lifecycle, such as factories, Asynchronous Object Initialization, and controlling the Object Initialization Order which you can read in more detail in the docs.

Enough talk, let’s get to the example.

The Example

In this particular case, I’m going to start of showing the example running and then we will dig a bit into the code. View Source is enabled.



Here is the structure of project.
This project is obviously larger than the previous examples.  It has two levels of context, the global and the local, but as you probably saw in the sample you can create as many local contexts as you like.
 The LoggerMessage class is a basically the same class we used on the previous messaging example.
The GlobalModels is the model used globally and the LocalModel is the model used in the subcontext.  As usual, these are quite simple for demonstration purposes.
The GlobalContext defines the global context and it contains the GlobalModel.
The LocalContext is used as a subcontext and it contains the LocalModel.
There are a total of 3 views.  The GlobalView which holds the GlobalContext and is a child of ParsleyNestedContextExample, the main app.  The LocalView which holds the LocalContext and it will be a child of GlobalView.  The SharedComponents which holds no context and is a child of LocalView.

Here is a diagram overview:
Diagram 2 - Parsley View Context Relation

So basically any object that is a view child of “Global View”, can have access to the Global Context, that includes “Local View 0” and “Local View 1”.  View Childs within “Local View 0” can have access to the local context of “Local View 0” and the global context, but they don’t have access to the local context of “Local View 1”.

And here is the code.  If you are looking for the new stuff, jump down to the LocalModel.as.

Let's start with LoggerMessage.as:

package messages
{
    /***************************************************************
     * This is a simple class that will hold a message so it can be 
     * delivered thought our application.  It only has one property
     * that is the message itself.  Note that it does not 
     * extend Flexs' Event.  
     * 
     * For more Parsley examples visit:  artinflex.blogspot.com
     * */ 
    
    public class LoggerMessage
    {
        /********************
         * This is the private var that holds our message
         * */
        private var _message:String;
        
        /********************
         * Constructor that takes the parameter as message
         * @param message  is a String that contains the text that will be set
         * */         
        public function LoggerMessage( message:String)
        {
            _message = message;
        }
        
        /*********************************
         * read only property that delivers the message.
         * */
        public function get message():String
         {
             return _message
         }
    }
}

Well, there is nothing new and special here, it just holds a string as a message.  Note that I'm not planning on using Flex Events to send messages as it doesn't extend the Event class.

Next let's look at the Global and Local Context config files, for more info on these please refer to example in part 3.

Here is GlobalContext.mxml:

<?xml version="1.0" encoding="utf-8"?>
    <!--
    This is the file that declares which objects you wish to manage through Parsley.
    Any object declared within the Object Tag will be allowed to participate in any of Parsley's 
    features.  This is a Simple MXML implementation, but this can also be declared in and XML file.
    
    In this particular case this is the Global context which is meant to be shared 
    throughout the whole app.
    
    For more Parsley Examples visit:  artinflex.blogspot.com
    -->
    
<mx:Object
    xmlns:mx="http://www.adobe.com/2006/mxml"  
    xmlns:model="model.*">
<!--    
Withing the object tags you declare de objects.  In this case I'm just including the 
GlobalModel Class
-->    
    <model:GlobalModel />    
</mx:Object>

And here is the LocalContext.mxml:

<?xml version="1.0" encoding="utf-8"?>
    <!--
    This is the file that declares which objects you wish to manage through Parsley.
    Any object declared within the Object Tag will be allowed to participate in any of Parsley's 
    features.  This is a Simple MXML implementation, but this can also be declared in and XML file.
    
    In this particular case this is the Local context which is NOT meant to be shared throughout
    the whole app, it is intended to be used in a more private context.
    
    For more Parsley Examples visit:  artinflex.blogspot.com
    -->
    
<mx:Object
    xmlns:mx="http://www.adobe.com/2006/mxml"  
    xmlns:model="model.*">
<!--    
Withing the object tags you declare de objects.  In this case I'm just including the 
LocalModel Class
-->    
    <model:LocalModel />    
</mx:Object>

Both don't have anything new, each hold Model class, GlobalModel and LocalModel respectively.  The new stuff is in the LocalModel.  So what do these models look like?

Here is GlobalModel.as:

 package model
{
    import messages.LoggerMessage;
   
    /*********************************************************************
     * GlobalModel is a simple class meant to demonstrate the Shared Model
     * concept.  This class is intended to be shared through the whole app.
     *      
     * For more examples visit: artinflex.blogspot.com
     **/ 

    public class GlobalModel
    {
        [Bindable]
        public var sharedString:String;   // The string to be shared       
    }
}

And here is the LocalModel.as:

package model
{
    import messages.LoggerMessage;
    
    /*********************************************************************
     * LocalModel is intended to be used in a more private context, and 
     * not shared among the whole app.
     * 
     *  Also LocalModel demonstrates another Parsley Feature are the
     *  Object lifecycle Methods:  [Init]  [Destroy]
     *     
     * For more Parsley examples visit: artinflex.blogspot.com
     **/ 

    public class LocalModel 
    {
        [Bindable]
        public var sharedString:String;   // The string to be shared        
        
        
        /******************************
         *  The MessageDispatcher is a special Parsley Flag
         *  that instructs Parsley to Inject Parsley’s message
         *  dispatcher into the function variable declared below.
         *  This allows this class to send messages anywhere without
         *  having to use Flex/Flash events.
         * */
        [MessageDispatcher]
        public var dispatcher:Function;
        
        
        /****************************
         * viewNumber is used to identify the view that this model
         * belongs to.  This is the private value.
         * */        
        private var _viewNumber:int = 0;

        /****************************
         * viewNumber is used to identify the view that this model
         * belongs to.  It dispatches a message when the view number is set.
         * */
        public function set viewNumber(value:int):void
        {
            _viewNumber = value;
            var message:String = "LocalModel assigned view number: " + value.toString();
            dispatcher(new LoggerMessage(message));            
        }        
        public function get viewNumber():int
        {
            return _viewNumber;
        }
        
        /*******************************************************
         * In Parsley, a function taged with Init will be called
         * when this object has been created and successfully added
         * to the context.  In this case it sends a message.
         * ****************************************************/
        [Init]
        public function handleInit():void
        {
            var message:String = "The LocalModel has been created and is ready.";
            dispatcher(new LoggerMessage(message));            
        }        

        /*******************************************************
         * In Parsley, a function taged with Destroy will be called
         * when this object is about to be removed from the context
         * and disposed.  This object will get Garbage Collected.
         * ****************************************************/
        [Destroy]
        public function handleDispose():void
        {
            var message:String = "LocalModel about to be destroyed for View - " + viewNumber.toString();
            dispatcher(new LoggerMessage(message));            
        }
    }
}

Now here we can see a couple of new things.  First, well you can notice that we can Inject into any model object that is managed by Parsley, Injecting is not limited to Views.  For example we are injecting the MessageDispatcher here.  Also you will note the [Init] and they [Destroy] tags in front of two separate functions.  As indicated in the comments, Init is called when the object has been set and is managed by Parsley, so you can use any of Parsley's features.  You can use Init and Destroy in view and non view components that are managed, regardless if they were added with <Configure /> or <ContextBuilder />.

Ok, now let's look at the views.

Start of with GlobalView.mxml:

<?xml version="1.0" encoding="utf-8"?>
<!-- *************************************************************
   This is the view that is "Global" and will hold the Global Context.
   This view will also hold all the local child windows that will
   have their own context.
   For more Parsley examples visit:  artinflex.blogspot.com
   ************************************************************* -->
   <mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml"               
               xmlns:parsley="http://www.spicefactory.org/parsley"
             width="390" height="570" verticalGap="5" >
             
    <mx:Label text="Global Message Log" fontWeight="bold"/>    
    <mx:TextArea editable="false" id="messageLogDisplay" width="100%" height="80" />
    <mx:HBox width="100%" horizontalAlign="center" horizontalGap="40">
    
    <!--  These buttons are to create and remove LocalView Children. -->    
        <mx:Button label="Create Local View" click="handleCreateLocalView(event)"/>
        <mx:Button label="Remove Local View" click="handleRemoveLocalView(event)"/>
    </mx:HBox>
      <!--This is the container that will hold the local views created.-->
    <mx:VBox height="430"  width="390" id="localViewsContainer" verticalScrollPolicy="auto">
    </mx:VBox>
    
    <!--  The date formatter is to show times in the logger -->
    <mx:DateFormatter formatString="LL:NN:SS" id="dateFormatter" />
    
    <!-- ContextBuilder is how you tell parsley to manage objects that are not Views.
    In this case I’m loading the GlobalContext configuration file which is intended
    to be shared in the whole app.  Notice that you can also specify a complete 
    function which gets called once it finishes creating the context and its 
    children (Managed Objects).     -->    
    <parsley:ContextBuilder config="{GlobalContext}"  complete="handleGlobalContextCreated()" />
    
    <!--  Configure is how you tell Parsley that you want this view to become 
    part of the Context and participate in all of Parsley’s features.  
    <Configure /> looks for the closest Context in the view hierarchy and attaches 
    the view to that particular context.  In this case, this view becomes part of 
    the context within this particular file. -->
    <parsley:Configure  />
    
    <mx:Script>
        <![CDATA[
            import messages.LoggerMessage;
            import mx.formatters.DateFormatter;            
                        
            /*************************************************
             * This function is being called when the ContextBuilder has finished
             * creating the Context and its children specified.  The context gets
             * created before the view is configured, so Injections will be performed
             * after this function gets called.  The objects are ready to be injected.
             * */
            public function handleGlobalContextCreated():void
            {
                addToLogDisplay("Global Context has been created.");                
            }
            
            /****************************************************
             * [Init] can be used in any Parsley managed object, including 
             * Views, and it’s not limited to objects declared in the Config 
             * file of the context Builder.  The functions tagged with [Init] 
             * will get called after Parsley finishes adding this view to the 
             * Context, all Injections have been performed and it is ready to 
             * send and receive messages.  This is a very handy tool.
             * */
            [Init]
            public function handleInit():void
            {
                addToLogDisplay("GlobalView has been added to context and is ready.");                
            }
            
            /***************************************************
             * This simple function takes a string and adds the current time to the 
             * front.  It then adds the new concatenated string to the Global 
             * Log Viewer and sets the log viewer to scroll to the bottom on the 
             * next frame.
             * @param message  is the text that will be added to the log
             */
            private function addToLogDisplay(message:String):void
            {
                var currentDate:Date = new Date();
                message = dateFormatter.format(currentDate) + " - " + message;
                
                if (messageLogDisplay.text.length > 0)                
                    messageLogDisplay.text = messageLogDisplay.text + "\n" + message;
                else
                    messageLogDisplay.text = message;
                    
                    // This is so that the logviewer scrolls to the bottom automatically.
                    // It is set on a callLater so it is called on the next frame
                    // so that the messageLogDisplay has done all its measuring after the new
                    // text is added and it really scrolls to the bottom.
                callLater(moveScrollToBottom);                
            }
            
            /******************************************
             * This is the global messages handler that receives any messages 
             * of the type LoggerMessage from anywhere and it appends them to 
             * the log viewer.
             * @param the message object that contains the message
             * */
            [MessageHandler]
            public function receiveGlobalMessage(logMessage:LoggerMessage):void
            {
                addToLogDisplay(logMessage.message);                
            }
            
            /***********************************************
             * This is the function that gets called when the create local 
             * view button is clicked.  It creates a new instance of LocalView 
             * and adds that instace to the container set to hold these views.
             * 
             * @param event is the mouse event from the Click of the button.
             * */
            private function handleCreateLocalView(event:MouseEvent):void
            {
                var newLocalView:LocalView = new LocalView();
                newLocalView.viewNumber = localViewsContainer.numChildren;
                localViewsContainer.addChild(newLocalView);                
            } 
            
            /*************************************************
             * This function gets called when the “remove local view” button 
             * is click and removes the last view added to the container.
             * It also checks to make sure that there are views to be removed.
             * 
             * @param event is the mouse event fromt the click of the button.
             * */
            private function handleRemoveLocalView(event:MouseEvent):void
            {
                if (localViewsContainer.numChildren > 0)
                {
                    localViewsContainer.removeChildAt(localViewsContainer.numChildren -1);
                }
            }
            /****************************************************
             * This is the function that is set to be called later so that 
             * the text area container that has the texts is scrolled to 
             * the bottom.
             * */
            private function moveScrollToBottom():void
            {
                messageLogDisplay.verticalScrollPosition = messageLogDisplay.maxVerticalScrollPosition;
            }
        ]]>
    </mx:Script>
    
</mx:VBox>

   This particular view has several interesting sections.  It has buttons to add and remove LocalView children.  It also has a TextArea that will act as a real time log display.  It also uses [Init], which is invoked after then context is fully created and this view is added to that context and is ready to participate in Parsley.  Notice the [MessageHandler] tag.  It doesn't specify a scope, which will be different in SharedComponents.mxml.  By default message handlers are global, that is they will receive messages sent from any context, that can be changed as it will be seen in the SharedComponents view.

Now let's take a look at the LocalView.mxml:

<?xml version="1.0" encoding="utf-8"?>
<!-- *************************************************************
   This is the view that is "Local" and will hold the LocalContext.  It is 
   intended to be created dynamically by the GlobalView, and have several 
   siblings.  Each one has its own context.  This view will hold a child 
   with the components that bind to the context objects.
   For more Parsley examples visit:  artinflex.blogspot.com
   ************************************************************* -->
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml"
         xmlns:parsley="http://www.spicefactory.org/parsley" 
         width="100%" xmlns:view="view.*" borderStyle="solid" 
         cornerRadius="5" backgroundColor="#60FA03" 
         backgroundAlpha="0.15" height="206">
        
    <!-- ContextBuilder is how you tell parsley to manage objects that are not Views.
    In this case I’m loading the LocalContext configuration file which is intended
    to be shared privately only by this view and its children.  Notice that you can 
    also specify a complete    function which gets called once it finishes creating
    the context and its    children (Managed Objects). -->        
    <parsley:ContextBuilder config="{LocalContext}" complete="handleLocalContextCreated()" />
    
    <!--  Configure is how you tell Parsley that you want this view to become 
    part of the Context and participate in all of Parsley’s features.  
    <Configure /> looks for the closest Context in the view hierarchy and attaches 
    the view to that particular context.  In this case, this view becomes part of 
    the context within this particular file, but it also can participate in the 
    context of its ancestors. -->    
    <parsley:Configure />

    <!--  SharedComponents is a view that contains the components that will 
    bind to properties of the models.   This view has two Shared components 
    to demonstrate the scope of the context.--> 
    <view:SharedComponents>
    </view:SharedComponents>
    <view:SharedComponents>
    </view:SharedComponents>
    
    <!--The text input and button are here to demonstrate custom messages 
        and how they can also be scoped.-->
    <mx:HBox width="100%" height="25">
        <mx:TextInput x="10" y="88" width="267" id="messageTextBox"/>
        <mx:Button x="255" y="88" label="Send Message"
                    id="sendMessageButton" enabled="false"
                     click="handleSendMessageClick(event)"/>
    </mx:HBox>

    <mx:Script>
        <![CDATA[            
            import model.LocalModel;
            import messages.LoggerMessage;
            
            /******************************
             * The [MessageDispatcher] is a special Parsely tag that injects 
             * a Parsley messaging function into the specified function.
             * Messages sent through this function do not need to extend 
             * the Event class and can be received either globally or locally.
             * */
            [MessageDispatcher]
            public var dispatcher:Function;
                
            /*******************************
             * This is the viewNumber assigned by the Global view, intended 
             * to indentify views and the context associated with it.  It also 
             * collaborates to illustrate part of the lifecycle of Parsley objects.
             * */            
            public var viewNumber:int;
            
            /*********************************
             * [Inject] is a special Parsley tag that asks Parsley to inject 
             * the object specified below.  Parsley will look in all of the 
             * contexts of this view and its ancestors (not its siblings, 
             * nor cousins, etc…) for a managed object of the class type 
             * specified, and it will inject that object into this view.
             * */
            [Inject]
            public var localModel:LocalModel;

            /**********************************
             * This function gets called when the context creation is complete,
             * notice that the dispatcher will be null, it is here primarily to 
             * demonstrate that specifically.  Dispatcher gets its value after 
             * the context has been created and configuration is complete.  
             * Dispatcher can be safely called in the [Init] method.
             * */
            public function handleLocalContextCreated():void
            {
                    // If you debug you will note that dispatcher will be null
                    // and that's why this message doesn't get sent.  dispatcher
                    // is injected afterwards, in the configure event.
                if (dispatcher != null)  
                    dispatcher(new LoggerMessage("Local Context has been created."));            
            }

            /**************************************
             * Methods tagged with [Init] get called when Parsley finishes 
             * processing this object or view and incorporates it into the 
             * context.  After this method is called, all injections have 
             * been performed, so it is safe to use them.
             * */
            [Init]
            public function handleInit():void
            {
                localModel.viewNumber = viewNumber;
                sendMessageButton.enabled = true;
                dispatcher(new LoggerMessage("LocalView is configured and ready"));
            }
            
            /****************************************
             * Methods tagged with [Destroy] get called when Parsley is about 
             * to remove this object/view from the context, and it will become 
             * available for garbage collection.  You can use this method to 
             * remove event listeners that could prevent this object/view to 
             * be garbage collected.
             * */
            [Destroy]
            public function handleDestory():void
            {
                dispatcher(new LoggerMessage("LocalView-" + viewNumber.toString()
                           + " is about to be destroyed"));
            }
            
            /****************************************
             * Simple method that dispatches the custom message entered in the 
             * text box.  Notice that dispatching messages is always Global, 
             * what selects if the message is local or not is the message handler.
             * */
            private function handleSendMessageClick(event:MouseEvent):void
            {
                dispatcher(new LoggerMessage(messageTextBox.text));
            }
        ]]>
    </mx:Script>
</mx:VBox>

  LocalView has two shared components, and creates it's own LocalContext and configures itself to participate in that context.  I would like you to notice that in the creation complete handler of the local context there is a call for the dispatcher.  The dispatcher will basically always be null.  The dispatcher will be injected after the view is configured which happens after the context has been created.  I added this to demonstrate exactly that.

  LocalView also allows you to send custom log messages.  Notice that dispatching messages is always global.  What chooses if it's local or not is the receiving message handler.

Ok, let's look at the SharedComponents.mxml:

<?xml version="1.0" encoding="utf-8"?>
<!--
    This component hosts two textboxes, one bound to a more 
    private model, and one to the global model.  It also has a 
    textbox that display messages received only within the 
    closest context.
 -->
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" 
    width="380" height="82" borderStyle="solid" cornerRadius="5" 
    horizontalScrollPolicy="off" verticalScrollPolicy="off"
    backgroundColor="#0538F6" backgroundAlpha="0.15" 
    xmlns:parsley="http://www.spicefactory.org/parsley">
    
    <!--  Configure is how you tell Parsley that you want this view to become 
    part of the Context and participate in all of Parsley’s features.  
    <Configure /> looks for the closest Context in the view hierarchy and attaches 
    the view to that particular context.  In this case, this view becomes part of 
    the context of its ancestors, no new context were created in this file. -->    
    <parsley:Configure />
    
    <!-- The text box bound to the more local model -->
    <mx:Canvas x="0" y="0" width="190" height="50" cornerRadius="5" borderStyle="solid">
        <mx:Label x="10" y="2" text="Local Context" fontWeight="bold"/>
        <mx:TextInput x="10" y="20" id="localTextBox"/>
    </mx:Canvas>
    
    <!-- The text box bound to the global model -->
    <mx:Canvas x="190" y="0" width="190" height="50" borderStyle="solid" cornerRadius="5">
        <mx:Label x="10" y="2" text="Global Context" fontWeight="bold"/>
        <mx:TextInput x="10" y="20" id="globalTextBox"/>
    </mx:Canvas>
    
    <!-- Binding is set with the special Flex Binding Tag to allow bidirectional
        binding.  -->
    <mx:Binding destination="localTextBox.text" source="localModel.sharedString" />
    <mx:Binding destination="localModel.sharedString" source="localTextBox.text" />        
    <mx:Binding destination="globalTextBox.text" source="globalModel.sharedString" />
    <mx:Binding destination="globalModel.sharedString" source="globalTextBox.text" />        
    
    <!-- This is the text box that displays local messages -->
    <mx:Label x="0" y="58" text="Received Local Message:"/>
    <mx:TextInput x="148" y="54" width="220" editable="false" id="receiveMessageTextB" enabled="true" color="#000000" backgroundColor="#BCD0D6"/>
    
    <mx:Script>
        <![CDATA[
            import model.GlobalModel;
            import model.LocalModel;
            import messages.LoggerMessage;
            
            /***********************************************
             * LocalModel is declared in the Local Context it will be unique
             * only within that specific view
             * */
            [Inject]
            [Bindable]
            public var localModel:LocalModel;
            
            /***********************************************
             * You can still access objects from a parent context.  GlobalModel
             * is from the context the grandparent of this component.  You can
             * access objects of any context of any ancestor of this view
             * */
            [Inject]
            [Bindable]
            public var globalModel:GlobalModel;

            /***********************************************
             * MessageHandler declares that this function should be called
             * when any message of type LoggerMessages is dispatch.  Notice
             * the special scope tag.  This indicates that this particular
             * function will only be called when the message is dispatched from
             * the same context this view is attached to.
             * */
            [MessageHandler (scope="local")]
            public function receiveLocalMessage(logMessage:LoggerMessage):void
            {
                receiveMessageTextB.text = logMessage.message;
            }
        ]]>
    </mx:Script>
</mx:Canvas>

  SharedComponents has a TextBox bound to the LocalContext and one to the GlobalContext.  Changes to the Local box will only be within the parent's Context, while the global view affects all SharedComponents.  Finally take a look a the [MessageHandler (scope="local")].  This handler will only be invoked when the message was dispatched from withing it's local context.  In this case from the context of it's parent LocalView.

  Well, that's it.  Hope you find this post useful.  I apologize for the long delay in posting it.  If you have any other particular question leave a comment.  There will be more examples in the near future.

  Next is the Decoupled Bindings Example.

Thursday, September 9, 2010

Quick Dive into Parsley (Messaging with Flex Events Example - Part 5)

This is part of a series of articles that should get you up and started using Spicesfactory's Parsley.
This is part 5.
In Part 1, I explained the reasons as to why we chose to use Parsley in our projects.
In Part 2, I explained what Parsley is and some basic concepts and how they relate to Parsley.
In Part 3, I showed how to set up Parsley and showed a basic injection example.
In Part 4, I showed how to send messages using Parsley that do not extend the Event class.

This article builds upon what was covered in Part's 2, 3, and 4 so if you haven't seen them, I encourage you to start with Part 1.

In this article will look at the same basic messaging example in Part 4 but in this case we will use managed Flex Events, rather than Parsley's dispatcher.

Parsley Messaging with Managed Flex Events Example
 So we looked at the basics of messaging in the previous note which involved creating a custom message class.  This class did not have to extend any Flash/Flex Event, and the message was dispatched using Parsley's Message Dispatcher.   This is not the only way of dispatching messages with Parsley.  Parsley can also manage standard Flash/Flex events.  You basically setup and dispatch an event like you would normally do in Flash/Flex and dispatch it.  Parsley will then manage that event and invoke any function set as a [MessageHandler].  Do note that once Parsley manages an event, it will no longer bubble.

  They way you tell Parsley to manage an event is with the [ManagedEvents] metadata tag.  First off, for Parsley to be able to know about an event, the event must be declared with the standard Flash/Flex Metadata tag [Event] .  You should get in the practice of doing this, regardless if you use Parsley or not, this allows the Flex compiler to be able to recognize the events as MXML tag attributes.  A quick example of how you use the [Event] tag is:
[Event(name="Log Message", type="events.LoggerMessageEvent")]
  You can declare your event tags in an pure ActionScript class before the class definition but within the package.  On an MXML component you can declare them in the <Script> block or within the <mx:Metadata> tags.  I think that in MXML component's it is best to declare them in the <mx:Metadata> section.

  Once you have your events declared, following those events you would place the [ManagedEvents] tag where you would specify the events you wish to manage.  Here is an example:

<mx:Metadata>
 [Event(name="Log Message", type="events.LoggerMessageEvent")]
 [ManagedEvents("Log Message")]

</mx:Metadata>

   Once that is set up, you dispatch your event like you would do usually, by calling the standard dispatchEvent() function.

   Ok, enough talk, let's go into the code.

   I've made some class name changes to reflect events, so here is the new structure.  I'm aware that I'm not following Flexs' convention where it comes to naming the packages.  I've structure the packages in a way that it's much easier to follow along.

   Let's begin begin by looking at the LoggerMessage clas, this class has been modified to extend the Flash Event class, so I renamed the class accordingly, it's new name is LoggerMessageEvent.  Here is the code to LoggerMessageEvent.as:

package events
{
    import flash.events.Event;
    
    /***************************************************************
     * This is a simple class that will hold a message so it can be 
     * delivered thought our application.  It only has one property
     * that is the message itself.  Note that it  
     * extends Flash Event.  It is inteded to be used for a log, sometime in 
     * future.
     * */ 
    public class LoggerMessageEvent extends Event
    {
        
        /********************
         * String used to identify our event
         * */
        public static const LOG_MESSAGE:String = "Log Message";
        
        
        /********************
         * This is the private var that holds our message
         * */
        private var _message:String;
        
        
        /********************
         * Constructor that takes the parameter as message
         * @param message  is a String that contains the text that will be set
         * */         
        public function LoggerMessageEvent(parType:String, message:String)
        {
            super(parType);            
            _message = message;
        }
        
        /*********************************
         * read only property that delivers the message.
         * */
        public function get message():String
         {
             return _message;
         }


        /*********************************
         * When you extend the Event class you should always override the
         * close class to make sure your custom event propagates properly. 
         * */        
        override public function clone():Event
         {
             return new LoggerMessageEvent(type,message);
         } 
        
    }
}

Basically the only changes are those required to extend an Event class.  Do note that I change the name of the package to reflect that it is an event.

Now let's look at the new SendView.mxml:

 <?xml version="1.0" encoding="utf-8"?>
<!--
This is a simple view that will send messages.  It is part of an example on how to
use Parsley.  Visit http://artinflex.blogspot.com for more info.
-->
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" width="128" height="86"
     xmlns:parsley="http://www.spicefactory.org/parsley" horizontalAlign="center" cornerRadius="2" borderStyle="inset">

    <!--
      If you wish to have Parsley manage your custom events extended from Events,
      they must be declared as metadata.  They can be in the script section or
      in between metadata tags in the MXML.  The first line is a stardard Event
      declaration that you should get into the habit of including regadless of
      using Parsley or not.  The second line [ManagedEvents] is what actually tells
      Parsley that it should listen for and take control of these events.
    -->
    <mx:Metadata>
        [Event(name="Log Message", type="events.LoggerMessageEvent")]
        [ManagedEvents("Log Message")]
    </mx:Metadata>
   
    <!--
    Configure is very important.  Configure dispatches an event that instructs Parsley that you 
    wish to have this view Managed by Parsley.  If you do not include this tag or do not launch
    the configure event, Injection nor Parsley messaging will not happen in this view.
    -->
    <parsley:Configure />
   
   
<!--
    The Text input is not tied to anything
-->
    <mx:TextInput width="100%" id="messageTextBox"/>
   
<!--
    The button will dispatch our events.
-->   
    <mx:Button label="Send Message"  click="handleButtonClick(event)"/>
   

    <mx:Script>
        <![CDATA[
            import events.LoggerMessageEvent;
       
    /******************************************
     *   In this example we will be using Parsley's Managed Events.
     *  This basically allows you to write standard FlexEvents that can
     *  easily be used in any other framework.  You simply dispatch your
     *  custom event like you would normaly do in Flex.  You simply have to
     *  have the [ManagedEvents] Metadata tag declared.  Look at the Metadata
     *  section of this file.
     ***/
            private function handleButtonClick(event:MouseEvent):void
            {
                dispatchEvent(new LoggerMessageEvent(LoggerMessageEvent.LOG_MESSAGE,messageTextBox.text));
            }
  
        ]]>
    </mx:Script>
</mx:VBox>
  

  To begin with, notice the <MX:Metadata> section at the top of the file, which was discussed at the beginning of this note.  Also note that you will no longer see a [MessageDispatcher] tag or function as these are no longer needed.  Because there is no dispatcher function injected, I removed the binding of the enabled property of the button with the function not being null.  Lastly note towards the bottom in the <Script> section that you now dispatch the message like you would dispatch any Flash/Flex event, with the dispatchEvent() function.

  You can read more about Flex custom events in Adobe's livedocs site.

  Now the ReceiveView really had no reason to change.  The only change I made was regarding the change of the LoggerMessage class into LoggerMessageEvent.  Other than that it's essentially the same class.
Here is ReceiveView.mxml:

<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml"  width="140" height="112"
     xmlns:parsley="http://www.spicefactory.org/parsley" horizontalAlign="center" 
     cornerRadius="3" borderStyle="solid">
    <mx:Label text="Received Messages" fontWeight="bold"/>
    
    <!--
    Configure is very important.  Configure dispatches an event that instructs Parsley that you 
    wish to have this view Managed by Parsley.  If you do not include this tag or do not launch
    the configure event, Injection nor Parsley messaging will not happen in this view.
    -->
    <parsley:Configure />    
    
     <!--
     This is a simple list component that will display the messages that will be received.
     -->
    <mx:List id="messageDisplayList" dataProvider="{receivedMessages}" 
              width="100%" height="86" x="0" y="0" ></mx:List>
    
    <mx:Script>
        <![CDATA[
            import mx.collections.ArrayCollection;
            import events.LoggerMessageEvent;
        
            /**************************
             * Private property that holds an ArrayCollection of the messages that have 
             * been received.
             * */
            [Bindable]         
               private var receivedMessages:ArrayCollection = null;
               
               /**************************
                * This function will receive any message that is of the type
                *  LoggerMessage Notice the MessageHandler tag.  That instructs
                *  Parsley that you wish to route any dispatched message of 
                * type LoggerMessage to this function.  Parsley figures this 
                * out because of the data type of the parameter of the function 
                * automatically.  MessageHandler can also be declared in MXML.
                * */
               [MessageHandler]
               public function handleLogMessage(logMessage:LoggerMessageEvent):void
                {
                    if (receivedMessages == null)
                       receivedMessages = new ArrayCollection();
                    receivedMessages.addItem(logMessage.message);
                }
            
        ]]>
    </mx:Script>
    
</mx:VBox>

And here is the app running with ViewSource enabled.  The basic functionality is the same as the previous article.



That's it for this note, hope it was helpful, in the next couple of notes we will look at some other features of Parsley.

Next is an example of Parsley Nested Context and Object lifecycle methods.

Saturday, September 4, 2010

Quick Dive into Parsley (Basic Messaging Example - Part 4)

This is part of a series of articles that should get you up and started using Spicesfactory's Parsley.
This is part 4.
In Part 1, I explained the reasons as to why we chose to use Parsley in our projects.
In Part 2, I explained what Parsley is and some basic concepts and how they relate to Parsley.
In Part 3, I showed how to set up Parsley and showed a basic injection example.

This article builds upon what was covered in Part's 2 and 3, so if you haven't seen them, I encourage you to start with Part 1.

In this article will look at a very basic example of how messaging works in Parsley.

Parsley Basic Messaging Example


What is Messaging?
   Messaging under the context covered here is the ability for two classes/components to communicate between each other without explicitly calling each others methods.  So basically "messaging" involves some decoupling, and that is the basic idea that drives messaging.

   Flash provides a fairly powerful and flexible messaging mechanism which is Flash  Events.  Flex adds Flex Events and Binding.  Yes, Binding is a messaging mechanism as it allows to invoke methods of other component's without calling them explicitly, in a very transparent way.  But Binding is focused on one specific task, notify that data has been updated, which is useful, and it's incredibly simple to set up.  The "problem" with Binding is that in the process of decoupling methods, you couple properties.  Now I'm not saying that Binding is bad, actually I think it's great, it is simply not something to be used everywhere.

  Flash and Flex Events are great.  From the sender's perspective, it's totally decoupled.  The class/component that needs to send the message does not need to know anything about who is listening.  All you do is dispatch an event.  Now from the receiving part is where it's not entirely decoupled.  In Flash and Flex, to be able to catch a message you must be listening for messages, and you implement this through event listeners. Event listeners, must be attached to some instance of a class/component which can be the the object that dispatched the event.  Now, events in Flash have the ability to "bubble" and what means is that you can add event listeners to the view parents of the object that dispatched the event and still catch the message.  Flash and Flex events have the capability to travel through the view hierarchy and be caught elsewhere.  So this still provides some decoupling but you still have to attach your listeners to some instance of that is part of the view hierarchy.  You could go for the root, which is the Application object, to decouple, but this means that you can't have more specific scopes.  Although Flash / Flex events are great, there is still a small gap to be filled, and that's where Parsley's messaging comes in.

Parsely's Messaging
  As discussed above, Parsley comes in to fill the gap and not only provides fully decoupled senders, it also provides fully decoupled receivers.  One of the great things about how Parsley implements it's messaging mechanism, is that it not only decouples the sender from the receiver, it decouples both from the Parsley framework itself.  You do not need to extend any class, nor you need to attach/bind/assign listeners to any specific object.  Also, Parsley's messaging is generic, it is not aimed towards any specific pattern.  So you can totally take advantage of Parsley's messaging to implement any MVC pattern or any other pattern and not have to extend/add/change any of Parsley's classes to do so.  It's simple and very flexible.  Additionally, you can use extend Flash/Flex events, but you do not have to.

So let's take a look at how it works.  You can have Parsley manage specific Flex events, or you can use simple classes to send messages.

We will begin with a simple class.  You can look at the docs on how to have Parsley manage standard Flex events.

Dispatching Messages
   Essentially you should begin by creating a class that will hold your message.  In this particular case, I'm building a simple application that logs every message sent and displays it in a listbox.  Here is the basic structure of our application.  You might notice that I'm not following convention regarding package names, this is on purpose to make the example much easier to follow.

So here is the messaging class called LoggerMessage.as

package messages
{
    /***************************************************************
     * This is a simple class that will hold a message so it can be 
     * delivered thought our application.  It only has one property
     * that is the message itself.  Note that it does not 
     * extend Flexs Event.  It is inteded to be used for a log in 
     * future.
     * */ 
    public class LoggerMessage
    {
        /********************
         * This is the private var that holds our message
         * */
        private var _message:String;
        
        /********************
         * Constructor that takes the parameter as message
         * @param message  is a String that contains the text that will be set
         * */         
        public function LoggerMessage( message:String)
        {
            _message = message;
        }
        
        /*********************************
         * read only property that delivers the message.
         * */
        public function get message():String
         {
             return _message
         }
    }
}

There is nothing special about this class.  It simply has a read only property which is the message itself that can only be defined when you instantiate the class.

Let's move on to the SendView.mxml file which is the view that actually sends the message.  There is a few things that you should notice.  There is a <Configure /> tag which if you recall from the previous article, it tells Parsley that you want it to manage this view.  There is a [MessageDispatcher] tag which we didn't see in the previous article.  This tells Parsley that you want it to inject a message dispatching function into the variable declared immediately below.  This function will help us dispatch non Flex Event messages.  Here is the code for SendView.mxml.

<?xml version="1.0" encoding="utf-8"?>
<!--
This is a simple view that will send messages
-->
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" width="128" height="86"
     xmlns:parsley="http://www.spicefactory.org/parsley" horizontalAlign="center" cornerRadius="2" borderStyle="inset">

    <!--
    Configure is very important.  Configure dispatches an event that instructs Parsley that you 
    wish to have this view Managed by Parsley.  If you do not include this tag or do not launch
    the configure event, Injection nor Parsley messaging will not happen in this view.
    -->
    <parsley:Configure />
    
<!--
    This CheckBox simple get's a check once the dispatcher has been injected.  
    If no injection has occured, the value will be null
-->    
    <mx:CheckBox label="Sender Ready" enabled="false" selected="{dispatcher != null}"/>
    
<!--
    The Text input is not tied to anything
-->
    <mx:TextInput width="100%" id="messageTextBox"/>
    
<!--
    The button will dispatch our events.
-->    
    <mx:Button label="Send Message" enabled="{dispatcher !=null}" click="handleButtonClick(event)"/>

    <mx:Script>
        <![CDATA[
            import messages.LoggerMessage;
        
    /******************************************
     *   In this example we will be using Parsley's Message dispatcher 
     *   which does not require the use of FlexEvents.  This takes 
     *   decoupling a little bit further, since the receiving end of the
     *   message will not have an event.target option in which they can
     *   access the sender.  This can also be declared in MXML.
     *   Important NOTE: Dispatcher does not have to be Bindable. I made
     *                   it Bindable so I could bind it to the checkbox and 
     *                    button for this example. 
     ***/            
             [MessageDispatcher]
             [Bindable]
            public var dispatcher:Function;
            
            private function handleButtonClick(event:MouseEvent):void
            {
                if (dispatcher != null)
                 {
                     dispatcher(new LoggerMessage(messageTextBox.text));
                 }
            }   
        ]]>
    </mx:Script>
</mx:VBox>

Take a look at the function executed when the button is pressed.  It first checks if the variable of type Function called dispatcher is not null.  This is the variable in which the  [MessageDispatcher] got injected into.  The message dispatcher function expects one argument, the instance of a class (any class) that you wish to send.  The keyword here is "any" in any class.  So I create a new instance of LoggerMessage with the text from the TextBox as the message.  That instance of the message will be sent to any class that wishes to receive it.  You can have more than one listener.  That's it for dispatching messages, now on to the receiving part.

Message Handlers
Receiving messages is quite simple and it is the same whether the message was sent using Parsley managed FlexEvents or the MessageDispatcher.  You must simply put a [MessageHandler] tag above the function declaration that you wish to get called.  Note that you will also have to include the <Configure />  tag to have Parsley manage this view.  Let's take a look at ReceiveView.mxml.


<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml"  width="140" height="112"
     xmlns:parsley="http://www.spicefactory.org/parsley" horizontalAlign="center" cornerRadius="3" borderStyle="solid">
    <mx:Label text="Received Messages" fontWeight="bold"/>
    
    <!--
    Configure is very important.  Configure dispatches an event that instructs Parsley that you 
    wish to have this view Managed by Parsley.  If you do not include this tag or do not launch
    the configure event, Injection nor Parsley messaging will not happen in this view.
    -->
    <parsley:Configure />    
    
     <!--
     This is a simple list component that will display the messages that will be received.
     -->
    <mx:List id="messageDisplayList" dataProvider="{receivedMessages}" 
              width="100%" height="86" x="0" y="0" ></mx:List>
    
    <mx:Script>
        <![CDATA[
            import mx.collections.ArrayCollection;
            import messages.LoggerMessage;
        
            /**************************
             * Private property that holds an ArrayCollection of the messages that have 
             * been received.
             * */
            [Bindable]         
               private var receivedMessages:ArrayCollection = null;
               
               /**************************
                * This function will receive any message that is of the type
                *  LoggerMessage Notice the MessageHandler tag.  That instructs
                *  Parsley that you wish to route any dispatched message of 
                * type LoggerMessage to this function.  Parsley figures this 
                * out because of the data type of the parameter of the function 
                * automatically.  MessageHandler can also be declared in MXML.
                * */
               [MessageHandler]
               public function handleLogMessage(logMessage:LoggerMessage):void
                {
                    if (receivedMessages == null)
                       receivedMessages = new ArrayCollection();
                    receivedMessages.addItem(logMessage.message);
                }
            
        ]]>
    </mx:Script>
    
</mx:VBox> 

  Notice towards the bottom there is a function called handleLogMessage(...) above that there is the [MessageHandler] tag.  How does Parsley know when to call this function?  Simple, by the datatype that it is expecting.  Any message sent through the Parsley dispatcher, or declared to be managed by Parsley, that is of the type LoggerMessage, will be received by this function, and Parsley will call this function for you.

So here is the app running.  View source is enabled.



Well that should get you started with Parsley.  I most certainly encourage you to go and read the docs, as this barely scratched the surface of what Parsley does.

In the next article, we look at this same example but using Managed Flex/Flash Events.