The Cairngorm Module library is designed to simplify the configuration, rendering and loading of modular content. Additonally it offers flexiblity to communicate to modules. It contains infrastructure classes, view components, a mechanism for loading and communicating to modular content on-demand in response to Parsley messages.
The biggest benefit of modularization we see is often not the reduced initial SWF size, although that is a benefit, but rather the development efficiency and encapsulation of functional areas. A module can be developed, tested and profiled in relative isolation by a small group of developers, and the contracts can be agreed between modules. Build times are quick and one module can effectively offer services to another. The implementation of one module can change without impact on others. This approach can scale to large delivery teams.
The Flex SDK provides a ModuleLoader view component for placing modular content in-line with other views. The ModuleManager singleton offers a manual management of modules. The IModuleInfo interface abstracts the loading and unloading of modular content.
The Cairngorm Module library provides additional features on top of the Flex SDK and Parsley module support:
The ParsleyModuleDescriptor can be used to describe the location and application domain of the module. This can be specified in a Parsley MXML or XML context.
When configuring the module library via MXML, use the CairngormModuleSupport ContextBuilder when creating the Parsley context:
<spicefactory:ContextBuilder>
<cairngorm:CairngormModuleSupport/>
<spicefactory:FlexConfig type="{ CairngormModuleLibSampleContext }"/>
</spicefactory:ContextBuilder>
Then, describe the module in the MXML context:
<cg:ParsleyModuleDescriptor objectId="moduleA"
url="example/moduleA/ModuleA.swf"
applicationDomain="{ ClassInfo.currentDomain }"/>
When configuring the module library via XML that can be loaded at runtime, use the CairngormModuleXMLSupport additionally to the CairngormModuleSupport ContextBuilder when creating the Parsley XML context:
<spicefactory:ContextBuilder>
<cairngorm:CairngormModuleSupport/>
<cairngorm:CairngormModuleXMLSupport/>
<spicefactory:XmlConfig file="runtimeContext.xml"/>
</spicefactory:ContextBuilder>
Then, describe the module in the XML context:
<objects xmlns="http://www.spicefactory.org/parsley"
xmlns:cairngorm="http://www.adobe.com/cairngorm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.spicefactory.org/parsley
http://www.spicefactory.org/parsley/schema/2.0/parsley-core.xsd">
<cairngorm:parsley-module-descriptor object-id="moduleA"
url="example/moduleA/ModuleA.swf"/>
</objects>
In order to reference a definition of a module you need to take the objectId of a ParsleyModuleDescriptor...
<cg:ParsleyModuleDescriptor objectId="moduleA"
url="example/moduleA/ModuleA.swf"/>
...and connect it with an IModuleManager instance defined in the view that loads the module.
<mx:Script>
<![CDATA[
import com.adobe.cairngorm.module.IModuleManager;
[Inject(id="moduleA")]
public var moduleManager:IModuleManager;
]]>
</mx:Script>
<mx:Label text="moduleId: { moduleId == null ? 'NOT SET' : moduleId }"/>
<module:ModuleViewLoader moduleManager="{ moduleManager }"
skinClass="com.adobe.cairngorm.module.ModuleViewLoaderSkin">
<module:loadPolicy>
<module:BasicLoadPolicy/>
</module:loadPolicy>
</module:ModuleViewLoader>
As soon as the IModuleManager implementation is injected into the view, (which would be the DisplayObject's addedToStage event following the default of Parsley), the module starts loading through the ModuleViewLoader component. The skinClass property allows to define the visual display during the module loading and error state, which is likley to be custom to most applications.
For Flex 3, you can use a ViewLoader component like the below
<module:ViewLoader moduleManager="{ moduleManager }">
<module:loadPolicy>
<module:BasicLoadPolicy/>
</module:loadPolicy>
</module:ModuleViewLoader>
Since we migrated our library to support Flex 4, we did refactor the ViewLoader to mimic the SDK4 new ModuleViewLoader in order to support LoadPolicy but now the ViewLoader do not support states for loading and error. If you want to have different states we suggest that you move to SDK4 and use ModuleViewLoader or you can subclass the ViewLoader to add these behaviors which is likely to be custom to most applications in any case.
When using the ModuleViewLoader (with SDK4) or the ViewLoader (SDK3) we provide a load policy strategy mechanism that will take care of loading the module when appropriate according to the strategy provided.
In the module library we provide 2 different strategies but developers can easily implement their own specific strategies using the ILoadPolicy interface.
When messages need to be send to modules using Parsley messaging, the modules need to be loaded and initialized in order to receive Parsley messages.
The module library takes care of this aspect and keep the messages in a queue until the destined modules are ready to accept the messages.
The BasicLoadPolicy strategy is the default loading policy that mimic the SDK ModuleLoader. This strategy will load the module as soon as the ModuleViewLoader (or ViewLoader) is added to stage and then it's going to unload the module as soon as removed from stage.
Any messages sent, while the module itself is not yet loaded and ready, are kept in a queue and re-dispatched as soon as the module is available.
Currently when a message is dispatched and the destination module is never loaded, the message is kept forever in the queue however we are currently working on improving this by providing a timeout to the queue so when the timeout occur, messages are cleaned from the queue.
The LazyModuleLoadPolicy strategy is going to load the module on demand when a message destined to that module is dispatched.
Therefore, when the ModuleViewLoader (or ViewLoader) is added to stage, the module is not yet loaded it will instead wait for a message that is destined to this specific module. When this is occurring the ModuleViewLoader (or ViewLoader) is automatically loading the module and as soon as the module is loaded will redispatch the message that was waiting in the queue.
The LazyModuleLoadPolicy do not provide any automatic ways to unload a module so when using a this strategy we need to manually take care of unloading the module when necessary.
The relationship between a module definition and a module view does not have to be 1:1.
Applications can for example display multiple instances of one module. The module library allows to control the communication between the loader and one or many module definitions and one or many module instances.
In any case for the inter-communication to work with the Module library there are few things to consider.
The user needs to define the message inside the context with a ModuleMessageInterceptor definition.
For the loaded module to receive the message the module needs to implement the IParsleyModule interface, which points to the Parsley context that can receive the message
:::xml
<mx:module xmlns:mx="http://www.adobe.com/2006/mxml" <br=""> xmlns:spicefactory="http://www.spicefactory.org/parsley"
implements="com.adobe.cairngorm.module.IParsleyModule" ></mx:module>
<mx:Script>
<![CDATA[
import com.adobe.cairngorm.module.IParsleyModule;
import org.spicefactory.parsley.flex.FlexContextBuilder;
public function get contextBuilder():ContextBuilderTag
{
return contextBuilderTag;
}
]]>
</mx:Script>
<spicefactory:ContextBuilder id="contextBuilderTag"
config="{ ModuleAContext }" />
:::ActionScript
[MessageHandler(scope="local")]
public function broadcastMessageHandler(message:BroadcastMessage):void
{ ... }
The ModuleMessageInterceptor from above...
<cg:ParsleyModuleDescriptor objectId="moduleA"
url="example/moduleA/ModuleA.swf"/>
<cg:ModuleMessageInterceptor type="{ BroadcastMessage }"
moduleRef="moduleA"/>
...directs every BroadcastMessage to all module instances from the definition "moduleA".
The below message is being directed to all instances of all module definitions.
<cg:ModuleMessageInterceptor type="{ ClearLogMessage }"/>
In order to dispatch to one particular instance of one module definition, the ModuleMessageInterceptor searches for a property annotated with a ModuleId definition....
<cg:ModuleMessageInterceptor type="{ PingMessage }"/>
...which can be defined inline:
public class PingMessage
{
private var _moduleId:String;
public function PingMessage(moduleId:String)
{
this._moduleId=moduleId;
}
[ModuleId]
public function get moduleId():String
{
return _moduleId;
}
}
The value of this ModuleId property must be defined on the ViewLoader component's moduleId property.
For more information, please view the examples within the ModuleTest project.