|
Communication between modules and the parent application,
and among modules, is possible. You can use the following approaches
to facilitate inter-module, application-to-module, and module-to-application
communication:
Interfaces — You can create ActionScript interfaces that
define the methods and properties that modules and applications
can access. This gives you greater control over module and application
interaction. It also prevents you from creating dependencies between
modules and applications. For more information, see Using interfaces for module communication.
Query string parameters — Modules are loaded with a URL;
you can pass parameters on this URL and then parse those parameters
in the module. This is only a way to pass simple data, and is not
appropriate for complex data. For more information, see Passing data to modules with the query string.
ModuleLoader's child, ModuleManager ‘s factory,
and Application’s parentApplication properties
— You can use these properties to access modules and applications.
However, by using these properties, you create a tightly-coupled
design that prevents code reuse and is easily broken. In addition,
you also create dependencies among modules and applications that cause
class sizes to be bigger. For more information, see Accessing modules from the parent application, Accessing the parent application from modules, and Accessing modules from other modules.
The following techniques for accessing methods and properties
apply to parent applications as well as modules. Modules can load
other modules, which makes the loading module similar to the parent
application in the simpler examples.
Using interfaces for module communicationYou can use an interface to provide module-to-application
communication. Your modules implement the interface and your application
calls its methods or sets its properties. The interface defines
stubs for the methods and properties that you want the application
and module to share. The module implements an interface known to
the application, or the application implements an interface known
to the module. This lets you avoid so-called hard dependencies between the
module and the application.
In the main application, when you want to call methods on the
module, you cast the ModuleLoader class’s child property
to an instance of the custom interface.
The following example application lets you customize the appearance
of the module that it loads by calling methods on the custom IModuleInterface interface.
The application also calls the getModuleName() method.
This method returns a value from the module and sets a local property
to that value.
<?xml version="1.0"?>
<!-- modules/interfaceexample/MainModuleApp.mxml -->
<s:Application
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
xmlns="*">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<fx:Script>
<![CDATA[
import mx.events.ModuleEvent;
import mx.modules.ModuleManager;
[Bindable]
public var selectedItem:Object;
[Bindable]
public var currentModuleName:String;
private function applyModuleSettings(e:Event):void {
/* Cast the ModuleLoader's child to the interface.
This child is an instance of the module.
You can now call methods on that instance. */
var ichild:* = mod.child as IModuleInterface;
if (mod.child != null) {
/* Call setters in the module to adjust its
appearance when it loads. */
ichild.setAdjusterID(myId.text);
ichild.setBackgroundColor(myColor.selectedColor);
} else {
trace("Uh oh. The mod.child property is null");
}
/* Set the value of a local variable by calling a method
on the interface. */
currentModuleName = ichild.getModuleName();
}
private function reloadModule():void {
// Reset the ColorPicker control:
myColor.selectedColor = 0xFFFFFF;
// Reload the module:
mod.unloadModule();
mod.loadModule();
}
]]>
</fx:Script>
<s:Form>
<s:FormItem label="Current Module:">
<s:Label id="l1" text="{currentModuleName}"/>
</s:FormItem>
<s:FormItem label="Adjuster ID:">
<s:TextInput id="myId" text="Enter your ID"/>
</s:FormItem>
<s:FormItem label="Background Color:">
<mx:ColorPicker id="myColor"
selectedColor="0xFFFFFF"
change="applyModuleSettings(event)"/>
</s:FormItem>
</s:Form>
<s:Label text="Long Shot Insurance" fontSize="24"/>
<s:ComboBox labelField="label" prompt="Select Module"
close="selectedItem=ComboBox(event.target).selectedItem">
<s:dataProvider>
<s:ArrayList>
<fx:Object label="Auto Insurance" module="AutoInsurance2.swf"/>
</s:ArrayList>
</s:dataProvider>
</s:ComboBox>
<s:Panel width="100%" height="100%">
<s:ModuleLoader id="mod"
width="80%" height="80%"
url="{selectedItem.module}"
ready="applyModuleSettings(event)"/>
</s:Panel>
<s:Button id="b1" label="Reload Module" click="reloadModule()"/>
</s:Application>
The following example defines a simple interface that has two
getters and one setter. This interface is used by the application
in the previous example.
// modules/interfaceexample/IModuleInterface.as
package
{
import flash.events.IEventDispatcher;
public interface IModuleInterface extends IEventDispatcher {
function getModuleName():String;
function setAdjusterID(s:String):void;
function setBackgroundColor(n:Number):void;
}
}
The following example defines the module that is loaded by the
previous example. It implements the custom IModuleInterface interface.
<?xml version="1.0"?>
<!-- modules/interfaceexample/AutoInsurance2.mxml -->
<s:Module
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
width="100%" height="100%" implements="IModuleInterface">
<s:Panel id="p1" title="Auto Insurance"
width="100%" height="100%"
backgroundColor="{bgcolor}">
<s:Label id="myLabel" text="ID: {adjuster}"/>
</s:Panel>
<fx:Script>
<![CDATA[
[Bindable]
private var adjuster:String;
[Bindable]
private var bgcolor:Number;
public function setAdjusterID(s:String):void {
adjuster = s;
}
public function setBackgroundColor(n:Number):void {
/* Use a bindable property to set values of controls
in the module. This ensures that the property will be set
even if Flex applies the property after the module is
loaded but before it is rendered by the player. */
bgcolor = n;
/* Don't do this. The backgroundColor style might not be set
by the time the ModuleLoader triggers the READY
event: */
// p1.setStyle("backgroundColor", n);
}
public function getModuleName():String {
return "Auto Insurance";
}
]]>
</fx:Script>
</s:Module>
In general, if you want to set properties on controls in the
module by using external values, you should create variables that
are bindable. You then set the values of those variables in the
interface’s implemented methods. If you try to set properties of
the module’s controls directly by using external values, the controls might
not be instantiated by the time the module is loaded and the attempt
to set the properties might fail.
Passing data to modules with the query stringOne way to pass data to a module is to append query string
parameters to the URL that you use to load the module. You can then
parse the query string by using ActionScript to access the data.
In the module, you can access the URL by using the loaderInfo property.
This property points to the LoaderInfoobject
of the loading SWF (in this case, the main application). The information
provided by the LoaderInfo object includes load progress, the URLs
of the loader and loaded content, the file size of the application,
and the height and width of the application.
The following example application builds a unique query string
for the module that it loads. The query string includes a firstName and lastName parameter.
<?xml version="1.0"?>
<!-- modules/QueryStringApp.mxml -->
<s:Application
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
height="500" width="400">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<fx:Script>
<![CDATA[
public function initModule():void {
// Build query string so that it looks something like this:
// "QueryStringModule.swf?firstName=Nick&lastName=Danger"
var s:String = "QueryStringModule.swf?" + "firstName=" +
ti1.text + "&lastName=" + ti2.text;
// Changing the url property of the ModuleLoader causes
// the ModuleLoader to load a new module.
m1.url = s;
}
]]>
</fx:Script>
<s:Form>
<s:FormItem id="fi1" label="First Name:">
<s:TextInput id="ti1"/>
</s:FormItem>
<s:FormItem id="fi2" label="Last Name:">
<s:TextInput id="ti2"/>
</s:FormItem>
</s:Form>
<s:ModuleLoader id="m1"/>
<s:Button id="b1" label="Submit" click="initModule()"/>
</s:Application>
The following example module parses the query string that was
used to load it. If the firstName and lastName parameters
are set, the module prints the results in a TextArea. The module
also traces some additional information available through the LoaderInfo
object:
<?xml version="1.0"?>
<!-- modules/QueryStringModule.mxml -->
<s:Module
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
creationComplete="parseString()">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<fx:Script>
<![CDATA[
import mx.utils.*;
[Bindable]
private var salutation:String;
public var o:Object = {};
public function parseString():void {
try {
/* Remove everything before the question mark, including
the question mark. */
var myPattern:RegExp = /.*\?/;
var s:String = this.loaderInfo.url.toString();
s = s.replace(myPattern, "");
/* Create an Array of name=value Strings. */
var params:Array = s.split("&");
/* Print the params that are in the Array. */
var keyStr:String;
var valueStr:String;
var paramObj:Object = params;
for (keyStr in paramObj) {
valueStr = String(paramObj[keyStr]);
ta1.text += keyStr + ":" + valueStr + "\n";
}
/* Set the values of the salutation. */
for (var i:int = 0; i < params.length; i++) {
var tempA:Array = params[i].split("=");
if (tempA[0] == "firstName") {
o.firstName = tempA[1];
}
if (tempA[0] == "lastName") {
o.lastName = tempA[1];
}
}
if (StringUtil.trim(o.firstName) != "" &&
StringUtil.trim(o.lastName) != "") {
salutation = "Welcome " +
o.firstName + " " + o.lastName + "!";
} else {
salutation = "Full name not entered."
}
} catch (e:Error) {
trace(e);
}
/* Show some of the information available through loaderInfo: */
ta2.text = "AS version: " + this.loaderInfo.actionScriptVersion;
ta2.text += "\nApp height: " + this.loaderInfo.height;
ta2.text += "\nApp width: " + this.loaderInfo.width;
ta2.text += "\nApp bytes: " + this.loaderInfo.bytesTotal;
}
]]>
</fx:Script>
<s:Label text="{salutation}"/>
<s:TextArea height="75" width="250" id="ta1"/>
<s:TextArea height="200" width="250" id="ta2"/>
</s:Module>
This example uses methods of the Stringand StringUtilclasses,
plus a for-in loop to parse the URLs. You can also use methods of
the URLUtiland URLVariablesclasses
to do this.
Modules are cached by their URL, including the query string.
As a result, you will load a new module if you change the URL or
any of the query string parameters in the URL. This can be useful
if you want multiple instances of a module based on the parameters
that you pass in the URL with the ModuleLoader.
Accessing the parent application from modulesModules can access properties and methods of the parent
application by using a reference to the parentApplication property.
In most cases, you should avoid doing this as it creates a close
coupling between the module and the application. Having this coupling
directly negates some of the benefits of using modules.
The following example accesses the expenses property
of the parent application when the module first loads. The module
then uses this property, an ArrayCollection, as the source for its
chart’s data. When the user clicks the button, the module calls
the getNewData() method of the parent application
that returns a new ArrayCollection for the chart:
<?xml version="1.0"?>
<!-- modules/ChartChildModule.mxml -->
<s:Module
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
width="100%" height="100%"
creationComplete="getDataFromParent()">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
private var expenses:ArrayCollection;
// Access properties of the parent application.
private function getDataFromParent():void {
expenses = parentApplication.expenses;
}
]]>
</fx:Script>
<mx:ColumnChart id="myChart" dataProvider="{expenses}">
<mx:horizontalAxis>
<mx:CategoryAxis dataProvider="{expenses}" categoryField="Month"/>
</mx:horizontalAxis>
<mx:series>
<mx:ColumnSeries xField="Month" yField="Profit"
displayName="Profit"/>
<mx:ColumnSeries xField="Month" yField="Expenses"
displayName="Expenses"/>
</mx:series>
</mx:ColumnChart>
<mx:Legend dataProvider="{myChart}"/>
<s:Button id="b1"
click="expenses = parentApplication.getNewData();"
label="Get New Data"/>
</s:Module>
The following example shows the parent application that the previous
example module uses:
<?xml version="1.0"?>
<!-- modules/ChartChildModuleLoader.mxml -->
<s:Application
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<fx:Script>
<![CDATA[
import mx.collections.ArrayCollection;
[Bindable]
public var expenses:ArrayCollection = new ArrayCollection([
{Month:"Jan", Profit:2000, Expenses:1500},
{Month:"Feb", Profit:1000, Expenses:200},
{Month:"Mar", Profit:1500, Expenses:500}
]);
public function getNewData():ArrayCollection {
return new ArrayCollection([
{Month:"Apr", Profit:1000, Expenses:1100},
{Month:"May", Profit:1300, Expenses:500},
{Month:"Jun", Profit:1200, Expenses:600}
]);
}
]]>
</fx:Script>
<s:ModuleLoader url="ChartChildModule.swf" id="m1"/>
</s:Application>
You can also call methods and access properties on other modules.
For more information, see Accessing modules from other modules.
The drawback to this approach is that it can create dependencies
on the parent application inside the module. In addition, the modules
are no longer portable across multiple applications unless you ensure
that you replicate the behavior of the applications.
To avoid these drawbacks, you should use interfaces that secure
a contract between the application and its modules. This contract
defines the methods and properties that you can access. Having an
interface lets you reuse the application and modules as long as
you keep the interface updated. For more information, see Using interfaces for module communication.
Accessing modules from other modulesYou can access properties and methods of other modules
by using references to the other modules through the parent application.
You do this by using the ModuleLoader class’s child property.
This property points to an instance of the module’s class, which
lets you call methods and access properties. In most cases, you
should avoid doing this as it creates a close coupling among the
modules. Having this coupling directly negates some of the benefits
of using modules in the first place.
The following example defines a single application that loads
two modules. The InterModule1 module defines a method that returns
a String. The InterModule2 module calls that method and sets the
value of its Label to the return value of that method.
Main application:
<?xml version="1.0"?>
<!-- modules/InterModuleLoader.mxml -->
<s:Application
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<fx:Script>
<![CDATA[
]]>
</fx:Script>
<s:ModuleLoader url="InterModule1.swf" id="m1"/>
<s:ModuleLoader url="InterModule2.swf" id="m2"/>
</s:Application>
Module 1:
<?xml version="1.0"?>
<!-- modules/InterModule1.mxml -->
<s:Module
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
width="100%" height="100%">
<fx:Script>
<![CDATA[
/* Defines the method that the other module calls. */
public function getNewTitle():String {
return "New Title";
}
]]>
</fx:Script>
</s:Module>
Module 2:
<?xml version="1.0"?>
<!-- modules/InterModule2.mxml -->
<s:Module
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
width="100%" height="100%">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<fx:Script>
<![CDATA[
[Bindable]
private var title:String = "Original Title";
// Call method of another module.
private function changeTitle():void {
title = parentApplication.m1.child.getNewTitle();
}
]]>
</fx:Script>
<s:HGroup>
<s:Label id="l1" text="Title: "/>
<s:Label id="myTitle" text="{title}"/>
</s:HGroup>
<s:Button id="b1" label="Change Title" click="changeTitle()"/>
</s:Module>
The application in this example lets the two modules communicate
with each other. You could, however, define methods and properties
on the application that the modules could access. For more information,
see Accessing the parent application from modules.
As with accessing the parent application’s properties and methods
directly, using the technique described in this section can make
your modules difficult to reuse and also can create dependencies
that can cause the module to be larger than necessary. Instead,
you should use interfaces to define the contract between modules.
For more information, see Using interfaces for module communication.
Accessing modules from the parent applicationYou can access the methods and properties of a module from
its parent application by getting an instance of the module’s class.
Referencing a module by its class name in an application causes
the whole module and all of its dependencies to be linked into the
application. This defeats the purpose of using modules. However,
using this technique can be useful for debugging and testing.
You should only use interfaces to access the methods and properties
of a module unless you want to create these dependencies. For more
information, see Using interfaces for module communication.
If you use the ModuleLoader to load the module, you can call
methods on a module from the parent application by referencing the
ModuleLoader class’s child property, and casting
it to the module’s class. The child property is
an instance of the module’s class. In this case, the module’s class
is the name of the MXML file that defines the module.
The following example calls the module’s getTitle() method
from the parent application:
Parent Application:
<?xml version="1.0"?>
<!-- modules/ParentApplication.mxml -->
<s:Application
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<fx:Script><![CDATA[
[Bindable]
private var s:String;
private function getTitle():void {
s = (m1.child as ChildModule1).getModTitle();
}
]]></fx:Script>
<s:Label id="l1" text="{s}"/>
<s:ModuleLoader id="m1"
url="ChildModule1.swf"
ready="getTitle()"/>
</s:Application>
Module:
<?xml version="1.0"?>
<!-- modules/ChildModule1.mxml -->
<s:Module
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
width="100%" height="100%">
<fx:Script><![CDATA[
/* Defines the method that the application calls. */
public function getModTitle():String {
return "Child Module 1";
}
]]></fx:Script>
</s:Module>
If you load the module that you want to call by using the ModuleManager
API, there is some additional coding in the shell application. You
use the ModuleManager factory property to get an
instance of the module’s class. You can then call the module’s method
on that instance.
The following module example defines a single method, computeAnswer():
<?xml version="1.0"?>
<!-- modules/mxmlmodules/SimpleModule.mxml -->
<s:Module
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx">
<fx:Script>
<![CDATA[
public function computeAnswer(a:Number, b:Number):Number {
return a + b;
}
]]>
</fx:Script>
</s:Module>
The following example gets an instance of the SimpleModule class
by using the factory property to call the create() method.
It then calls the module’s computeAnswer() method
on that instance:
<?xml version="1.0"?>
<!-- modules/mxmlmodules/SimpleMXMLApp.mxml -->
<s:Application
xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
creationComplete="initApp()">
<s:layout>
<s:VerticalLayout/>
</s:layout>
<fx:Script>
<![CDATA[
import mx.modules.IModuleInfo;
import mx.modules.ModuleManager;
public var assetModule:IModuleInfo;
public var sm:Object;
[Bindable]
public var answer:Number = 0;
public function initApp():void {
/* Get the IModuleInfo interface for the specified URL. */
assetModule = ModuleManager.getModule("SimpleModule.swf");
assetModule.addEventListener("ready", getModuleInstance);
assetModule.load(null, null, null, moduleFactory);
}
public function getModuleInstance(e:Event):void {
/* Get an instance of the module. */
sm = assetModule.factory.create() as SimpleModule;
}
public function addNumbers():void {
var a:Number = Number(ti1.text);
var b:Number = Number(ti2.text);
/* Call a method on the module. */
answer = sm.computeAnswer(a, b).toString();
}
]]>
</fx:Script>
<s:Form>
<s:FormHeading label="Enter values to sum."/>
<s:FormItem label="First Number">
<s:TextInput id="ti1" width="50"/>
</s:FormItem>
<s:FormItem label="Second Number">
<s:TextInput id="ti2" width="50"/>
</s:FormItem>
<s:FormItem label="Result">
<s:Label id="ti3" width="100" text="{answer}"/>
</s:FormItem>
<s:Button id="b1" label="Compute" click="addNumbers()"/>
</s:Form>
</s:Application>
In this example, you should actually create a module that extends
the ModuleBase class in ActionScript rather than an MXML-based module
that extends the Module class. This is because the example module
does not have any visual elements and contains only a single method
that computes and returns a value. A module that extends the ModuleBase
class would be more lightweight than a class that extends Module.
For more information on writing ActionScript-based modules that
extend the ModuleBase class, see Creating ActionScript-based modules.
|
|
|