Adobe Flex 3 Help

Passing data

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:

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.

Accessing modules from the parent application

You can access the methods and properties of a module from its parent application. To do this, you must get an instance of the module's class.

If you use the ModuleLoader to load the module, you can call methods on a module from the parent application by referencing the ModuleLoader 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 getTitle() method from the parent application:

Parent Application:

<?xml version="1.0"?>
<!-- modules/ParentApplication.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Script><![CDATA[    
        [Bindable]
        private var s:String;
        
        private function getTitle():void {
            s = (m1.child as ChildModule1).getModTitle();
        }       
    ]]></mx:Script>
    <mx:Label id="l1" text="{s}"/>
    <mx:ModuleLoader url="ChildModule1.swf" id="m1" ready="getTitle()"/>
</mx:Application>

The executing SWF file for the previous example is shown below:

Module:

<?xml version="1.0"?>
<!-- modules/ChildModule1.mxml -->
<mx:Module xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%">
    <mx:Script><![CDATA[    
        // Defines the method that the application calls.
        public function getModTitle():String {
            return "Child Module 1";
        }    
    ]]></mx:Script>
</mx:Module>

This approach creates a tight coupling between the application and the module, and does not easily let you use the same code when loading multiple modules. Another technique is to use an interface to define the methods and properties on the module (or group of modules) that the application can call. For more information, see Using interfaces for module communication.

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 -->
<mx:Module xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Script>
        <![CDATA[
            public function computeAnswer(a:Number, b:Number):Number {
                return a + b;
            }
        ]]>
    </mx:Script>
</mx: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 computeAnswer() method on that instance:

<?xml version="1.0"?>
<!-- modules/mxmlmodules/SimpleMXMLApp.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="initApp()">
  <mx: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();
        }

        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();
        }
    ]]>
  </mx:Script>

   <mx:Form>
        <mx:FormHeading label="Enter values to sum."/>
        <mx:FormItem label="First Number">
            <mx:TextInput id="ti1" width="50"/>
        </mx:FormItem>
        <mx:FormItem label="Second Number">
            <mx:TextInput id="ti2" width="50"/>
        </mx:FormItem>
        <mx:FormItem label="Result">
            <mx:Label id="ti3" width="100" text="{answer}"/>
        </mx:FormItem>
        <mx:Button id="b1" label="Compute" click="addNumbers()"/>
    </mx:Form>
</mx:Application>

The executing SWF file for the previous example is shown below:

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 this example 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.

Accessing the parent application from the modules

Modules can access properties and methods of the parent application by using a reference to the parentApplication property.

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 -->
<mx:Module xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%" creationComplete="getDataFromParent()">
    <mx:Script><![CDATA[    
        import mx.collections.ArrayCollection;

        [Bindable]
        private var expenses:ArrayCollection;
        
        // Access properties of the parent application.
        private function getDataFromParent():void {
            expenses = parentApplication.expenses;
        }        
    ]]></mx: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}"/>
     
     <mx:Button id="b1" click="expenses = parentApplication.getNewData();" label="Get New Data"/>
     
</mx:Module>

The following example shows the parent application that the previous example module uses:

<?xml version="1.0"?>
<!-- modules/ChartChildModuleLoader.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx: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}
            ]);
        }

    ]]></mx:Script>
    <mx:ModuleLoader url="ChartChildModule.swf" id="m1"/>
</mx:Application>

The executing SWF file for the previous example is shown below:

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 modules

You 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 child property. This property points to an instance of the module's class, which lets you call methods and access properties.

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 -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Script><![CDATA[    
       
    ]]></mx:Script>
    <mx:ModuleLoader url="InterModule1.swf" id="m1"/>
    <mx:ModuleLoader url="InterModule2.swf" id="m2"/>
</mx:Application>

The executing SWF file for the previous example is shown below:

Module 1:

<?xml version="1.0"?>
<!-- modules/InterModule1.mxml -->
<mx:Module xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%">
    <mx:Script><![CDATA[    
        // Defines the method that the other module calls.
        public function getNewTitle():String {
            return "New Module Title";
        }    
    ]]></mx:Script>
</mx:Module>

