Saturday, October 30, 2010

Quick Dive into Parsley (Automatic Component Wiring - Part 9)

 This is part of a series of articles that should get you up and started using Spicesfactory's Parsley by Jens Halm.. It includes examples on how to use Parsley for Flex.
This is part 9.
In Part 3, I showed how to set up Parsley and showed a Parsley 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.
In Part 6, I showed nested contexts and object lifecycle methods.
In Part 7, I showed Decoupled Bindings
In Part 8, I showed how to use Dynamic Commands & Dynamic Objects

This article builds upon what was covered in the previous examples, so if you haven't seen them, I encourage you to start with at the beginning of this series.  This article focuses on Parsley's Automatic Component Wiring.


Automatic Component Wiring
   In all of the previous examples from this series, I explicitly told Parsley that I wanted a particular view to be managed by Parsley through using the <Configure /> tag.  Although this is very convenient, it is not the only way.  It also poses one problem.  What if your view is written entirely in ActionScript?  You can fire off the configure event, but it is meant only for Flash and you are strongly discourage to use it under Flex because the configure could get dispatched before the Context is ready.  Additionally, you might be interested in a method in which you can centrally manage which views get added to the Context or not?

  Well, you are in luck, Parsley has an Automatic Component Wiring feature that allows you to centrally configure which views are managed and which are not.

   The concept is fairly simple.  If you enable the autowire component feature, you can specify in your context config file, which views you want Parsley to manage.  Alternatively, rather than specifying all the views in your context config file, you can write a filter function that can decide programmatically which views are managed and which are not.  I’ve added two examples that shows each method.

   So how do you set this up?   It’s actually quite simple.  First we must change a little our context config tag declaration from what we are used to.  Usually we set it up in a one line tag, but not in this case.  We must use the <ViewSettings /> sub tag of the <ContextBuilder> tag.

   So when you declare your context you would have something similar to this:
<parsley:ContextBuilder>
    <parsley:ViewSettings autowireComponents="true"/>
    <parsley:FlexConfig type="{MyConfig}"/>
</parsley:ContextBuilder>

   In your context config file you would add <View> tags.  Something similar to this:
<View type="{CustomViewOne}"/>

   As you can see it’s actually pretty simple.  You can read more in section 8.4 Automatic Component Wiring of the developer manual.

  I’ve got two examples again.  One that you specify the views in the context config file, and the other where a filter function is used so that you don’t have to specify each view in the context config file.

Specifying the views in the context config file example

   Here is the working Parsley Automatic Component wiring example with view source enabled:



Now let take a look at the structure of the project.

   As you can see, the structure is very simple.  We have one model that will be shared called SimpleModel, and we have 3 views.  The main view and two custom views inside the views package.

   If you pay close attention, you will notice that I've updated the Parsley and the spicelib libraries to the 2.3.1 version which is the current one at the time of this writing.

   Ok let's start by taking a look at the model. Here is SimpleModel.as:

package model
{
    /*****************************************
     *  This is a very simple model that holds a string that will be shared
     * among some different views.
     *
     *  For more Flex and Parsley examples visit: artinflex.blogspot.com 
     */
    public class SimpleModel
    {
        /**********
         *  This is the shared string of the Model 
         */
        [Bindable]
        public var sharedString:String = "Hello Parsley";
    }
}

   Well, this is a very simple model that hold just a single string set to be bindable.  Pretty much like the original example.

  Now let's take a look at the views.  Here is CustomViewOne.mxml:

<?xml version="1.0" encoding="utf-8"?>
<!--
   Custom View Component used to demonstrate automatic wiring to Parsley
   Notice that there is no <Configure /> tag in this file.
   
   For more Parsley and Flex examples visit:  artinflex.blogspot.com
