Example
This example show a simple interface that I created in the Flash IDE, which contains a list of different modules to be loaded at runtime. To run the example, select a Module from the list, then click Load Module. When you do this, the shell will load the selected module and the numbers will be feed into the module and a result will be returned by the module. In the result box, you should see the result.
You can enter different numbers in the input fields (don’t try invalid values like letter ‘a’ please) and press Calculate to see the result box changes.
The example does no do much but it illustrates how the principle can be applied for plug-ins or dynamic binding during run-time.
Remember that in this example, you need to press Load Module after you select the module, otherwise the result field will not be updated.
Overview
This example is a continuation of the previous post about creating modules in general:https://www.permadi.com/blog/2010/10/using-modules-in-flex-action-script-projects/
Here, we explore several things:
- Creating modules that do not extend MovieClip.
- Creating modules within packages.
- Loading module through a manager.
- Unloading module (unloading a module is a potentially complex issue and may be discussed in separate post in the future).
- Calling functions in modules.
- Using a custom module manager for book-keeping and unloading modules.
The Module Classes
This is the base class that will be used for the modules:
package com.permadi.modules { import flash.utils.getQualifiedClassName; import mx.modules.ModuleBase; [Frame(factoryClass="mx.core.FlexModuleFactory")] public class MyModuleBase extends ModuleBase { private var mModuleURL:String=""; public function MovieClipModuleBase():void { } public function performOperation(param1:Number, param2: Number):Number { return 0; } public function setModuleURL(moduleURL:String):void { mModuleURL=moduleURL; } public function getModuleURL():String { return mModuleURL; } public function unload():void { } public function getModuleName():String { return flash.utils.getQualifiedClassName(this); } } }
The mModuleURL is used for unloading the module. This is not strictly necessary and there are other creative ways to do this. getModuleName() returns the class name of the module, again not required, just for a demo purpose. performOperation() is a stub method for the other class (in a real application, you’d likely to create an Interface class for this). The three classes are shown below:
package com.permadi.modules { public class AdditionModule extends MyModuleBase { override public function performOperation(param1:Number, param2: Number):Number { return param1+param2; } } }
package com.permadi.modules { public class SubtractionModule extends MyModuleBase { override public function performOperation(param1:Number, param2: Number):Number { return param1-param2; } } }
package com.permadi.modules { public class MultiplicationModule extends MyModuleBase { override public function performOperation(param1:Number, param2: Number):Number { return param1*param2; } } }
You can see all they do are overriding the performOperation() function to do different things (addition, subtraction, multiplication). In real applications, they can be image processing operations, complex calculus, graph rendering, and so on.
The Shell UI
This Shell.FLA is a SWF that I created in the IDE for the purpose of creating some sort of User Interface. Nothing here is strictly required to use the module as the FLA only contains standard Flash UI or Components. The large white box is a TextArea that acts as a console for debugging purpose.
The Shell class
This class is the one responsible to load a module, unload a module, and call functions within the module. Although in this example, there is only one module active per given time, there’s no technical limitation that say so.
You can examine the shell class in the download package (linked at the bottom of this document), but here’s the part that load the module through a ModuleManager:
public function onLoadModuleButtonClicked(event:MouseEvent):void { // In this example, the Module class names are hard coded in the mTestShell SWF. var moduleURL:String="com/permadi/modules/"+mTestShell.combo_moduleList.selectedLabel+".swf"; mTestShell.mc_textArea.appendText("nLoading "+moduleURL); var moduleInfo:IModuleInfo=mModuleManager.loadModule(moduleURL); if (!moduleInfo) { mModuleManager.addEventListener(ModuleEvent.READY, onModuleReady); } // Already loaded before, so just use the same instance else { mModule=mModuleManager.getModuleInstance(moduleURL); mTestShell.btn_execute.visible=true; mTestShell.mc_textArea.appendText("nonModuleReady "+mModule.getModuleName()); // Execute opration onCalculateButtonClicked(null); } }
Again, the reason we are using a module manager is so that
The performOperation() function is called and depending on which module is active, the ‘operation’ can do different things. In this example, they just do basic math operations.
public function onCalculateButtonClicked(event:MouseEvent):void { mTestShell.mc_textArea.appendText("nonCalculateButtonClicked"); mTestShell.textField_result.text=mModule.performOperation( mTestShell.textField_param1.text, mTestShell.textField_param2.text); }
The Module Manager
This simple manager handles the task of loading modules and when the module is loaded, it redispatches the ModuleEvent.READY event. In this case the event is then intercepted by the shell and the shell creates an instance of the module (if there’s not already one in the manager). You can improve the class by having a cache of frequently used modules.
package { import mx.modules.IModuleInfo; import mx.events.ModuleEvent; import mx.modules.ModuleManager; import flash.events.Event; import flash.events.EventDispatcher; import mx.events.FlexEvent; import flash.display.Loader; import com.permadi.modules.MyModuleBase; public class ExampleModuleManager extends EventDispatcher { private var mModulesList:Object=null; public function ExampleModuleManager():void { mModulesList=new Array(); } public function loadModule(moduleURL:String):IModuleInfo { if (mModulesList[moduleURL]==null) { var moduleInfo:IModuleInfo=ModuleManager.getModule(moduleURL); moduleInfo.addEventListener(FlexEvent.LOADING, onModuleLoading); moduleInfo.addEventListener(ModuleEvent.SETUP, onModuleSetup); moduleInfo.addEventListener(ModuleEvent.READY, onModuleReady); moduleInfo.addEventListener(ModuleEvent.ERROR, onModuleError); moduleInfo.addEventListener(ModuleEvent.PROGRESS, onModuleProgress); moduleInfo.addEventListener(ModuleEvent.UNLOAD, onModuleUnload); // We are keeping the URL so we can do back referencing during unload moduleInfo.data={moduleURL:moduleURL}; mModulesList[moduleURL]=new Object(); mModulesList[moduleURL].moduleInfo=moduleInfo; moduleInfo.load(); trace("loadModule "+moduleURL); return null; } else { return mModulesList[moduleURL].moduleInfo; } } public function getModuleInstance(moduleURL:String):MyModuleBase { if (!mModulesList[moduleURL].moduleInstance) { mModulesList[moduleURL].moduleInstance=mModulesList[moduleURL].moduleInfo.factory.create(); } return mModulesList[moduleURL].moduleInstance; } public function unloadModule(module:MyModuleBase):void { var moduleInfo:IModuleInfo=mModulesList[module.getModuleURL()].moduleInfo; mModulesList[module.getModuleURL()]=null; moduleInfo.removeEventListener(FlexEvent.LOADING, onModuleLoading); moduleInfo.removeEventListener(ModuleEvent.SETUP, onModuleSetup); moduleInfo.removeEventListener(ModuleEvent.READY, onModuleReady); moduleInfo.removeEventListener(ModuleEvent.ERROR, onModuleError); moduleInfo.removeEventListener(ModuleEvent.PROGRESS, onModuleProgress); moduleInfo.unload(); trace("unloadModule "+module.getModuleURL()); } public function onModuleSetup(event:ModuleEvent):void { trace("onModuleSetup"); } public function onModuleLoading(event:FlexEvent):void { trace("onModuleLoading"); } public function onModuleReady(event:ModuleEvent):void { var moduleInfo:IModuleInfo=event.module; trace("onModuleReady "+moduleInfo.url); dispatchEvent(event); } public function onModuleError(event:ModuleEvent):void { trace("onModuleError "+event.errorText); } public function onModuleProgress(event:ModuleEvent):void { trace("onModuleProgress "+event.bytesLoaded/event.bytesLoaded*100+"%"); } public function onModuleUnload(event:ModuleEvent):void { trace("onModuleUnload"); event.module.removeEventListener(ModuleEvent.UNLOAD, onModuleUnload); } } }
The mModuleList keeps a list of the modules (or rather, the moduleInfo) and its instances (in moduleInstance) that are already loaded and instantiated. This means that modules are cached and can be re-used without having to reload again. Note: in this example, you can only have one instance per module, so if you need to have multiple instances, then this won’t work.
Download source: /tutorial/multiple-flex-modules-example/MultipleModulesExample.zip