Module 2:

<?xml version="1.0"?>
<!-- modules/InterModule2.mxml -->
<mx:Module xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%">
    <mx:Script><![CDATA[    
        [Bindable]
        private var title:String;
        
        // Call method of another module.
        private function changeTitle():void {
            title = parentApplication.m1.child.getNewTitle();
        }
        
    ]]></mx:Script>
    <mx:HBox>
        <mx:Label id="l1" text="Title: "/>
        <mx:Label id="myTitle" text="{title}"/>
    </mx:HBox>    
    <mx:Button id="b1" label="Change Title" click="changeTitle()"/>    
</mx: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 the 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.

Passing data with the query string

One way to pass data to a module is to append query string parameters to the URL that you use to load the module from. 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 LoaderInfo object of the loading SWF (in this case, the shell 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 -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" height="500" width="400">
    <mx: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;
        }
        
    ]]></mx:Script>

    <mx:Form>
        <mx:FormItem id="fi1" label="First Name:">
            <mx:TextInput id="ti1"/>
        </mx:FormItem>
        <mx:FormItem id="fi2" label="Last Name:">
            <mx:TextInput id="ti2"/>
        </mx:FormItem>  
    </mx:Form>

    <mx:ModuleLoader id="m1"/>
    
    <mx:Button id="b1" label="Submit" click="initModule()"/>

</mx:Application>

The executing SWF file for the previous example is shown below:

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 -->
<mx:Module xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="parseString()">
    <mx: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:
                trace("AS version: " + this.loaderInfo.actionScriptVersion);
                trace("App height: " + this.loaderInfo.height);
                trace("App width: " + this.loaderInfo.width);
                trace("App bytes: " + this.loaderInfo.bytesTotal);
            }
        ]]>
    </mx:Script>
    <mx:Label text="{salutation}"/>
    <mx:TextArea height="100" width="300" id="ta1"/>    
</mx:Module>

This example uses methods of the String and StringUtil classes, plus a for-in loop to parse the URLs. You can also use methods of the URLUtil and URLVariables classes 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 on the URL. This can be useful if you want multiple instances of a module based on the parameters that you pass in the URL to the module loaded.

Using interfaces for module communication

You 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 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 -->
<mx:Application xmlns="*" xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx: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 {
                mod.unloadModule();
                mod.loadModule();            
            }
        ]]>
    </mx:Script>
    
    <mx:Form>
        <mx:FormItem label="Current Module:">
            <mx:Label id="l1" text="{currentModuleName}"/>
        </mx:FormItem>
        <mx:FormItem label="Adjuster ID:">
            <mx:TextInput id="myId" text="Enter your ID"/>
        </mx:FormItem>
        <mx:FormItem label="Background Color:">
            <mx:ColorPicker id="myColor" 
                selectedColor="0xFFFFFF" 
                change="reloadModule()"
            />
        </mx:FormItem>
    </mx:Form>
    
    <mx:Label text="Long Shot Insurance" fontSize="24"/>
    <mx:ComboBox 
        labelField="label" 
        close="selectedItem=ComboBox(event.target).selectedItem"
    >
        <mx:dataProvider>
            <mx:Object label="Select Module"/>        
            <mx:Object label="Auto Insurance" module="AutoInsurance.swf"/>          
        </mx:dataProvider>
    </mx:ComboBox>

    <mx:Panel width="100%" height="100%">
        <mx:ModuleLoader id="mod" 
            width="100%" 
            url="{selectedItem.module}" 
            ready="applyModuleSettings(event)"
        />
    </mx:Panel>    
    <mx:Button id="b1" label="Reload Module" click="reloadModule()"/>
</mx:Application>

The executing SWF file for the previous example is shown below:

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
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 -->
<mx:Module xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%" implements="IModuleInterface">
    
    <mx:Panel id="p1" 
        title="Auto Insurance" 
        width="100%" 
        height="100%" 
        backgroundColor="{bgcolor}"
    >
        <mx:Label id="myLabel" text="ID: {adjuster}"/>
    </mx:Panel>

    <mx: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";
            }
        ]]>
    </mx:Script>
</mx: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.