-->
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" width="200" cornerRadius="5" 
    backgroundColor="#FADEDE" 
    xmlns:parsley="http://www.spicefactory.org/parsley" 
    borderStyle="solid" horizontalAlign="center" verticalAlign="middle" verticalGap="5">

    <mx:Label text="Custom View One" fontWeight="bold" fontSize="12"/>
    
    <!-- Text Input bound to shared string in the shared model -->
    <mx:TextInput width="192" id="sharedTextInput"/>
    <!-- Binding is set to be bi-directional  -->
    <mx:Binding destination="sharedTextInput.text" source="sharedModel.sharedString" />
    <mx:Binding destination="sharedModel.sharedString" source="sharedTextInput.text" />    
    
    <mx:Script>
        <![CDATA[
            import model.SimpleModel;
            /***********************************************
             * Shared Model injecected into this view.
             */
            [Inject] [Bindable]
            public var sharedModel:SimpleModel;
        ]]>
    </mx:Script>    
</mx:VBox>

   This is a pretty similar like the views on the Basic Injection Example.  A TextInput set with bi-directional binding to the string in the shared model.  The big difference is that there is no <Configure/> tag here.  This view gets added to the context by the Context Config file.  CustomViewTwo.mxml is identical to CustomViewOne, except that the title is changed and the background color is set to a different color.  Other than that they are the same.


   Now let's take a look at the context configuration file.  Here is AutowireContextConfig.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.
    -->    
<mx:Object
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:spicefactory="http://www.spicefactory.org/parsley"  
    xmlns:model="model.*"
    xmlns:views="views.*"
    >    
<!---    
   Our simple Model that holds the shared String  
-->    
    <model:SimpleModel />    
    
<!--  Here is where I declare the two views that I want Parsley to autowire as soon
as they are added to the display list.  Type is the Class name of the view component -->    
    <spicefactory:View type="{CustomViewOne}" />
    <spicefactory:View type="{CustomViewTwo}" />
    
    <mx:Script>
        <![CDATA[
        /**   Must use import to specify the class of the view */
            import views.*;
        ]]>
    </mx:Script>
</mx:Object>

   The config file is fairly standard.  The new kids on the block are the two <View /> tags that I mentioned towards the beginning of this post.  With the <View/> tag I tell Parsley that I want the view specified in the type parameter to be wired to the context whenever it is added to the stage as a child view of the view that holds this context, in this particular case as a child of AutowireComponents.  Nested contexts still apply.

So let's take a look at the main view file that holds the context. here is AutowireComponents.mxml:

<?xml version="1.0" encoding="utf-8"?>
<!--  This example demonstrates the ability to autowire view components.
  Rather than using <Configure /> in each view component, you can specify the
  view components that you wish Parsley to manage for you in the context config
  file.
  
  For more Flex and Parsley examples visit: http://artinflex.blogspot.com
 -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" 
    xmlns:parsley="http://www.spicefactory.org/parsley" 
    xmlns:views="views.*" width="215" height="140" viewSourceURL="srcview/index.html">

<!---    
    ContextBuilder is how you tell parsley to manage objects.
      Notice that the syntax used is a bit different from the previous examples.
      This is so that I can set the View Settings option to use Autowire.  This
      is not enabled by default. 
-->        
    <parsley:ContextBuilder>
        <parsley:ViewSettings autowireComponents="true" />
        <parsley:FlexConfig type="{AutowireContextConfig}" />
    </parsley:ContextBuilder>
    
    <mx:Label text="Autowire Component Example Ver 1" top="3" fontWeight="bold" horizontalCenter="0"/>
    <!-- Two view components that get added -->
    <views:CustomViewOne top="25" horizontalCenter="0"/>    
    <views:CustomViewTwo top="80" horizontalCenter="0"/>
</mx:Application>

   As mentioned in the comments, the <ContextBuilder> tag is no longer set in one line.  It has two child tags.  <ViewSettings/> that specifies that the components should be wired automatically and the <FlexConfig/> which is the context config file, that we usually set just with the config parameter.

   Not much to it.

Using the autowireFilter option example
   Imagine that you have a fairly large project in which you have many view components.  You would like to programmatically set which views are wired to the Context, let's say by the package they are in, that way you don't have to specify each individual component in your context config file with <View> tags.  Or you wish to apply some condition, any condition to check to see if the view component should get wired to the context or not.

   Well, that's what the autowireFilter option is for.  With it, you can specify a class that will have a specific method that will be called by Parsley in which you receive the view component as a parameter and you just tell Parsley if it should wire that component to the Context.

   So here is the example with view source enabled:



