ModuleManager and IModuleInfo - loading Flex Modules dynamically
When you’re working with Flex Modules, most of the time the ModuleLoader component will be enough to get you up and running. It will quickly and easily let you load your Modules and add them to the display list, it’ll even dispatch an event to let you know when the Module is ready. Lovely stuff. It’s very similar to the SWFLoader component with some added Module-related goodness thrown in.
However, there may be times when the ModuleLoader component may not be appropriate for your needs, and you’ll have to get your hands dirty and get involved with the lower-level ModuleManager class. Some examples of when ModuleLoader might not be suitable include:
- Your Module is entirely non-visual, and therefore shouldn’t be added to the display list
- Performance is really important to your application, and the extra overhead of the ModuleLoader container is something you need to remove to try and speed things up. (ModuleLoader essentially wraps your Module’s content in an extra container, incurring a slight performance hit).
- You need greater control over how and when your modules are loaded and unloaded.
How does ModuleManager work?
ModuleManager maintains a map of modules, indexed by each module’s URL. Information about the module is stored as an object implementing IModuleInfo, and is retrieved by calling the ModuleManager.getModule method, passing in the module’s URL as the method’s only parameter. Once you’ve retrieved the IModuleInfo object, you can call its load method to starting loading in the module. While the module is loading, you can monitor the load progress using a variety of different events (of type ModuleEvent). Finally, when the module is loaded and available, you can create an instance of that module using someModuleInfo.factory.create ().
IModuleInfo in more detail
As mentioned above, ModuleManager maintains a mapping of URLs to modules. When you call ModuleManager’s static getModule method, it returns an object of type IModuleInfo. If you dig around within the ModuleManager class, you’ll see that this interface is implemented internally. It’s useful to know a little bit more about how you can interact with the ModuleInfo, what methods it exposes, and what events it dispatches.
There are four read-only public Boolean properties which offer information about the current state of the module. They can briefly be described:
- IModuleInfo.error - This’ll be true if there was an error during loaded. You’re also notified of such an error by the ModuleEvent.ERROR event.
- IModuleInfo.loaded - This will be true if you’ve previously called the IModuleInfo.load method. Note: It does not necessarily signify that the module has been loaded, just that the module load has begun.
- IModuleInfo.setup - When enough of the module has loaded to be able to call the factory.info () method, this flag will be true. When this point in the load has been reached, a ModuleEvent.SETUP event will be dispatched.
- IModuleInfo.ready - When enough of the module has been loaded for you to be able to instantiate an instance of the module using the factory.create () method, this flag will be true. At this stage, a ModuleEvent.READY event will be dispatched. This property will typically become true at some point after the IModuleInfo.setup becomes true.
There are some additional properties which can also be useful:
- IModuleInfo.url fairly obviously returns the URL associated with the IModuleInfo object. This could be useful if you’re using one event handler to listen out for the events from multiple modules - the event.currentTarget.url property could be used to identify which module had sent out the event.
- IModuleInfo.data is an object which you can use to associate data with a particular module. The data will be shared across all instances of a particular module, so this property comes in handy if you want to share state, or user information between multiple instances of the same dynamically loaded module.
- IModuleInfo.factory is pretty important. Once the the module is ‘ready’ (after the ModuleEvent.READY event has been dispatched, or when IModuleInfo.ready returns true), you can create an instance of your module by calling the IFlexModuleFactory.create method. Additionally, once the module has been ’setup’ (after ModuleEvent.SETUP has fired, or when IModuleInfo.setup returns true), you can query the IFlexModuleFactory.info method to find out a little more about the module that has been loaded.
Next we should look at some of the methods exposed by an IModuleInfo:
- IModuleInfo.load - This is the method you’ll call to start loading a module. You can optionally pass in information about the current ApplicationDomain and SecurityDomain if necessary. When the module is loading, events will be dispatched along the way as detailed below. Quite a nice feature here is that if the module has already been loaded, and you attempt to reload it, it’ll still dispatch the “setup” and “ready” events, meaning that you can use one set of event listener methods without needing to worry about whether the module is loading for the first time, or if it already exists in local memory. Snazzy…
- IModuleInfo.release - This method will release the current reference to the module. Internally, when this method is called, a reference counter will be decremented. If no references to the module exist after this method has been called, it will automatically be unloaded.
- IModuleInfo.unload - This triggers the unload of the module. It’s worth noting that if any references to the module exist, it will not be garbage collected. Also, calling this method won’t remove your module from the display list, you’ll have to do that too.
Lastly, let’s look at the events dispatched… All the events listed here are instances of ModuleEvent, and for some of the events you’ll be able to access information about the load progress through the event’s bytesLoaded and bytesTotal properties.
- ModuleEvent.ERROR - Rather obviously, this is dispatched when an error occurs during loading. This will also be indicated by the IModuleInfo.error property returning true.
- ModuleEvent.PROGRESS - This event is dispatched periodically during the load, giving you the chance to display a visual indication of load progress if you want. This is one of the events where the bytesLoaded and bytesTotal properties will be set.
- ModuleEvent.SETUP - This event is dispatched when enough of the module has downloaded for you to call the IModuleInfo.factory.info method. At this point, IModuleInfo.setup will also return true. This event does not allow you to access load progress through the bytesLoaded and bytesTotal properties.
- ModuleEvent.READY - This event is dispatched when enough of the module has downloaded for you to create an instance of the module through the IModuleInfo.factory.create method. From then on, IModuleInfo.ready will also return true. Apparently, this event is one where the bytesLoaded and bytesTotal properties should be set, although I’ve noticed that this isn’t always the case. I reckon it’s safer just to use the ModuleEvent.PROGRESS information for displaying progress information.
- ModuleEvent.UNLOAD - This event is dispatched when the module has been unloaded. No progress information is available through the event object with this event.
A simple example
First, a basic module, SimpleModule, which does nothing more than display a label within a panel.
<? xml version= "1.0" encoding= "utf-8"?>
<mx:Module xmlns:mx= "http://www.adobe.com/2006/mxml" layout= "absolute">
<mx:TitleWindow x= "10" y= "10" width= "120" height= "100" layout= "absolute">
<mx: Text text= "SimpleModule!" />
</mx:TitleWindow>
</mx:Module>
The the main application, which will load in this module and attach instances to a Tile control.
<? xml version= "1.0" encoding= "utf-8"?>
<mx:Application xmlns:mx= "http://www.adobe.com/2006/mxml"
applicationComplete= "initApp()">
<mx:Script>
<! [CDATA [
import mx. modules. Module;
import mx. events. ModuleEvent;
import mx. modules. ModuleManager;
import mx. modules. IModuleInfo;
protected var _moduleInfo:IModuleInfo;
private function initApp (): void {
addToLog ( "Application initialised" );
// create the module - note, we're not loading it yet
_moduleInfo = ModuleManager. getModule ( "SimpleModule.swf" );
// add some listeners
_moduleInfo. addEventListener (ModuleEvent. READY, onModuleReady );
_moduleInfo. addEventListener (ModuleEvent. SETUP, onModuleSetup );
_moduleInfo. addEventListener (ModuleEvent. UNLOAD, onModuleUnload );
_moduleInfo. addEventListener (ModuleEvent. PROGRESS, onModuleProgress );
}
protected function getModuleInfo () : IModuleInfo {
// return the module info
return _moduleInfo;
}
/**
* Adds output to the log
**/
protected function addToLog (msg: String ) : void {
log. text += msg + "/n";
// scroll to the bottom on the next frame
callLater (scrollToBottom );
}
protected function scrollToBottom () : void {
// scroll to the bottom
log. verticalScrollPosition = log. maxVerticalScrollPosition;
}
/**
* Called when the "load" button is pressed
*
* Starts loading the module - when the module has been
* loaded, an instance of the module will be created
* and added to the tile control
*
**/
private function loadModule () : void {
addToLog ( "Loading module" );
// load the module
getModuleInfo (). load ();
}
/**
* Called when the "unload" button is pressed
*
* Removes all the instances of the module from the
* tile control, then unloads the module
*
*/
private function unloadModule () : void {
addToLog ( "Unloading module" );
// clear out all the the instances
// of the module from the tile
tile. removeAllChildren ();
// unload the module
getModuleInfo (). release ();
//getModuleInfo().un
}
/**
* Handler for the ModuleEvent.PROGRESS event
**/
protected function onModuleProgress ( e:ModuleEvent ) : void {
addToLog ( "ModuleEvent.PROGRESS received: " + e. bytesLoaded + " of " + e. bytesTotal + " loaded." );
}
/**
* Handler for the ModuleEvent.SETUP event
**/
private function onModuleSetup ( e:ModuleEvent ) : void {
addToLog ( "ModuleEvent.SETUP received" );
// cast the currentTarget
var moduleInfo:IModuleInfo = e. currentTarget as IModuleInfo;
addToLog ( "Calling IModuleInfo.factory.info ()" );
// grab the info and display information about it
var info: Object = moduleInfo. factory. info ();
for ( var each: String in info ) {
addToLog ( " " + each + " = " + info [each ]);
}
}
/**
* Handler for the ModuleEvent.READY event
**/
private function onModuleReady ( e:ModuleEvent ): void {
addToLog ( "ModuleEvent.READY received" );
// cast the currentTarget
var moduleInfo:IModuleInfo = e. currentTarget as IModuleInfo;
// Add an instance of the module's class to the
// display list.
addToLog ( "Calling IModuleInfo.factory.create ()" );
tile. addChild ( moduleInfo. factory. create () as SomeModule );
addToLog ( "SomeModule instance created and added to Display List" );
}
/**
* Handler for the ModuleEvent.UNLOAD event
**/
public function onModuleUnload ( e:ModuleEvent ) : void {
addToLog ( "ModuleEvent.UNLOAD received" );
}
]]>
</mx:Script>
<mx:HBox width= "100%" height= "100%">
<mx:Tile id= "tile" width= "100%" height= "100%" />
<mx:TextArea id= "log" width= "100%" height= "100%"/>
</mx:HBox>
<mx:ApplicationControlBar>
<mx: Button label= "Load" click= "loadModule ()" />
<mx: Button label= "Unload" click= "unloadModule ()"/>
</mx:ApplicationControlBar>
</mx:Application>
What’s going on here? Well… when the app is created, initApp is called and the IModuleInfo object is created, and various event listeners are setup. When the user clicks the load button, the module is loaded. It doesn’t matter whether this is the first time the module is loaded, or if the module is already loaded and ready, the ModuleEvent.READY event will be dispatched to let us know that the module is ready, and that we can now create an instance of it. Within the handler for the ModuleEvent.READY event, we call IModuleInfo.factory.create () and add the instance of the module to our Tile control.
When the user clicks the unload button, all instances of the Module that we’ve added to the Tile control are removed, and we call IModuleInfo.unload to unload the module. A log message from within the handler for ModuleEvent.UNLOAD demonstrates that this is happening.
Additionally, within the event handler for ModuleEvent.SETUP we show an example of the IModuleInfo.factory.info method - this’ll give you an example of the kind of information you can retrieve about the module, and from looking at the log messages you’ll be able to see the order in which these events are dispatched.
Lastly, here’s the finished example:
<script type="text/javascript"></script>
(Update: I’ve explained the IFlexModuleFactory class in a little more detail here.)