Archive for November, 2010

Controlling Navigation of Dynamically Added Modules in Cairngorm

In a recent application I was building there was a requirement for modules to be dynamically added at runtime based on a configuration file.  Since the application was already using Parsley and Cairngorm I looked to the existing Cairngorm navigation and module libraries to see if this was supported. I quickly discovered that the dynamic addition of modules and then being able to navigate to these modules was not supported out of the box. This led me to implement my own solution on top of the existing Cairngorm 3 libraries. There have also been a number of questions over on the Adobe forums about this topic so I decided it would be worth blogging about.

To solve my problem I had to implement two new distinct features.

  1. Dynamically add modules at runtime
  2. Connect these dynamically loaded modules to Cairngorm navigation

Dynamically Adding Modules

I’ll first look at how modules can be dynamically added to a Cairngorm/Parsley application based on a configuration file.

This is the simple XML file I defined that contains information about each module that needs to be loaded.

<modules>
<module id="mod1" label="Module 1" url="Module1.swf" location="content"/>
<module id="mod2" label="Module 2" url="Module2.swf" location="content"/>
</modules>

During the initialization of the main presentation model a message is dispatched to load the XML.

sendMessage(new LoadModuleConfigMessage(LoadModuleConfigMessage.LOAD));

Once the XML is loaded, a ModuleSet is updated to include a collection of Modules defined in the XML.  After the XML completes loading and is configured a second message is dispatched to add the modules to the view.  In this example all the modules are added to a ViewStack through the use of a repeater.

<mx:Repeater id="panelRepeater" dataProvider="{model.panels}"
repeatEnd="model.handleRepeatEnd(this)">
  <core:ControllerViewLoader width="100%" height="100%"
automationName="{panelRepeater.currentItem.moduleId}"
moduleManager="{panelRepeater.currentItem.moduleInfo}"
loadPolicy="{panelRepeater.currentItem.loadPolicy}"/>
</mx:Repeater>

You’ll notice that the repeater gets its data from a panels collection in its presentation model and simply repeats a ControllerViewLoader.  In the presentation model for this ViewStack there is a handler for the LoadModuleConfigMessage complete message.  In this handler the panels collection is generated by creating objects that contain the moduleId, moduleInfo and loadPolicy. The moduleInfo property is an instance of the ParsleyModuleManager which implements the IModuleManager interface.  It is this module manager that defines the location of the actual module SWF and what context and domain it should be loaded into.

[MessageHandler(selector="complete")]
public function loadComplete(msg:LoadModuleConfigMessage):void
{ 
    toolbarPM.menuItems.items.removeAll();
    ...
    moduleViews = new Array();
    var i:int;
    for (i = 0; i < modSet.modules.length; i++)
    { 
  var module:Module = modSet.modules.getItemAt(i) as Module;
       var view:Object = new Object();
       view.moduleId = module.location + "." + module.id;
       var modInfo:ParsleyModuleManager =
new ParsleyModuleManager(module.url, this.context,
                ClassInfo.currentDomain, null, null);
       view.moduleInfo = modInfo;
       view.loadPolicy = new BasicLoadPolicy(); ;
       moduleViews.push(new ObjectProxy(view));
       //Add menu bar item
       var dest:ContentDestination = new ContentDestination();
      dest.label = module.label;
      dest.destination = view.moduleId; 
      toolbarPM.menuItems.addDestination(dest);
    }
}
panels = new ArrayCollection(moduleViews);
}

Connecting Dynamically Added Modules to Navigation

A [Waypoint] metadata tag was first added to the ViewStack as specified in the Cairngorm documentation.

After the repeatEnd event is dispatched by the repeater in the ViewStack the modules have all been added and can now be safely wired to Cairngorm navigation.  This can be achieved by dispatching the Initialize event of the view stack.

vs.dispatchEvent(new Event(FlexEvent.INITIALIZE));

In order to actually navigate to a module the module has to be loaded first and then navigated to.  The module only needs to be loaded the first time it is navigated to.

 dispatchMessage(new LoadModuleMessage(destination));
 dispatchEvent(NavigationEvent.createNavigateToEvent(destination));

Finally, when defining landmarks in each individual module the landmark id must match the module id that was defined.  In the case of my solution moduleId is created from the location and id attributes defined in moduleConfig.xml.

Feel free to check out the full solution form the link below.

Share on Facebook