Here is the structure of this project.

   The overall structure didn't change.  I simply added a new file and package.  The new file is the AutowireFilterExample.as file which is a class that extends the DefaultViewAutowireFilter class from Parsley.  So technically it's not just a filter function, it's a filter function inside a filter class.  Other than that, it's the same.

   So let's take a look at what this class looks like:  Here is AutowireFilterExample.as:

package parsleyFilters
{
    import flash.display.DisplayObject;
    import flash.utils.getQualifiedClassName;
    
    import org.spicefactory.parsley.core.factory.ViewSettings;
    import org.spicefactory.parsley.core.view.ViewAutowireMode;
    import org.spicefactory.parsley.core.view.impl.DefaultViewAutowireFilter;

    /*****************************************
     *  This class is the filter that decides whether a view will be 
     * managed or not by Parsley.  When declaring the context you must
     * specify to use the filter class in the ViewSettings section
     * 
     * For more Flex examples visit:  http://artinflex.blogspot.com
     */
    public class AutowireFilterExample extends DefaultViewAutowireFilter
    {
        /******************************
         * The constructor
         */
        public function AutowireFilterExample(settings:ViewSettings=null, excludedTypes:RegExp=null)
        {
            super(settings, excludedTypes);            
        }
        
        /******************************
         * The actual filter function.  It receives a view DisplayObject that
         * Parsley wants to know if it should add or not to it's managed views.
         * I simply get the full qualified class name of the view.  And
         * check to see if it is in the package called "views".  If it is, then it is
         * managed, if not, then it's not.
         */
        public override function filter (view:DisplayObject) : ViewAutowireMode
        {
            var fullClassname:String = getQualifiedClassName(view);
            
            if (fullClassname.indexOf("views::") == 0) 
             {
                return ViewAutowireMode.ALWAYS;
             }
            else 
             {
                return ViewAutowireMode.NEVER;
             }
        }
    }
}

   As you can see, it's fairly simple.  It's a class in which you have to override the public filter() function.  The filter function receives one argument which is a DisaplayObject that Parsley wants to know if it should be wired to the context.  You either return an Always  for yes, or Never for no.  In this particular example I make the decision based on if the view is in the "views" package.  If it is, then wire it, if not, then don't.  Parsley calls this method for every view that is a child of the view holding the context, and also for the view that holds the context itself.

  So what else is new?  Well, in the context config file I removed the <View> tags as these are no longer needed.  Here is the AutowireContextConfig.mxml file:

<?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.
    
    The views are now longer specified as they are handled through the AutowireFilter
    -->    
<mx:Object
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:spicefactory="http://www.spicefactory.org/parsley"  
    xmlns:model="model.*"
    xmlns:views="views.*"
    >    
<!---    
   Our simple Model that holds the shared String  
-->    
    <model:SimpleModel />
</mx:Object>

   As stated, just the <View> tags were removed, and the script section as the imports where no longer needed.

   The only other file that was changed is the main file where the context is created.  Here is AutowireComponents.mxml:

<?xml version="1.0" encoding="utf-8"?>
<!--  This example demonstrates the ability to autowire view components.
  Rather than using <Configure /> in each view component, you can specify the
  view components that you wish Parsley to manage for you in the context config
  file.
  
  For more Flex and Parsley examples visit: http://artinflex.blogspot.com
 -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" 
    xmlns:parsley="http://www.spicefactory.org/parsley" 
    xmlns:views="views.*" width="215" height="140">
    <mx:Script>
        <![CDATA[
            import parsleyFilters.AutowireFilterExample;
        ]]>
    </mx:Script>

<!---    
    ContextBuilder is how you tell parsley to manage objects.
      Notice that the syntax used is a bit different from the previous examples.
      This is so that I can set the View Settings option to use Autowire.  This
      is not enabled by default. 
-->        
    <parsley:ContextBuilder>
        <parsley:ViewSettings  autowireComponents="true" autowireFilter="{new AutowireFilterExample()}" />
        <parsley:FlexConfig type="{AutowireContextConfig}" />
    </parsley:ContextBuilder>
    
    <mx:Label text="Autowire Component Example Ver 2" top="3" fontWeight="bold" horizontalCenter="0"/>
    <!-- Two view components that get added -->
    <views:CustomViewOne top="25" horizontalCenter="0"/>    
    <views:CustomViewTwo top="80" horizontalCenter="0"/>    
</mx:Application>

   The only new addition to this file is the autowireFilter parameter in the <ViewSettings> tag.  You just assign a new instance of the class you created that extends Parsley's DefaultViewAutowireFilter class.  It's fairly simple to set up.

   Well, I do hope that you found this post useful.

   Next -> Fast Inject.  Injection without Reflection.

12 comments:

  1. Hi All:
    "CustomViewOne/Two".mxml should include tags,make it works!

    ReplyDelete
  2. smartkit:
    No it should not include the <Configure> tags. That's the point!
    This is automatic component wiring, which mean you do not have to use the Tag. It works just fine without it, as long as you set you context builder with:
    <parsley:ContextBuilder>
       <parsley:ViewSettings autowireComponents="true" />
       <parsley:FlexConfig type="{AutowireContextConfig}" />
    </parsley:ContextBuilder>

    ReplyDelete
  3. Interesting possibilities. But whatever the one choosed, the thing is still that in one way or another, you have to tell parsley that a given view should be managed or not.

    Having this information stored directly inside the view or centralized (or dynamically computed based on package name), I have yet to decide what is better for my project.

    ReplyDelete
  4. This comment has been removed by the author.

    ReplyDelete
  5. If one is writing a pure ActionScript views is it better to use the autowireFilter of to dispatch a "configureView" event when the view is added on stage?

    This second technique is described in the Parsley documentation:
    http://www.spicefactory.org/parsley/docs/2.4/manual/view.php#config_explicit

    The second approach looks a lot simpler...

    ReplyDelete
  6. Is it a Flex app? Jens has always recomended that the dispatching a new Event("configureView", true) should not be used in Flex apps, because how Flex handles the adding and removing of views. Although it works, it may not work under certain conditions.

    New in 2.4, there is a Configure.view(this).execute(); which would seem the best way to go, but I haven't used it.

    ReplyDelete
  7. Hi,

    Thank you for your tutorial,

    i tried to use auto wiring with Parsley, but i have a strange problem.

    i declared a panel with a form as autowired.

    When i try to access to the different field of the form from a controller (another class) i have an null object exception. even if, the injection is OK.

    has you an idea about orgines of this problem ?

    i suspect the reflection but i am not sure.

    thanks in advance.

    ReplyDelete
  8. Hi Amine,

    You are welcome.

    I'm going to guess a little because there is not enough info.

    I think you have a misconception. Wired views are not meant to be injectable to other components such controllers. It is just an alternative to using the Configure tag. They are not models in which you can access their properties.

    Hope that helps.

    ReplyDelete
  9. Very well written series. Parsley, like any other framework, has quite a steep learning curve and blog posts like this help ease the curve. Had 2 questions with respect to this blog entry.

    1. In a multi-context application, we will have multiple autowirecontextconfig's and map them to respective ContextBuilder through id's. Is that correct?
    2. In a large application, as 'jahz' mentioned above, we will have to declare plenty of views in the config. Are there any trade-off's here, any workarounds here?

    ReplyDelete
  10. Hi Subbu,

    Haven't done that explicitly, but I guess it depends on what you are calling a multi-context application. You can give each context config a unique name, so when you create something like what is shown in:
    http://artinflex.blogspot.com/2010/09/quick-dive-into-parsley-nested-contexts.html

    Each context has it's own scope so that ids should not be needed.

    2) Tradeoff with respect to what? Not using the Configure tag? Performance wise, it should be insignificant unless you have hundreds of thousands of views, which is unlikely. Maintenance wise it can be more work and harder to keep in sync, but it isn't that hard unless you have a couple of hundred views, but if you do then you should probably break your app into modules, and keep in set of view in it's own context. Or at least break it into smaller sub context following some view hierarchy.

    If you have an app with THAT many views, should also consider investing some time into building a more generic view that configures itself to the content. Are the vies really that unique? Can you use one view all over the place just by adding an option or two?

    What do you call a large application? How many views?

    ReplyDelete