O'Reilly    
 Published on O'Reilly (http://oreilly.com/)
 See this if you're having trouble printing code examples


Modules, Libraries, Applications, and Portals: Chapter 7 - Enterprise Development with Flex

by Yakov Fain, Anatole Tartakovsky, Victor Rasputnis
Enterprise Development with Flex book cover

This excerpt is from Enterprise Development with Flex. If you want to use Adobe Flex to build production-quality Rich Internet Applications for the enterprise, this groundbreaking book shows you exactly what's required. You'll learn efficient techniques and best practices, and compare several frameworks and tools available for RIA development -- well beyond anything you'll find in Flex tutorials and product documentation. Through many practical examples, the authors impart their considerable experience to help you overcome challenges during your project's life cycle.

buy button

Before software can be reusable, it first has to be usable.

--Ralph Johnson

Flex Portals and Modularization

For many people, the word “portal” stands for content personalization, as in Yahoo! or iGoogle. In the enterprise world, portals are mainly about content aggregation. HTML portals consist of pieces wrapped into HTML tags; Flex portals aggregate Flex applications or modules into a bigger Flex application. Quite naturally, aggregation does not exist without modularization. After all, while developing any decent size application, we tend to break it into smaller, relatively independent parts.

Such intervening of aggregation and modularization determines the layout of this chapter. You’ll start with image loading as the nucleus of Flex modularization, and then progress to Flex modules and subapplications. You’ll learn how to use such classes as Loader and URLLoader and how they deal with style modules and code modules.

This chapter will suggest an approach of creating custom Flex portals that load and communicate with independently built and compiled subapplications: portlets. Finally, you will learn how to integrate existing Flex application as legacy portlets in a JSR 168 portal.

Basic Modularization: Image

The essence of Flex application modularization is dynamic loading of the byte code.

Consider the following two lines of code:

<mx:Image source="@Embed('assets/logo.png')"/>
<mx:Image source="assets/logo.png"/>

The first line illustrates image embedding. It increases the size of the application by the size of the image. As a result, the application carries the image as a part of the SWF file. The loading of such applications takes longer, but the actual rendering of the image will be faster, as there is no need to make a network call just to bring the image to the client.

The second line of code illustrates runtime loading of the image bytes. This time the application’s .swf does not include the image logo.png and loads faster than the embedded one. The download of logo.png will need additional time, but that time will be deferred until the view that contains the image is displayed.

Now consider an alternative, explicit way of image embedding:

<mx:Script>
   <![CDATA[
      [Embed(source="assets/farata_logo.png")]
      [Bindable] private var logoClass:Class;
   ]]>
</mx:Script>

<mx:Image source="{logoClass}"/>
<mx:Button icon="{logoClass}"/>

This method explicitly exposes the variable logoClass of type Class. In fact, the Flex compiler generates an instance of mx.core.BitmapAsset that is a wrapper around the ByteArray of the actual image. The similar variable is generated when you use the @Embed meta tag, although explicit embedding lets you reuse it multiple times. The resource pointed to by the URL, in this case assets/farata_logo.png, gets copied across the network and displayed on the stage. In the case of embedding, copying is done during compilation of the SWF and the job of the Image component is reduced to merely displaying the content of a ByteArray. Importantly, the source property of the Image may outright point to an existing ByteArray representing an image.

You can get a reference to this ByteArray with the help of the class flash.net.URLLoader, as presented in Example 7.1, “Separating transfer of byte code from loading into stage”.

Example 7.1. Separating transfer of byte code from loading into stage

<mx:Script>
   [Bindable] private var imageData:ByteArray;
   private function loadImage():void {
      var urlRequest:URLRequest =  new URLRequest(IMAGE_URL);
      var urlLoader:URLLoader = new URLLoader();
      urlLoader.dataFormat = URLLoaderDataFormat.BINARY;
      urlLoader.addEventListener(Event.COMPLETE, onComplete);
urlLoader.load(urlRequest);
   }
   private function onComplete(event:Event):void{
      var urlLoader:URLLoader = event.target as URLLoader;
      imageData = urlLoader.data as ByteArray;
   }
</mx:Script>

<mx:Button label="Load Image" click="loadImage()" />
<mx:Image id="image" source="{imageData}"/>

The code snippet in Example 7.1, “Separating transfer of byte code from loading into stage” emphasizes that transferring of the remote byte code over the network (by URLLoader) and adding it to the stage (by Image) are two independent actions.

Using this technique for image loading is a good demonstration of two important application modularization concepts:

Once you master loading a single image, you can move up to style modules, which enable you to load many images in one shot.

Runtime Style Modules

Say you have a set of images that collectively, via CSS, determine the skin of your application, as in Example 7.2, “Sample CSS file”.

Example 7.2. Sample CSS file

/* styles.css */
Application {
   background-image:Embed("assets/background.png") ;
   background-size:"100%" ;
}
.arrowLeft {
   skin: Embed("assets/arrow_right.png") ;
   over-skin: Embed("assets/arrow_right_rollover.png") ;
   down-skin: Embed("assets/arrow_right_down.png") ;
}

.arrowRight {
   skin: Embed("assets/arrow_left.png") ;
   over-skin: Embed("assets/arrow_left_rollover.png") ;
   down-skin: Embed("assets/arrow_left_down.png") ;
}

.tileStyle {
   skin: Embed("assets/tile.png") ;
   over-skin: Embed("assets/tile_rollover.png") ;
   down-skin: Embed("assets/tile_rollover.png") ;
}

.minimizeStyle{
   skin: Embed("assets/minimizeall.png") ;
   over-skin: Embed("assets/minimizeall_rollover.png") ;
   down-skin: Embed("assets/minimizeall_rollover.png") ;
}

.restoreStyle {
   skin: Embed("assets/restoreall.png") ;
   over-skin: Embed("assets/restoreall_rollover.png") ;
   down-skin: Embed("assets/restoreall_rollover.png") ;
}

.saveButtonStyle {
   skin: Embed("assets/save_gray.png") ;
   over-skin: Embed("assets/save_rollover.png") ;
   down-skin: Embed("assets/save_rollover.png") ;
}
.showPanelButtonDown {
   skin: Embed("assets/gray_down_small.png") ;
   over-skin: Embed("assets/rollover_down_small.png") ;
   down-skin: Embed("assets/rollover_down_small.png") ;
}
.hidePanels {
   skin: Embed("assets/hide_panels.png") ;
   over-skin: Embed("assets/hide_panels_rollover.png") ;
   down-skin: Embed("assets/hide_panels_rollover.png") ;
}
.showPanels {
   skin: Embed("assets/show_panels.png") ;
   over-skin: Embed("assets/show_panels_rollover.png") ;
   down-skin: Embed("assets/show_panels_rollover.png") ;
}

.controlBarPanelStyle {
   border-style: none ;
   fillColors: #4867a2, #4f75bf ;
   border-skin: ClassReference("border.SimpleGradientBorder");
}

A CSS file can be compiled to the corresponding .swf. To do so via Flash Builder, right-click the filename and select “Compile CSS to SWF.” Now you can dynamically load all required byte code, define classes, create instances, and apply styles to objects that are already present in the display list—all with the single instruction StyleManager.loadStyleDeclarations(), as shown in Example 7.3, “Dynamic style loading via StyleManager”.

Example 7.3. Dynamic style loading via StyleManager

<?xml version="1.0" encoding="utf-8"?>
<!-- RuntimeStyleDemo.mxml -->
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:navigation="com.farata.portal.navigation.*"
layout="absolute"
click="toggleStyles()"
>
<mx:Script>
<![CDATA[
import mx.modules.IModuleInfo;
import mx.modules.ModuleManager;
private function toggleStyles():void {
  var moduleInfo:IModuleInfo = ModuleManager.getModule('styles.swf');
  if (moduleInfo.loaded) {
     StyleManager.unloadStyleDeclarations('styles.swf');
  } else {
     StyleManager.loadStyleDeclarations('styles.swf');
  }
}
]]>
</mx:Script>
<navigation:ControlBar/>
</mx:Application>

The sample application presented in Example 7.3, “Dynamic style loading via StyleManager” allows you to load and unload the compiled stylesheet styles.swf when the user clicks anywhere in the application area. Figure 7.1, “RuntimeStyleDemo with styles.swf loaded (top) and unloaded (bottom)” illustrates the striking difference before and after the styles were loaded.

When developing a portal, you can apply similar styling techniques. If every portlet is styled dynamically, making them conform to the required look and feel is simply a matter of adjusting and recompiling the relevant CSS files. Perhaps the portal owner may even rebuild the CSS module without bothering the creator of the portlet. The portlet itself will not have to be rebuilt to change its appearance.

Example 7.4, “ControlBar of a sample portal” represents the top-level control bar of a sample portal desktop.

Example 7.4. ControlBar of a sample portal

<?xml version="1.0" encoding="utf-8"?>
<!-- com.farata.portal.navigation.ControlBar.mxml -->
<mx:HBox xmlns:mx="http://www.adobe.com/2006/mxml"
    width="100%" height="28"    verticalAlign="middle"
    styleName="controlBarPanelStyle">

   <mx:HBox verticalAlign="middle" horizontalGap="10" paddingLeft="10">
      <mx:Button id="saveButton" height="16" width="16"
         styleName="saveButtonStyle" toolTip="Save Portal"
         useHandCursor="true" buttonMode="true"/>
      <mx:Button id="showTopPanelButton" height="16" width="16"
         styleName="hidePanels" toolTip="Hide/Show Top Panel"
         useHandCursor="true" buttonMode="true"/>
      <mx:Button id="showPanelButton"   height="16" width="16"
         styleName="showPanelButtonDown" toolTip="Show Panel"
         useHandCursor="true" buttonMode="true"/>
   </mx:HBox>
   <mx:HBox width="100%" horizontalAlign="right" paddingRight="5">
      <mx:HBox borderStyle="solid" cornerRadius="13"
                       borderThickness="0" horizontalGap="0" >
         <mx:Button styleName="arrowRight"
            useHandCursor="true" buttonMode="true" />
         <mx:Button styleName="arrowLeft"
            useHandCursor="true" buttonMode="true" />
         <mx:filters>
            <mx:BevelFilter />
            <mx:GlowFilter color="#d3dffd"/>
         </mx:filters>
      </mx:HBox>
      <mx:Button
         styleName="tileStyle" toolTip="Arrange Windows"
      useHandCursor="true" buttonMode="true" />
      <mx:Button styleName="minimizeStyle" toolTip="Minimize All "
         useHandCursor="true" buttonMode="true" />
      <mx:Button styleName="restoreStyle" toolTip="Restore All
         useHandCursor="true" buttonMode="true" />
   </mx:HBox>
</mx:HBox>

Figure 7.1. RuntimeStyleDemo with styles.swf loaded (top) and unloaded (bottom)

RuntimeStyleDemo with styles.swf loaded (top) and unloaded (bottom)

Now you are ready to investigate the most obvious part of the modularization API.

Real Actors: Loader and URLLoader

So far this chapter has touched briefly on the Image, StyleManager, and ModuleManager classes, and equally briefly used ModuleManager. To further your understanding of the modularization techniques, you need to be aware of two important connections:

As the saying goes, all roads lead to Rome, and for your purposes Rome is flash.display.Loader. Be it SWFLoader, ModuleManager, StyleManager (or the similar ResourceManager), modularization is all about loading and unloading classes via flash.display.Loader, the only Flash component that creates class definitions and class instances from the remote URL. In addition, flash.display.Loader can create classes from the existing byte code, for instance, the byte code obtained with the help of flash.net.URLLoader (as illustrated in Example 7.1, “Separating transfer of byte code from loading into stage”).

Loading Modules with Module Loader

The simplest way you can modularize your application is by using Flex modules. The class Module is a VBox that, like Application, is a Container that also gets compiled, along with the dependent classes, to a separate .swf file. Example 7.5, “Example of the module” illustrates a trivial module.

Example 7.5. Example of the module

<?xml version="1.0"?>
<!-SimpleModule.xml -->
<mx:Module xmlns:mx=http://www.adobe.com/2006/mxml layout="vertical">
    <mx:Text text="This is the simplest module" >
</mx:Module>

Any functional part of your application UI that can be developed and tested independently is a good candidate to become a module. The advantages are obvious: you can delegate the development and testing efforts to a different team or allocate a different time slot to it. Modularization will also improve memory utilization, because you can unload the module when the application does not need it anymore.

For Flash Builder to compile your module, it needs to be included into the .actionScriptProperties file of your project. You typically add the module via the project’s properties, as shown in Figure 7.2, “Registration of the module to be compiled by Flash Builder”, or by using the New Module wizard.

Figure 7.2. Registration of the module to be compiled by Flash Builder

Registration of the module to be compiled by Flash Builder

The easiest way to load a module to your application during runtime is via ModuleLoader, a descendant of the VBox that has an extra API to load and unload module SWF files, as shown in Example 7.6, “Loading a module via ModuleLoader”.

Example 7.6. Loading a module via ModuleLoader

<?xml version="1.0"?>
<!-- ModuleLoaderDemo.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
   <mx:HBox>
      <mx:Button label="Load Module"
         click="moduleLoader.loadModule('SimpleModule.swf')" /> 
      <mx:Button label="Unload Module"
         click="moduleLoader.unloadModule()"
         enabled="{moduleLoader.loaderInfo.bytesTotal!=0}"/>
   </mx:HBox>
<mx:ModuleLoader id="moduleLoader"/>
</mx:Application>

As you could figure by now, the ultimate performer of the class loading in the case of the ModuleLoader is, again, flash.display.Loader. Being clear on the role of flash.display.Loader will help you understand other concepts in this chapter.

Preloading Modules with ModuleManager

In addition to ModuleLoader, which is a high-level module API, Flex offers ModuleManager. The prime benefit of using ModuleManager is that you can separate the transfer of the module byte code over the network, which is potentially a lengthy operation, from the actual creation of the module instance(s). Certainly, you could do it yourself with the URLLoader (as illustrated in Example 7.1, “Separating transfer of byte code from loading into stage”), but you should take advantage of the nice abstraction layer provided by the ModuleManager class. In particular, the contract of the ModuleManager guarantees that you won’t transfer the module bytes over the network more than once.

To load a module into a singleton registry of modules provided by ModuleManager, you use a module proxy, such as an implementation of the IModuleInfo interface, corresponding to the module URL. You then perform the load() via this module proxy, as shown in Example 7.7, “Module preloading technique”. The actual loading task will be delegated to flash.display.Loader.

Example 7.7. Module preloading technique

private var moduleInfoRef:Object = {};

private function loadModule(moduleUrl:String):void {
   var moduleInfo:IModuleInfo  = ModuleManager.getModule(moduleUrl);
   moduleInfo.addEventListener(ModuleEvent.READY, onModuleReady ) ;
   //You need to protect moduleInfo from being garbage-collected
   moduleInfoRef[moduleUrl] = moduleInfo;
   moduleInfo.load();
}

// Module is loaded. You may create modules via event.module.factory
private   function onModuleReady(event:ModuleEvent):void {
   // Remove 'protection' from moduleInfo
   moduleInfoRef[event.module.url]=null;
}

The code, similar to the function loadModule(), can be called well in advance of the immediate need of the module. Then, to create an instance of the module, you obtain another instance of the module proxy and use its factory property, as shown in Example 7.8, “Creating an instance of the preloaded module”.

Example 7.8. Creating an instance of the preloaded module

private function createModuleInstance(moduleUrl:String,
parent:UIComponent=null):Module {
   var module:Module;
   var moduleInfo:IModuleInfo  = ModuleManager.getModule(moduleUrl);
   var flexModuleFactory:IFlexModuleFactory = moduleInfo.factory;
   if (flexModuleFactory != null) {
      module = flexModuleFactory.create() as Module;
      if (parent) {
         parent.addChild(module); // in Flex 4 use addElement()
      }
   }
   return module;
}

If this code looks confusing and leaves you wondering what to think of IFlexModuleFactory and where create() comes from, try this: from the Flash Builder project’s Properties, navigate to Flex Compiler, and in the pop-up window add the compiler option -keep in the field Additional Compiler Arguments to see the generated ActionScript code. Then, in the src/generated folder, open the file _SimpleModule_mx_core_FlexModuleFactory.as. The Flex compiler adds an implementation of IFlexModuleFactory for each module, similar to the one shown in the Example 7.9, “Compiler-generated descendant of FlexModuleFactory”.

Example 7.9. Compiler-generated descendant of FlexModuleFactory

package{
public class _SimpleModule_mx_core_FlexModuleFactory
    extends mx.core.FlexModuleFactory
    implements IFlexModuleFactory{
   .  .  .
    override public function create(... params):Object{
        if (params.length > 0 && !(params[0] is String))
            return super.create.apply(this, params);

        var mainClassName:String = params.length == 0 ? "SimpleModule" :
                                                    String(params[0]);
        var mainClass:Class = Class(getDefinitionByName(mainClassName));
        if (!mainClass) return null;

        var instance:Object = new mainClass();
        if (instance is IFlexModule)
            (IFlexModule(instance)).moduleFactory = this;
        return instance;
    }

    override public function info():Object {
        return {
            compiledLocales: [ "en_US" ],
            compiledResourceBundleNames: [ "containers", "core", "effects",
                                                       "skins", "styles" ],
            creationComplete: "onCreationComplete()",
            currentDomain: ApplicationDomain.currentDomain,
            mainClassName: "SimpleModule",
            mixins: [ "_SimpleModule_FlexInit",
"_richTextEditorTextAreaStyleStyle", "_ControlBarStyle",
.  .  .
"_SimpleModuleWatcherSetupUtil" ]
        }
    }
}
}

Finally, to enable the unloading of the module, you need to detach all module instances from their parents. To that end, the example application maintains a Dictionary of loaded modules instances, one per module URL:

[Bindable]private var modules:Dictionary = new Dictionary();

Although this example deals with only one module (SimpleModule.swf), you may upgrade this code to a reusable utility. Then the unloading of the module can be coded like in Example 7.10, “Module unloading technique”.

Example 7.10. Module unloading technique

private function unloadModule(moduleUrl:String):void {
   var moduleInfo:IModuleInfo  = ModuleManager.getModule(moduleUrl);
   if (moduleInfo.loaded) {
      var moduleList:Array = modules[moduleUrl];
      // If more then one module instance was loaded, unload each one 
      for  each(var module:Module in  moduleList) {
          module.parent.removeChild(module);
      }
      delete modules[moduleUrl];
      moduleInfo.unload();
      moduleInfo.release();
   }
   isModuleLoaded = false;
}

Figure 7.3, “ModuleManagerDemo with one instance of the module” illustrates the example application after the creation of one instance of SimpleModule. Example 7.11, “Complete code of ModuleManagerDemo” lists the complete code of the ModuleManagerDemo application.

Figure 7.3. ModuleManagerDemo with one instance of the module

ModuleManagerDemo with one instance of the module

Example 7.11. Complete code of ModuleManagerDemo

<?xml version="1.0" encoding="utf-8"?>
<!-- ModuleManagerDemo.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
   <![CDATA[
   import mx.core.UIComponent;
   import mx.controls.Alert;
   import mx.modules.Module;
   import mx.core.IFlexModuleFactory;
   import mx.modules.IModuleInfo;
   import mx.events.ModuleEvent;
   import mx.modules.ModuleManager;

   private const MODULE_URL:String='SimpleModule.swf';
   private var moduleInfoRef:Object = {};
   [Bindable]private var modules:Dictionary = new Dictionary();

   private function loadModule(moduleUrl:String,
                      applicationDomain:ApplicationDomain=null):void {
      var moduleInfo:IModuleInfo  = ModuleManager.getModule(moduleUrl);
      moduleInfo.addEventListener(ModuleEvent.READY, onModuleReady ) ;
      moduleInfo.addEventListener(ModuleEvent.ERROR, onModuleError ) ;
      moduleInfoRef[moduleUrl] = moduleInfo;
      moduleInfo.load(
         applicationDomain?
             applicationDomain:ApplicationDomain.currentDomain
      );
   }

   private function createModuleInstance(moduleUrl:String,
                                       parent:UIComponent=null):Module {
      var module:Module;
      var moduleInfo:IModuleInfo  = ModuleManager.getModule(moduleUrl);
      var flexModuleFactory:IFlexModuleFactory = moduleInfo.factory;
      if (flexModuleFactory != null) {
         module = flexModuleFactory.create() as Module;
         var moduleList:Array = modules[moduleUrl] ?  modules[moduleUrl] :
                                                          new Array();
         moduleList.push(module);
         modules[moduleUrl] = moduleList;
         if (parent) {
            parent.addChild(module);
         }
      }
      return module;
   }

   [Bindable] private var isModuleLoaded:Boolean=false;
    private   function onModuleReady(event:ModuleEvent):void {
      // Module is loaded. You may create module instances
      //  via event.module.factory (moduleInfo)
      moduleInfoRef[event.module.url]=null;
      isModuleLoaded = true;
   }

   private function onModuleError (event:ModuleEvent):void {
      Alert.show( event.errorText );
   }

   private function unloadModule(moduleUrl:String):void {
      var moduleInfo:IModuleInfo  = ModuleManager.getModule(moduleUrl);
      if (moduleInfo.loaded) {
         var moduleList:Array = modules[moduleUrl];
         for  each(var module:Module in  moduleList) {
             module.parent.removeChild(module);
         }
         delete modules[moduleUrl];
         moduleInfo.unload();
         moduleInfo.release();
      }
      isModuleLoaded = false;
   }
]]>
</mx:Script>

   <mx:HBox>
      <mx:Button label="Load Module" click="loadModule(MODULE_URL)" />
      <mx:Button label="Instantiate Module"
         click="createModuleInstance(MODULE_URL, this)"
         enabled="{isModuleLoaded}"/>
      <mx:Button label="Unload Module"
         click="unloadModule(MODULE_URL)"
         enabled="{isModuleLoaded}"/>
  </mx:HBox>
</mx:Application>

Note that Example 7.11, “Complete code of ModuleManagerDemo” applies the concept of application domains:

moduleInfo.load(
   applicationDomain?applicationDomain:ApplicationDomain.currentDomain
);

You’ll learn about domains a bit later in this chapter. For now, suffice it to say that the code loads module classes into the same area (in memory) where the classes of the calling applications were loaded.

Whether via ModuleLoader or ModuleManager, you have loaded your module. How will the application communicate with it?

Communicating with Modules

You’ve designed your modules to be independent, but there should be provisions to allow external applications to communicate with them, pass them some information and receive response notifications. From the user’s point of view, it may look like an innocent drag-and-drop action, but internally you must resort to one of the several available means of communication. We will start with direct references to the module variables and methods.

First, consider the method-based interfaces. We’ll assume that you have the IGreeting interface, as shown in Example 7.12, “IGreeting interface”.

Example 7.12. IGreeting interface

//IGreeting.as
package
{
   public interface IGreeting {
      function getGreeting():String;
      function setGreeting( value:String ):void;
   }
}

Further, suppose that a module, such as ModuleWithIGreeting in Example 7.13, “Example of a module implementing the IGreeting interface”, is implementing this interface. Please notice that calling setGreeting() will modify the bindable variable greeting that affects the title of the module’s panel.

Example 7.13. Example of a module implementing the IGreeting interface

<?xml version="1.0"?>
<!- ModuleWithIGreeting.mxml -->
<mx:Module xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="*"
   implements="IGreeting"
   creationComplete="onCreationComplete()"
>
 <mx:Script>
<![CDATA[
   [Bindable] private var greeting:String="";

   public function setGreeting(value:String):void {
      greeting = value;
   }
   public function getGreeting():String {
      return greeting;
   }
]>
 </mx:Script>
   <mx:Panel id="panel" title="Module With Greeting{greeting}" width="400"
height="200">
   </mx:Panel>
</mx:Module>

How can your application take advantage of the fact that the loaded module implements a known interface? Assuming that it has used a ModuleLoader, as the following snippet shows, you can cast its child property to the IGreeting interface:

var greeting:IGreeting = moduleLoader.child as IGreeting;
greeting.setGreeting(" loaded by application");

Then again, no one prevents you from simply referencing the panel from ModuleWithIGreeting by name:

var module:Module = moduleLoader.child as Module;
var panel:Panel = module.getChildByName("panel") as Panel;
trace(panel.title); //Simple Module  loaded by application

The complete ReferenceCommunicationDemo application is presented in Example 7.14, “ReferenceCommunicationDemo application”.

Example 7.14. ReferenceCommunicationDemo application

<?xml version="1.0"?>
<!-- ReferenceCommunicationDemo.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
   import mx.modules.Module;
   import mx.containers.Panel;

   private const MODULE_URL:String="ModuleWithIGreeting.swf";

   private function modifyLoadedContent():void {
      var greeting:IGreeting = moduleLoader.child as IGreeting;
      greeting.setGreeting(" loaded by application");

      var module:Module = moduleLoader.child as Module;
      var panel:Panel = module.getChildByName("panel") as Panel;
      trace(panel.title); //Simple Module  loaded by application
}
]]>
</mx:Script>
   <mx:HBox>
      <mx:Button label="Load Module"
         click="moduleLoader.loadModule(MODULE_URL)" />
      <mx:Button label="Modify Content"
         click="modifyLoadedContent()"/>
      <mx:Button label="Unload Module"
         click="moduleLoader.unloadModule()"
         enabled="{moduleLoader.loaderInfo.bytesTotal!=0}"/>
   </mx:HBox>

   <mx:ModuleLoader id="moduleLoader"/>
</mx:Application>

This application has three buttons labeled Load Module, Modify Content, and Unload Module (Figure 7.4, “ReferenceCommunicationDemo”), each associated with a similarly named function. This separation of functions enables you to profile the application and verify that there is no memory leak associated with module unloading.

Figure 7.4. ReferenceCommunicationDemo

ReferenceCommunicationDemo

Although this interface-based method of working with modules is appealing, use it with care: it uses direct references to the modules, and any unreleased direct reference will indefinitely lock your module in memory. Against this backdrop, the elegance of the interfaces does not matter much.

The best way to make sure you do not have unreleased references is to avoid them to begin with. Instead, use events to communicate with the loaded modules. To do so, you need an EventDispatcher that can be commonly accessed by the module and the loading application (here’s yet another example of the Mediator design pattern from Chapter 2, Selected Design Patterns). One object that suits the task particularly well is sharedEvents, accessible as loader.loaderInfo.sharedEvents from the module and loading application as well.

The complete code of the sample application EventCommunicationDemo is presented in Example 7.15, “EventCommunicationDemo application”. Note that in the loadModule(), you subscribe to Event.COMPLETE to be sent by the modules upon loading and creating the module’s display list. Then the onComplete() handler application itself sends an event to the module. The module, as you will see soon, interprets this event to modify a panel’s header.

Example 7.15. EventCommunicationDemo application

<?xml version="1.0"?>
<!-- EventCommunicationDemo.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
   import mx.events.DynamicEvent;
   import mx.controls.Alert;
   import mx.events.ModuleEvent;
   import mx.modules.Module;

   private const MODULE_URL:String="ModuleWithEvents.swf";
   [Bindable] private var moduleLoaded:Boolean;

   private function loadModule():void {
      // Subscribe to notifications from the module
      var sharedEventDispatcher:IEventDispatcher =
         moduleLoader.loaderInfo.sharedEvents;
      sharedEventDispatcher.addEventListener(
         Event.COMPLETE, onModuleCreated
      );
      moduleLoader.loadModule(MODULE_URL);
      moduleLoaded = true;
   }

   // This event "comes" from the module
   private function onModuleCreated(event:Event):void {
      trace("Module CreateComplete happened");
      //Send commands to the module
      var sharedEventDispatcher:IEventDispatcher =
         moduleLoader.loaderInfo.sharedEvents;
      var dynamicEvent:DynamicEvent = new DynamicEvent("command");
      dynamicEvent.data = " Two-way talk works!";
      sharedEventDispatcher.dispatchEvent(dynamicEvent);
   }
   private function unloadModule():void {
      moduleLoader.unloadModule();
      moduleLoaded = false;
   }

]]>
</mx:Script>
   <mx:HBox>
      <mx:Button label="Load Module" click="loadModule()" />
      <mx:Button label="Unload Module"   click="unloadModule()"
         enabled="{moduleLoaded}"/>
     </mx:HBox>

   <mx:ModuleLoader id="moduleLoader"/>
</mx:Application>

Example 7.16, “Counterpart module example to EventCommunicationDemo” presents the corresponding module sample ModuleWithEvents. Notice the handler of the creationComplete event. It subscribes to the command events sent by the application and notifies the application that the module is ready for receiving such events by dispatching Event.COMPLETE.

The syntax of addEventListener() specifies weak reference, because strong reference to the sharedEventDispatcher would prevent the module from being garbage-collected. If you run the application and click on the button Load Module, you will see the screen shown in Figure 7.5, “EventCommunicationDemo application”.

The panel’s header will read “Module With Events. Two-way talk works!” to emphasize the fact that the application and the module exchange events in both directions. You may want to actually profile the application and watch how referencing of the event listener (weak versus strong) dramatically affects the ability to unload the module.

Example 7.16. Counterpart module example to EventCommunicationDemo

<?xml version="1.0"?>
<!- ModuleWithEvents.mxml -->
<mx:Module xmlns:mx="http://www.adobe.com/2006/mxml"
   creationComplete="onCreationComplete()"
   >
 <mx:Script>
   <![CDATA[
   import mx.events.DynamicEvent;

   [Bindable] private var command:String="";
   private function onCreationComplete():void {
      var sharedEventDispatcher:IEventDispatcher =
         systemManager.loaderInfo.sharedEvents
      //Subscribe to command from the application
      sharedEventDispatcher.addEventListener(
         "command", onCommand,false,0,true
      ); //Strong reference would lock the module to application

      // Notify the applications that creation has completed
sharedEventDispatcher.dispatchEvent(new Event(Event.COMPLETE)
      );
   }

   private function onCommand(event:DynamicEvent):void {
       command = event.data as String;
   }
]]>
</mx:Script>
   <mx:Panel id="panel" title="Module With Events. {command}" width="400"
    height="200"/>
</mx:Module>

Figure 7.5. EventCommunicationDemo application

EventCommunicationDemo application

Introducing Application Domains

You’re packing for the snorkeling trip with your kid. Into your travel bag you put the two new pairs of goggles you bought just yesterday. Meanwhile, your small one found two old pairs in the garage and placed them in his backpack. You arrive to the beach with two sets of goggles. Which ones are you going to use?

Now, if we replace the travel bag with a parent application domain, your kid’s backpack with a child application domain, and start discussing class definitions instead of goggles, the only choice you are going to get is #3, or “delegate to your parent.”

Classes get loaded into application domains, which form a tree. By default, a module’s classes get loaded into the child domain (of the application or parental module). The child has access to all classes in the parental chain. This means that a module can create all the classes the application can (your kid can use your goggles).

On the contrary, the application does not get access to the classes carried by the module (you are not allowed to open your kid’s backpack), and the child can’t reload the class already known to the parent (your goggles are the only ones your kid gets to use).

The application ModuleDomainDemo illustrates this concept. Its ModuleLoader has an applicationDomain property set to a bindable expression that depends on the user-controlled radio button:

<mx:ModuleLoader id="moduleLoader"
    applicationDomain="{
       same_domain.selected?
       ApplicationDomain.currentDomain:
       new ApplicationDomain(ApplicationDomain.currentDomain)
   }"
/>

Note

For the complete code of ModuleDomainDemo, see Example 7.19, “Complete code of ModuleDomainDemo” (a bit later).

The subexpression ApplicationDomain.currentDomain refers to the domain that the very code containing this expression belongs to. In the example’s case, it means the domain that keeps the class definitions of the application itself. At the same time, the expression new ApplicationDomain(ApplicationDomain.currentDomain) refers to the child of that domain. These are two alternative application domain settings when you are loading the modules: the same domain or a child domain (default). The module that you are going to load is a slightly modified version of the SimpleModule you used earlier: it explicitly links in the CustomGrid control, as shown in Examples 7.17 and 7.18.

Example 7.17. SimpleModule with linked-in CustomGrid component

<?xml version="1.0"?>
<!-- SimpleModule -->
<mx:Module xmlns:mx="http://www.adobe.com/2006/mxml"
   ><mx:Script>
      <![CDATA[
         CustomGrid; //Needed only for ModuleDomainDemo
      ]]>
   </mx:Script>
     <mx:Panel id="panel" title="Simple Module" width="400" height="200">
    </mx:Panel>
</mx:Module>

Example 7.18. CustomGrid component

<?xml version="1.0" encoding="utf-8"?>
<!-- CustomGrid.mxml -->
<mx:DataGrid xmlns:mx="http://www.adobe.com/2006/mxml">
   <mx:columns>
      <mx:Array>
         <mx:DataGridColumn  dataField="name" headerText="Name" width="150"/>
         <mx:DataGridColumn  dataField="phone" headerText="Phone"/>
      </mx:Array>
   </mx:columns>
</mx:DataGrid>

The application attempts dynamic creation of the CustomGrid, purely by class name. To obtain the class definition from the current application domain, use the loaderInfo property shared by all display objects:

var clazz:Class =
   loaderInfo.applicationDomain.getDefinition("CustomGrid") as Class;
dg  = DataGrid(new clazz());

Run the application and make sure that the radio button Same Domain is selected. This means that classes will get loaded into the ApplicationDomain.currentDomain. In other words, you have allowed your kid to put his things into your bag (it’s a “MiracleCompactPro” bag, all right, because it does not accept the same article twice). Click Load Module and then click Create Custom Grid. The application will look as shown in Figure 7.6, “ModuleDomainDemo: loading the module to the same domain”. The application (not the module!) has created DataGrid using the class from the module’s .swf.

Restart the application and load the module with the radio button Child Domain selected. The application won’t be able to create the CustomGrid. It’s out of the application’s reach now, because you loaded modules classes in the isolated child application domain (Figure 7.7, “ModuleDomainDemo: loading the module to the child domain”).

By no means are we suggesting the use of modules instead of the libraries, as far as reusable resources are concerned (we discuss libraries in the next section). Example 7.19, “Complete code of ModuleDomainDemo”, ModuleDomainDemo.mxml, merely illustrates the class isolation provided by the application domains. That said, if you find yourself loading your modules into the same domain—you’ve got company! Provided you use careful class naming, this is a viable alternative to child domains.

Figure 7.6. ModuleDomainDemo: loading the module to the same domain

ModuleDomainDemo: loading the module to the same domain

Figure 7.7. ModuleDomainDemo: loading the module to the child domain

ModuleDomainDemo: loading the module to the child domain

Example 7.19. Complete code of ModuleDomainDemo

<?xml version="1.0"?>
<!-- ModuleDomainDemo.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml">
<mx:Script>
<![CDATA[
   import mx.controls.Alert;
   import mx.controls.DataGrid;

   private const  MODULE_URL:String = "SimpleModule.swf";
   [Bindable] private var moduleLoaded:Boolean;

   private var dg:DataGrid;
   private function createCustomGrid():void {
      try {
         var clazz:Class =
         loaderInfo.applicationDomain.getDefinition("CustomGrid") as Class;
      } catch (error:ReferenceError) {
         Alert.show ("Definition of 'CustomGrid' class can not be found
          in the current domain of the application ","Class Not Found Error");
         return;
      }
      dg  = DataGrid(new clazz());
      dg.dataProvider = [
         {name:"Anatole Tartakovsky", phone:"5618325611"},
         {name:"Victor Rasputnis", phone:"7184017234"},
         {name:"Yakov Fain",phone:"7322342654"}
      ];
      addChild(dg);
   }

   [Bindable] private var moduleLoaded:Boolean;
   private function loadModule():void {
      moduleLoader.loadModule(MODULE_URL);
      moduleLoaded=true;
   }

   private function unloadModule():void {
      removeChild(dg); // Remove references to the module
      dg = null;
      moduleLoader.unloadModule();
      moduleLoaded=false;
   }   ]]>
   </mx:Script>
   <mx:VBox>
      <mx:HBox>
         <mx:RadioButton groupName="domain" label="Same Domain"
         id="same_domain" selected="true" enabled="{!moduleLoaded}"/>
         <mx:RadioButton groupName="domain" label="Child Domain"
                       id="child_domain" enabled="{!moduleLoaded}"/>
      </mx:HBox>

      <mx:HBox>
         <mx:Button label="Load Module" click="loadModule(MODULE_URL) "  />
         <mx:Button label="Create Custom Grid" click="createCustomGrid()" />
         <mx:Button label="Unload Module" click="unloadModule()"
         enabled="{moduleLoaded}"/>
     </mx:HBox>
   </mx:VBox>

    <mx:ModuleLoader id="moduleLoader"
        applicationDomain="{
           same_domain.selected?
           ApplicationDomain.currentDomain:
           new ApplicationDomain(ApplicationDomain.currentDomain)
       }"
    />

</mx:Application>

Paying Tribute to Libraries

If you need to modularize reusable components, look no further than libraries: Runtime Shared Libraries (RSL), to be specific. Assuming that you are using Flash Builder, the basic procedure is:

  1. Create a Flex Library project containing classes to be reused (call it, say, ComponentLibrary).

  2. Add a mapping to this project to the Flex Build Path of the application(s) that makes use of the library classes.

If you do not have the source code, add a mapping to the SWC file of the library compiled by a third party instead of to the library project. Look in the Flex Build Path of your application: all Flex framework classes are added via several .swc files, similar to Figure 7.8, “Default link type: merge into code”.

Figure 7.8. Default link type: merge into code

Default link type: merge into code

At this configuration level, library projects merely separate development of the business application from building of the reusable components; however, your application is still built as monolithic .swf. Why? Because when you add mapping to the library project or .swc of the compiled library, the default link type is “Merged into code.” This is static linking, where the application .swf contains only those classes it could determine as required at compile time. Recall the dynamic instantiation from Example 7.19, “Complete code of ModuleDomainDemo”:

var clazz:Class =
   loaderInfo.applicationDomain.getDefinition("CustomGrid") as Class;
dg  = DataGrid(new clazz());

Assuming the CustomGrid class belongs to ComponentLibrary, under “Merged into code,” this dynamic instantiation will not work, because definition of the CustomGrid will not become a part of the application .swf.

If you want to reference CustomGrid explicitly, you may add the following line to your application:

import CustomGrid; CustomGrid;

Alternatively, you may add -includes CustomGrid to the compiler options.

Either way, you are not using the library (RSL), you’re only creating a monolithic SWF via a library project. To use the RSL, change the link type to “Runtime shared library.” Figure 7.9, “RSL link type defaults to autoextraction of the RSL SWF” shows one way to do it, with the option “Automatically extract swf to deployment” turned on. What this really means is that the SWF of the library (RSL) will be created on each compile of the application. (You’ll learn about the opposite setting of this option later in the chapter.)

Figure 7.9. RSL link type defaults to autoextraction of the RSL SWF

RSL link type defaults to autoextraction of the RSL SWF

According to Figure 7.9, “RSL link type defaults to autoextraction of the RSL SWF”, after building an application that is mapped to the ComponentLibrary (Flex Library) project, you will find ComponentLibrary.swf in the output folder.

Now your application is using an RSL. To be precise, the compiler-generated code will have flash.display.Loader (what else?) preload the classes of the RSL .swf into ApplicationDomain.currentDomain. In other words, the default application domain setting for libraries is the same domain as the application (same bag for you and your kid).

The application .swf gets smaller, because it does not carry the footprint of any of the library classes, whether statically required or not. That said, you incurred extra .swf content: the library itself. If you are developing an intranet application, the size does not matter much. Additionally, if you are deploying for extranet use, recall that library .swf files get cached in the browser cache per domain.

On top of that, as far as Flex framework RSLs are concerned, the latest releases of Flash Player 9 and Flash Player 10 support Adobe-signed RSLs that get cached by Flash Player; these .swf files are cached across different server domains.

RSLs: “Under”-Libraries

Unfortunately, RSLs fail to deliver on the promise of dynamic linking. As it turns out, a SWF of the RSL itself does not contain all the code that the RSL requires to function. The complementary part is generated by the Flex compiler as part of the application’s (or module’s) bootstrap. That’s not all.

Besides dependency of an RSL SWF on the application’s bootstrap, the very bootstrap is totally ignoring any library class that the application does not reference statically. As a result, dynamic instantiation of RSL-based classes fails.

This section demonstrates the problem. If you are looking for the immediate solution, skip to the section the section called “Bootstrapping Libraries As Applications”.

Here you will create a Flex library project, ComponentLibrary, with a single component, CustomPanel (Example 7.20, “CustomPanel, to be dynamically loaded by LibraryDemo”).

Example 7.20. CustomPanel, to be dynamically loaded by LibraryDemo

<!-- com.farata.samples.CustomPanel.mxml -->
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml"
   title="'Custom' Panel #{instanceNumber}"
   width="300" height="150"
   creationComplete="instanceNumber=++count;"
>
   <mx:Script>
      public static var count:int;
      [Bindable] private var instanceNumber:int;
   </mx:Script>
</mx:Panel>

The example application, LibraryDemo, will merely attempt to dynamically create instances of the CustomPanel using applicationDomain.getDefinition(), as shown in Example 7.21, “LibraryDemo dynamically loads CustomPanel”.

Example 7.21. LibraryDemo dynamically loads CustomPanel

<!-- LibraryDemo -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
   layout="vertical"
>
   <mx:Button label="CreatePanel"
click="createComponent('com.farata.samples.CustomPanel')"/>
   <mx:Script>
   <![CDATA[
      //import mx.containers.Panel;Panel; // Make sure this is commented out

      private var displayObject:DisplayObject;
      private function createComponent(componentName:String) : void {
         var clazz : Class =
loaderInfo.applicationDomain.getDefinition(componentName) as Class;
         displayObject = DisplayObject(new clazz() );
         addChild(displayObject);
      }
   ]]>
   </mx:Script>
</mx:Application>

To test the application, add the ComponentLibrary project to the Flex Build Path of the application project, as shown in Figure 7.9, “RSL link type defaults to autoextraction of the RSL SWF”. Now, if you run the application and click Create Panel, the application will crash, as shown in Figure 7.10, “LibraryDemo fails to dynamically create CustomPanel”.

Figure 7.10. LibraryDemo fails to dynamically create CustomPanel

LibraryDemo fails to dynamically create CustomPanel

If, however, you uncomment this line:

//import mx.containers.Panel;Panel;

the application will run successfully, as shown in Figure 7.11, “If you link in the Panel class, LibraryDemo works well”.

Consider the problem. Debugging the application reveals that the null pointer error happens because of an uninitialized instance variable of the Panel class: titleBarBackground. The corresponding snippet of the Panel.as is presented in Example 7.22, “First snippet of Panel.as”. At the time of the crash, the titleBarBackground class is null.

Example 7.22. First snippet of Panel.as

override protected function layoutChrome(unscaledWidth:Number,
                        unscaledHeight:Number):void
{
   super.layoutChrome(unscaledWidth, unscaledHeight);
   .  .  .
      titleBarBackground.move(0, 0);
      .  .  .
}

Figure 7.11. If you link in the Panel class, LibraryDemo works well

If you link in the Panel class, LibraryDemo works well

Following the lead, in the same Panel.as you will discover that the value of titleBarBackground is dependent on dynamic instantiation of titleBackgroundSkin (Example 7.23, “Second snippet of Panel.as”).

Example 7.23. Second snippet of Panel.as

var titleBackgroundSkinClass:Class = getStyle("titleBackgroundSkin");

if (titleBackgroundSkinClass){
     titleBarBackground = new titleBackgroundSkinClass();
.  .  .

Because you did not do anything beyond linking in the Panel to make the LibraryDemo application work, the difference between the working application and the buggy one must be in the generated code. Specifically, the difference is in the compiler-generated descendant of SystemManager, _LibraryDemo_mx_managers_SystemManager, which is the main application class.

The code of the nonworking application is presented in Example 7.24, “Compiler-generated SystemManager for the LibraryDemo (nonworking version)”. Note that the class implements IFlexModuleFactory again. You came across this interface first during the discussion of loading modules with ModuleManager. At that time, you learned that modules get bootstrapped by classes implementing IFlexModuleFactory interface (see Example 7.9, “Compiler-generated descendant of FlexModuleFactory”). As you see now, the same technique works with applications.

Also note the currentDomain and rsls properties of the object returned by the info() method. This rsls property contains the url of the ComponentLibrary.swf that will be loaded in the current domain of the application.

And last, compare the mixins array with Example 7.25, “mixins array from the compiler-generated SystemManager for the working version of the LibraryDemo”, which presents the second version of the mixins array—this time taken from the working application (the one where you force linking in of the Panel class). This is the only place where two applications are different! And the only two lines that make this difference mention _ControBarStyle and _Panel mixins classes. FYI: the mixins class is a helper class with the method initialize(baseObject).

Example 7.24. Compiler-generated SystemManager for the LibraryDemo (nonworking version)

// Compiler-generated SystemManager for the LibraryDemo
package
{

import . . .

[ResourceBundle("containers")]
[ResourceBundle("core")]
[ResourceBundle("effects")]
[ResourceBundle("skins")]
[ResourceBundle("styles")]
public class _LibraryDemo_mx_managers_SystemManager
    extends mx.managers.SystemManager
    implements IFlexModuleFactory
{
    public function _LibraryDemo_mx_managers_SystemManager() {
        super();
    }

    override     public function create(... params):Object {
        if (params.length > 0 && !(params[0] is String))
            return super.create.apply(this, params);

        var mainClassName:String = params.length == 0 ? "LibraryDemo" :
                                      String(params[0]);
        var mainClass:Class = Class(getDefinitionByName(mainClassName));
        if (!mainClass)
            return null;

        var instance:Object = new mainClass();
        if (instance is IFlexModule)
            (IFlexModule(instance)).moduleFactory = this;
        return instance;
    }

    override    public function info():Object {
        return {
            compiledLocales: [ "en_US" ],
            compiledResourceBundleNames: [ "containers", "core", "effects",
           "skins", "styles" ],
            currentDomain: ApplicationDomain.currentDomain,
            layout: "vertical",
            mainClassName: "LibraryDemo",
            mixins: [ "_LibraryDemo_FlexInit", "_richTextEditorTextAreaStyleStyle",
"_alertButtonStyleStyle", "_textAreaVScrollBarStyleStyle", "_headerDateTextStyle",
"_globalStyle", "_todayStyleStyle", "_windowStylesStyle", "_ApplicationStyle",
"_ToolTipStyle", "_CursorManagerStyle", "_opaquePanelStyle", "_errorTipStyle",
"_dateFieldPopupStyle", "_dataGridStylesStyle", "_popUpMenuStyle",
"_headerDragProxyStyleStyle", "_activeTabStyleStyle", 
"_ContainerStyle", "_windowStatusStyle", "_ScrollBarStyle",
"_swatchPanelTextFieldStyle", "_textAreaHScrollBarStyleStyle", "_plainStyle",
"_activeButtonStyleStyle", "_advancedDataGridStylesStyle", "_comboDropdownStyle",
"_ButtonStyle", "_weekDayStyleStyle", "_linkButtonStyleStyle" ],
            rsls: [{url: "ComponentLibrary.swf", size: -1}]

        }
    }
}

}

Example 7.25. mixins array from the compiler-generated SystemManager for the working version of the LibraryDemo

mixins: [ "_LibraryDemo_FlexInit", "_richTextEditorTextAreaStyleStyle",
"_ControlBarStyle",
"_alertButtonStyleStyle", "_textAreaVScrollBarStyleStyle", "_headerDateTextStyle",
"_globalStyle", "_todayStyleStyle", "_windowStylesStyle", "_ApplicationStyle",
"_ToolTipStyle", "_CursorManagerStyle", "_opaquePanelStyle", "_errorTipStyle",
"_dateFieldPopupStyle", "_dataGridStylesStyle", "_popUpMenuStyle",
"_headerDragProxyStyleStyle", "_activeTabStyleStyle",
"_PanelStyle",
"_ContainerStyle", "_windowStatusStyle", "_ScrollBarStyle",
"_swatchPanelTextFieldStyle", "_textAreaHScrollBarStyleStyle", "_plainStyle",
"_activeButtonStyleStyle", "_advancedDataGridStylesStyle", "_comboDropdownStyle",
"_ButtonStyle", "_weekDayStyleStyle", "_linkButtonStyleStyle" ]

MXML applications are, by design, two-phased. The first phase is the bootstrap (the first frame of the Flex application or Flex module .swf). At this time, the application preloads the RSLs and manipulates support classes generated by the compiler, such as mixins. In this example’s case, not knowing about Panel made the Flex compiler omit the creation and use of _ControlBarStyle and _PanelStyle mixins, which in turn lead to an uninitialized titleBackgroundSkin and, finally, a reference error in the panel’s layoutChrome(). All in all, there are two problems:

  • RSLs are not quite reusable libraries. They are “under”-libraries that require bootstrap support from the loading .swf.

  • The bootstrap code generated by the Flex compiler fails to support classes that your application (or module) is referencing dynamically.

Now that we’ve admitted the problems, the rest is technicality.

Bootstrapping Libraries As Applications

Step back a little and consider Flex library projects, or more specifically, library .swc files. At the end of the day, when you link your application with the library, you link it with the .swc, whether made from sources in a library project or obtained from a third party.

If you recall, Figure 7.9, “RSL link type defaults to autoextraction of the RSL SWF” included the option “Automatically extract swf to deployment path.” Being an option, it underscores the two missions of the SWC. The critical mission is to resolve the compile-time references for the application. The optional mission is to begin autoextracting the RSL SWF.

Here comes the big idea: do not rely on the automatically extracted library SWF, because it’s incomplete, and do not trust the bootstrap from the application SWF, because the application does not necessarily know about all library classes. Instead, purposely create this knowing application yourself, merge it with the library classes, and give it the same name as the SWF of the library that otherwise would have been autoextracted. In other words, say “no” to autoextraction. Replace it with the custom compilation of the library as a fully bootstrapped application. Doing so changes nothing in how the main application gets compiled, but it no longer relies on bootstrap generation for the main application. Copy the custom-compiled library into the deployment folder, and when the main application loads the library (for instance, ComponentLibrary.swf), it will not know that it is loading a self-sufficient, custom-compiled SWF instead of the immature, autoextracted one.

Example 7.26, “Example of bootstrapping the library as SimpleApplication to consolidate compiler-generated and manual code in one SWF” contains the example of the ComponentLibrary_Application class that is added to the library project to bootstrap the library. Notice the static reference to the CustomPanel: it is your responsibility to add such references as import com.farata.samples.CustomPanel; CustomPanel; to the body of the ComponentLibrary_Application class whenever you add new components to the library. Importantly, all these references stay encapsulated in the library itself. This library will not need outside help to guarantee the success of the dynamic calls.

Example 7.26. Example of bootstrapping the library as SimpleApplication to consolidate compiler-generated and manual code in one SWF

// ComponentLibrary_Application.as
// Example of Library bootstrapped as SimpleApplication
// Libraries created this way do not have problems with dynamic class references
package {
   import mx.core.SimpleApplication;

   public class ComponentLibrary_Application extends SimpleApplication {

      import com.farata.samples.CustomPanel; CustomPanel;

      public function ComponentLibrary_Application() {
         // Custom library initialization code should go here
         trace("ComponentLibrary_Application.swf has been loaded and initialized");
      }

   }
}

Example 7.27, “MXML extension of the bootstrap to force MXML compiler into code generation” contains the example of the ComponentLibrary_Bootstrap.mxml class derived from the ComponentLibrary_Application.

Example 7.27. MXML extension of the bootstrap to force MXML compiler into code generation

<?xml version="1.0" encoding="UTF-8"?>
<!-- ComponentLibrary_Bootstrap.mxml
   By wrapping ComponentLibrary_Application into MXML tag, we
   force Flex compiler to create all mixins required by the
   library  classes  (in the generated bootstrap class)
-->
<ComponentLibrary_Application xmlns="*" />

This extra step up to MXML is required to trick the Flex compiler into generating its own bootstrap class (the code of that class is shown in Example 7.30, “Compiler-generated main class for the bootstrapped library”). Finally, Example 7.28, “Ant script that compiles ComponentLibrary_Bootstrap.mxml” contains the example of the Ant script that can be used to compile the SWF of the self-initializing library.

Example 7.28. Ant script that compiles ComponentLibrary_Bootstrap.mxml

<project name="Library-Application" default="compile" basedir="." >
   <target name="compile">
      <property name="sdkdir" value="C:/Program Files/Adobe/Flash Builder 3 Plug-
       in/sdks/3.2.0" />
      <property name="swclibs" value="${sdkdir}/frameworks/libs"   />
      <property name="application.name" value="ComponentLibrary_Bootstrap" />
      <property name="library.name" value="ComponentLibrary" />
      <exec executable="${sdkdir}/bin/mxmlc.exe" dir="${basedir}">
            <arg line="-external-library-
             path='${swclibs}/player/9/playerglobal.swc'"/>
            <arg line="-keep-generated-actionscript=true "/>
            <arg line="src/${application.name}.mxml"/>
            <arg line="-output bin/${library.name}.swf"/>
      </exec>
   </target>
</project>

When you run this script in Flash Builder, you will see output similar to that of Example 7.29, “Output of the Ant script compiling library-bootstrapped-as-application”.

Example 7.29. Output of the Ant script compiling library-bootstrapped-as-application

Buildfile: C:\workspaces\farata.samples\ComponentLibrary\build.xml
compile:
     [exec] Loading configuration file C:\Program Files\Adobe\Flash Builder 3
            Plug-in\sdks\3.2.0\frameworks\flex-config.xml
     [exec] C:\workspaces\farata.samples\ComponentLibrary\bin\ComponentLibrary.swf
            (181812 bytes)
BUILD SUCCESSFUL
Total time: 5 seconds

Make sure you copy ComponentLibrary.swf into the output folder of your application project and do not forget to turn off the autoextraction of the SWF, as shown in Figure 7.12, “Autoextraction of the RSL SWF is turned off to avoid overwriting the custom-compiled library”.

Figure 7.12. Autoextraction of the RSL SWF is turned off to avoid overwriting the custom-compiled library

Autoextraction of the RSL SWF is turned off to avoid overwriting the custom-compiled library

Congratulations! You just created a bulletproof Flex RSL. If you are a practitioner, your job is complete. If you are a researcher, however, you may want to look at Example 7.30, “Compiler-generated main class for the bootstrapped library”, which is the bootstrap class generated by the Flex compiler in response to this Ant-based compilation. Notice it contains yet another implementation of the IFlexModuleFactory interface. In response to the base class being flex.core.SimpleApplication, the compiler generates a descendant of mx.core.FlexApplicationBootstrap (as opposed to mx.managers.SystemManager, which is being generated in response to mx.core.Application). Upon the load of the library’s SWF, Flash will instantiate the ComponentLibrary_Bootstrap_mx_core_FlexApplicationBootstrap class. The construction of the superclass results in calling the create() method, which consumes the return of the method info(). This way, the library bootstrap is completely owned and controlled by the library itself.

Example 7.30. Compiler-generated main class for the bootstrapped library

// Compiler-generated descendant of the FlexApplicationBootstrap
package
{

import flash.text.Font;
import flash.text.TextFormat;
import flash.system.ApplicationDomain;
import flash.utils.getDefinitionByName;
import mx.core.IFlexModule;
import mx.core.IFlexModuleFactory;

import mx.core.FlexApplicationBootstrap;

[ResourceBundle("containers")]
[ResourceBundle("core")]
[ResourceBundle("effects")]
[ResourceBundle("skins")]
[ResourceBundle("styles")]
public class _ComponentLibrary_Bootstrap_mx_core_FlexApplicationBootstrap
    extends mx.core.FlexApplicationBootstrap
    implements IFlexModuleFactory
{
    public function _ComponentLibrary_Bootstrap_mx_core_FlexApplicationBootstrap()
    {

        super();
    }

    override     public function create(... params):Object
    {
        if (params.length > 0 && !(params[0] is String))
            return super.create.apply(this, params);

        var mainClassName:String = params.length == 0 ?
                  "ComponentLibrary_Bootstrap" : String(params[0]);
        var mainClass:Class = Class(getDefinitionByName(mainClassName));
        if (!mainClass)
            return null;

        var instance:Object = new mainClass();
        if (instance is IFlexModule)
            (IFlexModule(instance)).moduleFactory = this;
        return instance;
    }

    override    public function info():Object{
        return {
            compiledLocales: [ "en_US" ],
            compiledResourceBundleNames: [ "containers", "core", "effects",
                                                     "skins", "styles" ],
            currentDomain: ApplicationDomain.currentDomain,
            mainClassName: "ComponentLibrary_Bootstrap",
            mixins: [ "_ComponentLibrary_Bootstrap_FlexInit",
"_richTextEditorTextAreaStyleStyle",
"_ControlBarStyle",
"_alertButtonStyleStyle", "_textAreaVScrollBarStyleStyle", "_headerDateTextStyle",
"_globalStyle", "_todayStyleStyle", "_windowStylesStyle", "_ApplicationStyle",
"_ToolTipStyle", "_CursorManagerStyle", "_opaquePanelStyle", "_errorTipStyle",
"_dateFieldPopupStyle", "_dataGridStylesStyle", "_popUpMenuStyle",
"_headerDragProxyStyleStyle", "_activeTabStyleStyle", 
"_PanelStyle",
"_ContainerStyle", "_windowStatusStyle", "_ScrollBarStyle",
"_swatchPanelTextFieldStyle", "_textAreaHScrollBarStyleStyle", "_plainStyle",
"_activeButtonStyleStyle", "_advancedDataGridStylesStyle", "_comboDropdownStyle",
"_ButtonStyle", "_weekDayStyleStyle", "_linkButtonStyleStyle",
 "_CustomPanelWatcherSetupUtil" ]
        }
    }
}

}

Note

Read the blog post “Avoiding pitfalls of Flex RSL with Self Initialized Libraries” for more information.

Sibling Domains and Multiversioning

By now, it should be clear that applications, modules, and libraries (albeit bootstrapped as applications) are simply different forms of packaging .swf files. Libraries assume the tightest coupling with the loading code, and that’s why they get preloaded (by the application’s code generated by the Flex compiler). Modules get loaded and unloaded on demand, because they are needed only conditionally and only temporarily. Applications are similar to modules, in that they get loaded and unloaded on demand. The important advantage of applications over modules (as units of modularization) is that applications are self-sufficient, which allows you to mix multiple application .swfs compiled against different versions of the Flex framework (Flex 3.1, Flex 3.2, Flex 4.0, and so on).

Let’s elaborate. As you already know, libraries get loaded into the same domain as the application: ApplicationDomain.currentDomain. Accordingly, to avoid conflicts, a library has to be compiled against the same version of the Flex framework as the enclosing application. With modules, you get to choose between the same domain or a child domain (new ApplicationDomain(ApplicationDomain.currentDomain)), but even in the latter case, the class search starts with the parent domain. Again, to avoid conflicts, modules have to be compiled against the same version of the Flex framework as the consuming application. When it comes to applications, you still may use same-domain or child-domain techniques, provided that the loading application and subapplication are compiled against the same version of the Flex framework. What if you can’t recompile the Flex 3.2 subapplication and you want to load it from the Flex 4 main application? Then you need to load into the domain that is the sibling of the main application domain (new ApplicationDomain(null)).

Sibling domains allow ultimate separation of classes; you absolutely have to load the sub into the sibling domain to support multiversioning. That said, you may want to indiscriminately use sibling domains even when multiversioning is not an issue. A typical use case for this is portals, when you have to integrate portlets, perhaps developed by a third party. In brief:

To simplify the discussion, the following sections will use the term “portlet” instead of the subapplication and “portal” instead of the loading application.

Four Scenarios of Loading Portlets

To load and unload a portlet, you have to use SWFLoader (unless you are into writing your own loader). As you remember, SWFLoader is a wrapper around flash.display.Loader. As such, SWFLoader exposes the loaderContext property that controls the application domain precisely, like it does it for Loader. For instance, Example 7.31, “Using SWFLoader with default LoaderContext”’s MXML illustrates the loading of the RemoteApplication.swf portlet using the default loaderContext.

Example 7.31. Using SWFLoader with default LoaderContext

<mx:SWFLoader id="swfLoader"
   source="http://localhost:8080/RemoteSite/RemoteApplication.swf"
/>

Identical results can be achieved by Example 7.32, “Using SWFLoader with explicit LoaderContext”’s script.

Example 7.32. Using SWFLoader with explicit LoaderContext

private function loadApplication():void {
   swfLoader.loaderContext = new LoaderContext(
      false,
      new ApplicationDomain(ApplicationDomain.currentDomain)
   );
   swfLoader.source = "http://localhost:8080/RemoteSite/RemoteApplication.swf";
}

In both cases, the portlet’s classes get loaded in the child domain of the portal, according to the default loaderContext of a flash.display.Loader. However, there is more to loaderContext than controlling the application domain.

When a Flex application is loaded from a web domain, Flash Player, by default, assigns it a security sandbox. Applications coming from the different web domains get assigned different sandboxes. As an example, consider that the portal comes from http://localhost and loads the portlet from http://127.0.0.1. Unless you deviate from the default settings, these two applications will be assigned different sandboxes. Remember that class definitions get loaded into application domains and that application domains form a tree. There is one and only one tree per sandbox.

You can read more about sandboxes in the Flash documentation (Adobe often refers to them as security domains as well), but a few important points should be noted here:

  • You can indicate the sandbox preference in the constructor of the LoaderContext. For instance, Example 7.33, “Forced loading into the current sandbox”’s code snippet results in loading classes into the current security sandbox.

    Example 7.33. Forced loading into the current sandbox

    swfLoader.loaderContext = new LoaderContext(
        false,
        new ApplicationDomain(
           ApplicationDomain.currentDomain
        )
        SecurityDomain.currentDomain
    )

  • Although you can easily load portlets from other web domains into the current sandbox, there is no way you can programmatically load the portlet from the same web domain into the different sandbox. In other words, you can admit strangers into your family, but you can’t expel your kin. And the only way to load a portlet into a different sandbox is to host it in a different web domain or subdomain.

  • Assigning a different sandbox means a totally different tree of application domains.

To sum up, there are only four loaderContext combinations that you can arrange either programmatically or via hosting the portlet on the different subdomain:

  • Different Sandbox Different Domain (DSDD)

  • Same Sandbox Different (sibling) Domain (SSDD)

  • Same Sandbox Child Domain (SSCD)

  • Same Sandbox Same Domain (SSSD)

Table 7.1, “Loading portlets across web domains” illustrates how you can achieve a particular combination—DSDD, SSDD, SSCD, and SSSD (in this order)—provided that the portal and the portlet are hosted by the different web domains. You can explicitly use the loaderContext property or you can manipulate loadForCompatibility and trustContent.

Table 7.1. Loading portlets across web domains

loaderContext syntax

SWFLoader syntax

swfLoader.loaderContext=new
  LoaderContext( false,
    new ApplicationDomain(null),
    null
  );
<mx:SWFLoader
id="swfLoader"
/>
swfLoader.loaderContext=new
  LoaderContext( false,
    new ApplicationDomain(null),
    SecurityDomain.currentDomain
  );
<mx:SWFLoader
id="swfLoader"
loadForCompatibility="true"
trustContent="true"
/>
swfLoader.loaderContext=new
  LoaderContext( false,
    new ApplicationDomain(
      ApplicationDomain.currentDomain
    ),
    SecurityDomain.currentDomain
  );
<mx:SWFLoader
id="swfLoader"


trustContent="true"
/>
swfLoader.loaderContext=new
  LoaderContext( false,
 ApplicationDomain.applicationDomain,
    SecurityDomain.currentDomain
  );
Not applicable

Table 7.2, “Loading portlets from the same web domain” illustrates how the combination SSDD, SSCD, and SSSD can be achieved, provided that the portal and the portlet are located on the same web domain.

Table 7.2. Loading portlets from the same web domain

loaderContext syntax

SWFLoader syntax

swfLoader.loaderContext=new
  LoaderContext( false,
    new ApplicationDomain(null)
  );
<mx:SWFLoader
id="swfLoader"
loadForCompatibility="true"
/>
swfLoader.loaderContext=new
  LoaderContext( false,
    new ApplicationDomain(
      ApplicationDomain.currentDomain
    )
  );
<mx:SWFLoader
id="swfLoader"
/>
swfLoader.loaderContext=new
  LoaderContext( false,
    ApplicationDomain.currentDomain
  );
Not applicable

Some of these scenarios make more sense than the others. In particular, the Same Sandbox Same Domain scenario is the one most prone to class name clashing. To reiterate: duplicate loading of a class in the tree of application domains is not possible. At the same time, sub’s code can easily and perhaps inadvertently modify static variables of the classes hosted by the parent application. This relates to classes, such as mx.core.Application and mx.messaging.config.ServerConfig, for instance, and their properties application and xml, respectively.

On the opposite end is the Different Sandbox Different Domain scenario. Here you have the ultimate class isolation, which supports multiversioning plus ultimate security (more on this a bit later), at the price of a not-so-seamless user experience. For instance, the pop ups and alerts of the portlet will appear centered and clipped relative to the portlet rather than the entire portal, as shown in Figure 7.13, “DifferentSandboxDifferentDomainDemo; pop up is centered relatively to the loaded portlet”.

Figure 7.13. DifferentSandboxDifferentDomainDemo; pop up is centered relatively to the loaded portlet

DifferentSandboxDifferentDomainDemo; pop up is centered relatively to the loaded portlet

The remaining two scenarios are Same Sandbox Child Domain and Same Sandbox Different Domain. The latter should be considered the top choice for enterprise portals, as it supports multiversioning and delivers a seamless user experience. The simpler scenario, Same Sandbox Child Domain, is the one you’ll examine next. After that, you’ll investigate scenarios that provide multiversioning support.

Default Portlet Loading: Same Sandbox Child Domain

Same Sandbox Child Domain is the default scenario when the application and the subapplication are located in a single web domain. Unless you tell SWFLoader otherwise, portlet classes get loaded into the child application domain. To see how this works, start with a sample portlet, such as RegularApplication.mxml, in Example 7.34, “RegularApplication.mxml—sample portlet”.

Example 7.34. RegularApplication.mxml—sample portlet

<?xml version="1.0"?>
<!-- RegularApplication.mxml-->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" implements="IGreeting"
     backgroundColor="0xffeeff"   xmlns:local="*"
      creationComplete="onCreationComplete()">
<mx:Script>
<![CDATA[
   import mx.events.DynamicEvent;
   import mx.controls.Alert;
   import events.RemoteEvent;

   [Bindable] private var command:String="";
    [Bindable]  public var greeting:String = "";

   public function setGreeting(value:String):void {
      greeting = value;
   }
   public function getGreeting():String {
      return greeting;
   }

   private function onCreationComplete():void {
      Alert.show("Loaded application talks back...");
      // While you may use systemManager["swfBridge"] in the DSDD and SSDD,
      //   systemManager.loaderInfo.sharedEvents will work always
      var swfBridge:IEventDispatcher = systemManager.loaderInfo.sharedEvents;

      // Subscribe to command from the application
       swfBridge.addEventListener("command", onCommand,false,0,true );
       // Notify the application that creation has completed
      var evt:RemoteEvent = new RemoteEvent("creationComplete");
      evt.data = ". Loaded application reported createComplete!";
       swfBridge.dispatchEvent(evt);
   }

   private function onCommand(event:Event):void {
       command = event["data"] as String;
    }

]]>
</mx:Script>
    <mx:Panel title="Loaded Application - Google News {greeting}{command}."
                                width="90%" height="90%">
       <local:GoogleNews width="100%" height="100%"/>
    </mx:Panel>
</mx:Application>

RegularApplication.mxml implements the interface IGreeting from Example 7.12, “IGreeting interface”. Under the SSCD scenario, a portlet will see the definition of the IGreeting loaded by the portal. Accordingly, the portal will be able to cast the portlet to IGreeting, as shown in Example 7.35, “Interface-based scripting of the portlet loaded into the child domain” (you may compare swfLoader.content with moduleLoader.child).

Example 7.35. Interface-based scripting of the portlet loaded into the child domain

public function modifyValue():void {
   var systemManager:SystemManager = SystemManager(swfLoader.content);
   var loadedApplication:IGreeting = systemManager.application as IGreeting;
   loadedApplication.setGreeting(" accessed from outside");
}

Similarly to the way you arranged event-based communication with the modules, this portlet listens to and communicates with the loading application via loaderInfo.sharedEvents (Example 7.36, “Event-based portlet-portal communication via sharedEvents”).

Example 7.36. Event-based portlet-portal communication via sharedEvents

private function onCreationComplete():void {
   var swfBridge:IEventDispatcher = systemManager.loaderInfo.sharedEvents;

   // Subscribe to command from the application
   swfBridge.addEventListener("command", onCommand,false,0,true );

   // Notify the application that creation has completed
   var evt:RemoteEvent = new RemoteEvent("creationComplete");
   evt.data = ". Loaded application reported createComplete!";
   swfBridge.dispatchEvent(evt);
}

Make sure to deploy RegularApplication.mxml into an entirely dedicated BlazeDS or LCDS context. This example creates a combined Flex/Java LCDS/Web Tools Platform (WTP) project called RemoteSite, as shown in Figure 7.14, “Applications from RemoteSite will be accessed via domain 127.0.0.1”. (Please see the Adobe documentation on how to create a combined Flex/Java project with LiveCycle Data Services and WTP.) Having a dedicated Flex/JEE project enables you to define destinations of the portlet without affecting a portal or another portlet application.

Figure 7.14. Applications from RemoteSite will be accessed via domain 127.0.0.1

Applications from RemoteSite will be accessed via domain 127.0.0.1

To the RemoteSite/WebContent/WEB-INF/flex/proxy-config.xml file of this project, you need to add the destination GoogleNews, as shown in Example 7.37, “GoogleNews proxy destination”.

Example 7.37. GoogleNews proxy destination

<destination id="GoogleNews">
   <properties>
     <url>http://news.google.com/?output=rss</url>
   </properties>
</destination>

Example 7.38, “GoogleNews DataGrid” presents the class GoogleNews, a descendant of DataGrid that encapsulates HTTPService and displays Google News headlines to the user. When you run the portlet, it should look like Figure 7.15, “Sample portlet of RegularApplication”.

Example 7.38. GoogleNews DataGrid

<?xml version="1.0" encoding="utf-8"?>
<!-- GoogleNews.mxml -->
<mx:DataGrid  xmlns:mx="http://www.adobe.com/2006/mxml"
creationComplete="news.send()"
   dataProvider="{news.lastResult.channel.item}"
   variableRowHeight="true">
   <mx:columns>
     <mx:DataGridColumn headerText="Date"  dataField="pubDate" />
     <mx:DataGridColumn headerText="Title" dataField="title" wordWrap="true" />
   </mx:columns>


   <mx:HTTPService id="news" useProxy="true"  destination="GoogleNews"
resultFormat="e4x" fault="onFault(event)" />
<mx:Script>
<![CDATA[
   import mx.rpc.events.*;
   private function onFault(event:FaultEvent):void {
     mx.controls.Alert.show( "Destination:" + event.currentTarget.destination +
      "\n" + "Fault code:" + event.fault.faultCode + "\n" +
      "Detail:" + event.fault.faultDetail, "News feed failure"
      );
   }
]]>
</mx:Script>
</mx:DataGrid>

Figure 7.15. Sample portlet of RegularApplication

Sample portlet of RegularApplication

Finally, consider the sample portal, SameSandboxChildDomainDemo.mxml, in Example 7.39, “SameSandboxChildDomainDemo application”. We suggest you create a separate combined Flex/Java/WTP Eclipse project, as shown in Figure 7.16, “ApplicationLoaders project”. To illustrate the cross-domain specifics, you can run the portal from http://localhost while loading the portlet from the different domain, http://127.0.0.1.

Example 7.39. SameSandboxChildDomainDemo application

<?xml version="1.0"?>
<!-- SameSandboxChildDomainDemo.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:local="*" >
<mx:Script>
<![CDATA[
   import events.RemoteEvent;
    import mx.managers.SystemManager;

   private const APP_URL:String =
"http://127.0.0.1:8080/RemoteSite/RegularApplication.swf";

   public function modifyValue():void {
      // Casting to SystemManager and IGreeting is possible
      var systemManager:SystemManager = SystemManager(swfLoader.content);
      var loadedApplication:IGreeting = systemManager.application as IGreeting;
      loadedApplication.setGreeting(" accessed from outside");
    }

    private function loadApplication():void {
      swfLoader.addEventListener("complete", onLoadComplete);
         swfLoader.source = APP_URL;
   }

    [Bindable] private var applicationLoaded:Boolean;
    private var sharedEventDispatcher:IEventDispatcher;

   private function onLoadComplete(event:Event):void {
      applicationLoaded = true;
         sharedEventDispatcher = swfLoader.content.loaderInfo.sharedEvents;
         sharedEventDispatcher.addEventListener(
         "creationComplete", onLoadedApplicationCreated
      );
    }

      [Bindable] private var reply:String="";
   // Casting to RemoteEvent is possible
    private function onLoadedApplicationCreated(event: RemoteEvent):void
                    reply = event.data as String;
         var remoteEvent:RemoteEvent = new RemoteEvent("command");
         remoteEvent.data = ". Two-way communication works!";
         sharedEventDispatcher.dispatchEvent(remoteEvent);
    }
    ]]>
    </mx:Script>
    <mx:HBox>
      <mx:Button label="Load Application" click="loadApplication()" />
          <mx:Button label="Modify Value" click="modifyValue();"
         enabled="{applicationLoaded}"/>
    </mx:HBox>

    <mx:Panel title="Yahoo News{reply}" width="100%" height="50%"
              id="panel">
       <local:YahooNews width="100%" height="100%"/>
    </mx:Panel>
    <mx:SWFLoader id="swfLoader" width="100%" height="50%"
                                                trustContent="true"/>
</mx:Application>

Notice the setting trustContent="true" of the swfLoader. This guarantees that despite different web domains of the portal and portlet, class loading happens into the same sandbox and, by default, to the child application domain.

That said, you should stick to the golden Flash security rule that the .swf (of the portal) can access a resource (portlet) on the different web domain only when such domain holds a cross-domain policy file that expresses trust to the domain of the .swf. So make sure your root web application contains the file shown in Example 7.40, “Policy file cross-domain.xml”.

Figure 7.16. ApplicationLoaders project

ApplicationLoaders project

Example 7.40. Policy file cross-domain.xml

<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy
  SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
 <allow-access-from domain="*"/>
</cross-domain-policy>

Make sure that you do not use this indiscriminating policy file in production. For more information on secure cross-domain communication in Flash Player, see http://www.adobe.com/devnet/flashplayer/articles/secure_swf_apps.html.

SameSandboxChildDomainDemo.mxml has its own news grid—it displays Yahoo! News. (The code of YahooNews is identical to GoogleNews from Example 7.37, “GoogleNews proxy destination”, except that it uses the different destination, as presented in Example 7.41, “Proxy destination for Yahoo! News”. You should add this destination to ApplicationLoaders/WebContent/WEB-INF/flex/proxy-config.xml.)

Example 7.41. Proxy destination for Yahoo! News

<destination id="YahooNews">
   <properties>
     <url>http://rss.news.yahoo.com/rss/topstories</url>
   </properties>
</destination>

When you run the application and click OK on the pop up called “Loaded application talks back,” it will look like Figure 7.17, “SameSandboxChildDomainDemo”.

Figure 7.17. SameSandboxChildDomainDemo

SameSandboxChildDomainDemo

Loading Portlets for Multiversioning

What about the scenarios that support multiversioning? The default loading scenario from different web domains is Different Sandbox Different Domain. Example 7.42, “DifferentSandboxDifferentDomainDemo”’s sample portal, DifferentSandboxDifferentDomainDemo, not only illustrates this scenario, it will also help you to understand the Same Sandbox Different Domain scenario.

When you examine the code, notice the seemingly redundant reference to the class PopUpManager. It’s not accidental. You always have to link the PopUpManager class to your portal to allow pop-up controls in the portlets. That’s how Adobe implemented it, and this requirement does not seem like too much to ask for.

Next, note that casting across sibling domains is out of reach. Look at the body of the modifyValue() method. You can’t cast the loadedApplication either to IGreeting or to mx.core.Application. Instead, the example declares it as flash.display.DisplayObject. For similar reasons, the declaration of the onLoadedApplicationCreated() method downcasts the type of object to flash.events.Event. If you instead try to declare loadedApplication as Application, you will receive this runtime error:

TypeError: Error #1034: Type Coercion failed: cannot convert
TrustfulApplication@c8f20a1 to mx.core.Application.

Now, examine the function onLoadComplete(). To obtain the reference to the sharedEventDispatcher, the function uses the expression swfLoader.swfBridge instead of swfLoader.content.loaderInfo.sharedEvents.

Example 7.42. DifferentSandboxDifferentDomainDemo

<?xml version="1.0"?>
<!-- DifferentSandboxDifferentDomainDemo.mxml -->
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:local="*" >
<mx:Script>
<![CDATA[
   import events.RemoteEvent;
    import mx.managers.PopUpManager; PopUpManager;
    import mx.managers.SystemManager;

    private const APP_URL:String =
    "http://127.0.0.1:8080/RemoteSite/TrustfulApplication.swf";

   public function modifyValue():void {
      var loadedApplication:DisplayObject = swfLoader.content["application"];
loadedApplication["setGreeting"]("loaded from outside");
    }

    private function loadApplication():void {
         swfLoader.addEventListener("complete", onLoadComplete);
            swfLoader.source=APP_URL;
    }

    [Bindable] private var applicationLoaded:Boolean;
     private var sharedEventDispatcher:IEventDispatcher;
     private function onLoadComplete(event:Event):void {
            swfLoader.removeEventListener("complete", onLoadComplete);
            applicationLoaded = true;
           // Since swfLoader.content.loaderInfo.sharedEvents=null,
           // use swfLoader.swfBridge
            sharedEventDispatcher = swfLoader.swfBridge;
            sharedEventDispatcher.addEventListener("creationComplete",
                                         onLoadedApplicationCreated);
     }

      [Bindable] private var reply:String="";
      // We cannot cast RemoteEvent across Application Domains
    private function onLoadedApplicationCreated(event:/*RemoteEvent*/ Event):void {
         if (event.hasOwnProperty("data")) {
               reply = event["data"];
         }
         var remoteEvent:RemoteEvent = new RemoteEvent("command");
         remoteEvent.data = ". Two-way communication works!";
         sharedEventDispatcher.dispatchEvent(remoteEvent);
    }

]]>
 </mx:Script>
    <mx:HBox>
      <mx:Button label="Load Application" click="loadApplication()" />
          <mx:Button label="Modify Value" click="modifyValue();"
         enabled="{applicationLoaded}"/>
    </mx:HBox>

    <mx:Panel title="Yahoo News{reply}" width="100%" height="50%" id="panel">
       <local:YahooNews width="100%" height="100%"/>
    </mx:Panel>
    <mx:SWFLoader id="swfLoader" width="100%" height="50%"/>
</mx:Application>

The same concepts hold true for the Same Sandbox Different Domain scenario as well. Specific to the cross-domain scenario, however, is that DifferentSandBoxDifferentDomainDemo loads TrustfulApplication.swf (Example 7.43, “TrustfulApplication”), which extends the RegularApplication merely to express cross-scripting trust to the web domain of the portal via Security.allowDomain("*").

Example 7.43. TrustfulApplication

<?xml version="1.0"?>
<!-- TrustfulApplication.mxml-->
<RegularApplication xmlns:mx="http://www.adobe.com/2006/mxml" xmlns="*"
preinitialize="onPreinitialize(event)">
   <mx:Script>
      <![CDATA[
         // Try to use without allowDomain and see the r.t. SecurityError
         private function onPreinitialize(event:Event):void {
            Security.allowDomain("*"); //localhost, wwww.adobe.com, etc.
         }
      ]]>
   </mx:Script>
</RegularApplication>

The body of the function modifyValue() takes advantage of these cross-scripting permissions, referring to swfLoader.content. Had you loaded the untrusted RemoteApplication.swf, you would have received the error shown in Example 7.44, “Example of security error”.

Example 7.44. Example of security error

SecurityError: Error #2121: Security sandbox violation: Loader.content:
http://localhost:8080/ApplicationLoaders/DifferentSandboxCommunicationDemo.swf
cannot access http://127.0.0.1:8080/RemoteSite/RegularApplication.swf.
This may be worked around by calling Security.allowDomain.
    at flash.display::Loader/get content()
    at mx.controls::SWFLoader/get content

This is the only coding specific to the DSDD scenario versus SSDD. Of course, in the case of SSDD, the loadingForCompatibility property of the swfLoader would be set to true, and you would specify trustContent="true" to offset the domain difference.

The successfully running DSDD application was previously presented in Figure 7.16, “ApplicationLoaders project”, and Figure 7.18, “Same Sandbox Different Domain: Flex Messaging does not work without bootstrap loading of the messaging classes” illustrates a problem in the SSDD scenario: the Google News panel is showing up empty. As it turns out, in the case of SSDD, you need to change your architecture and preload Flex messaging, RPC, and Data Management Services–related classes in the application domain that will parent the domain of the portal.

Figure 7.18. Same Sandbox Different Domain: Flex Messaging does not work without bootstrap loading of the messaging classes

Same Sandbox Different Domain: Flex Messaging does not work without bootstrap loading of the messaging classes

Bootstrap Class Loading

The previous section mentioned that casting is out of reach across sibling domains. That constraint is not as tight, however, as you might think. Remember how you cast loaded modules and applications to the IGreeting interface earlier in the chapter? You did not cast the IGreeting of the child to the IGreeting of the parent, because the IGreeting of the child did not exist. A child is always reusing classes loaded in the parental chain. So, two sibling domains can cast classes if they share a common parent that preloads these classes. In particular, such bootstrap class loading, as Adobe calls it, is required to maintain a common definition of the following classes from the mx.messaging.messages package per security domain:

  • ConfigMap

  • AcknowledgeMessage

  • AcknowledgeMessageExt

  • AsyncMessage

  • AsyncMessageExt

  • CommandMessage

  • CommandMessageExt

  • ErrorMessage

  • HTTPRequestMessage

  • MessagePerformanceInfo

  • RemotingMessage

  • SOAPMessage

In the Different Sandbox Different Domain scenario, the portal and portlet reside in the different sandboxes, so bootstrap loading of the Flex messaging classes is not an issue. However, in the Same Sandbox Different Domain scenario, the absence of the common bootstrap loader results in the first application that happens to load these classes into its own domain (be that portal or portlet) to block all other siblings from receiving messages from the MessageBroker.

At Farata Systems, we customized PortalBootstrapLoader, which is a separate ActionScript project (Figure 7.19, “PortalBootstrapLoader project”).

Figure 7.19. PortalBootstrapLoader project

PortalBootstrapLoader project

As you study the code for PortalBootstrapLoader in Example 7.45, “PortalBootstrapLoader.as”, notice that in addition to linking in all classes required by Adobe, we also link in the class com.farata.portal.Message. Follow this pattern to link in any class that you want to make available for all portlets in your portal (and the portal itself).

Example 7.45. PortalBootstrapLoader.as

//PortalBootstrapLoader.as
package {
   import flash.display.Loader;
   import flash.display.Sprite;
   import flash.display.StageAlign;
   import flash.display.StageScaleMode;
   import flash.events.Event;
   import flash.net.URLRequest;
   import flash.system.ApplicationDomain;
   import flash.system.LoaderContext;
   import flash.system.SecurityDomain;

   import utils.QueryString;

   import mx.messaging.config.ConfigMap; ConfigMap;
   import mx.messaging.messages.AcknowledgeMessage; AcknowledgeMessage;
   import mx.messaging.messages.AcknowledgeMessageExt; AcknowledgeMessageExt;
   import mx.messaging.messages.AsyncMessage; AsyncMessage;
   import mx.messaging.messages.AsyncMessageExt; AsyncMessageExt;
   import mx.messaging.messages.CommandMessage; CommandMessage;
   import mx.messaging.messages.CommandMessageExt; CommandMessageExt;
   import mx.messaging.messages.ErrorMessage; ErrorMessage;
   import mx.messaging.messages.HTTPRequestMessage; HTTPRequestMessage;
   import mx.messaging.messages.MessagePerformanceInfo; MessagePerformanceInfo;
   import mx.messaging.messages.RemotingMessage; RemotingMessage;
   import mx.messaging.messages.SOAPMessage; SOAPMessage;

   import com.farata.portal.Message;Message;

   public class PortalBootstrapLoader extends Sprite {

   public function PortalBootstrapLoader() {
      super();

    if (ApplicationDomain.currentDomain.hasDefinition("mx.core::UIComponent"))
       throw new Error("UIComponent should not be in the bootstrap loader.");
    if (ApplicationDomain.currentDomain.hasDefinition("mx.core::Singleton"))
       throw new Error("Singleton should not be in the bootstrap loader.");

    if (stage) {
         stage.scaleMode = StageScaleMode.NO_SCALE;
         stage.align = StageAlign.TOP_LEFT;
    } else
         isStageRoot = false;
      root.loaderInfo.addEventListener(Event.INIT, onInit);
   }

   /**
   * The Loader that loads the main application's SWF file.
   */
   private var loader:Loader;

   /**
   * Whether the bootstrap loader is at the stage root or not,
   * it is the stage root only if it was the root
   * of the first SWF file that was loaded by Flash Player.
   * Otherwise, it could be a top-level application but not stage root
   * if it was loaded by some other non-Flex shell or is sandboxed.
   */
   private var isStageRoot:Boolean = true;

   /**
   * Called when the bootstrap loader's SWF file has been loaded.
   * Starts loading the application SWF specified by the applicationURL
   * property.
   */
   private function onInit(event:Event):void {
      loader = new Loader();

       var loaderContext:LoaderContext  = new LoaderContext(
         false,
         new ApplicationDomain(ApplicationDomain.currentDomain),
         SecurityDomain.currentDomain
       );

      addChild(loader);
      loader.load(new URLRequest(applicationUrl), loaderContext );

      loader.addEventListener(
         "mx.managers.SystemManager.isBootstrapRoot",
         bootstrapRootHandler
      );
      loader.addEventListener(
         "mx.managers.SystemManager.isStageRoot",
         stageRootHandler
      );

      loader.addEventListener(Event.ADDED, resizeHandler );
      stage.addEventListener(Event.RESIZE, resizeHandler);
   }

   private function get applicationUrl():String{
      var qs:QueryString = new QueryString();
      return qs.root + qs.parameters.app;
   }

   private function bootstrapRootHandler(event:Event):void {
      event.preventDefault();
   }

   private function stageRootHandler(event:Event):void {
      if (!isStageRoot)
         event.preventDefault();
   }

   private function resizeHandler(event:Event=null):void {
      if ( loader.content ){
         Object(loader.content).setActualSize(stage.stageWidth, stage.stageHeight);
      }
   }
  }
}

To use the bootstrap loader, we copy PortalBootstrapLoader.html and PortalBootstrapLoader.swf to the deployment folder of the portal and, in the browser, type the URL, similar to:

http://localhost:8080/ApplicationLoaders/PortalBootstrapLoader.html?app=ApplicationLoaders/SameSandboxDifferentDomain.swf

As you can see from Figure 7.20, “SameSandboxDifferentDomain with bootstrap class loading”, now the Google News panel of the portlet is filled by the data. Flex Messaging works because we made the definitions of the messaging classes visible to all application domains in the portal.

Figure 7.20. SameSandboxDifferentDomain with bootstrap class loading

SameSandboxDifferentDomain with bootstrap class loading

Sample Flex Portal

To speed up your portal development, this section describes a sample Flex portal that you can download from the site accompanying this book. You’ll need to download the following projects:

PortalLib

Utility library referenced by all other projects

Feeds

Combined Flex/Java Dynamic Web Project with GoogleFinancialNews and YahooFinancialNews applications

Charts

Combined Flex/Java Dynamic Web Project with Chart1 and Chart2 applications

Portal

Combined Flex/Java Dynamic Web Project with the SamplePortal application

PortalBootstrapLoader

ActionScript project

Figure 7.21, “SamplePortal” illustrates running SamplePortal, which you should start via PortalBootstrapLoader:

http://localhost:8080/Portal/PortalBootstrapLoader.html?app=/Portal/SamplePortal.swf

You can to create instances of portlets of different types by dragging and dropping on the portal canvas the navigational items located in the lower part of the screen, such as “Same Sandbox—Child Domain,” “Same Sandbox—Sibling Domain,” and “Different Sandbox—Different Domain.”

Each portlet is contained by a custom resizable and draggable Panel and carries either the GoogleFinancialNews or the YahooFinancialNews application, according to the descriptor of the navigation items in SamplePortal, as shown in Example 7.46, “SamplePortal”.

Example 7.46. SamplePortal

<?xml version="1.0" encoding="utf-8"?>
<!--  SamplePortal  -->
<mx:Application layout="absolute"
   xmlns:mx="http://www.adobe.com/2006/mxml"
   xmlns:fx="http://www.faratasystems.com/2009/portal" >

   <mx:Style source="styles.css"/>
   <fx:PortalCanvas  width="100%" height="100%">
      <fx:navItems>
         <fx:NavigationItem>
            <fx:PortletConfig title="Same Sandbox - Child Domain"
               preferredHeight="400" preferredWidth="850" >
               <fx:props>
                  <mx:Object trusted="true" multiversioned="false"
               url="http://127.0.0.1:8080/Feeds/YahooFinancialNews.swf"/>
               </fx:props>
            </fx:PortletConfig>
         </fx:NavigationItem>
         <fx:NavigationItem>
            <fx:PortletConfig title="Same Sandbox - Sibling Domain"
               preferredHeight="400" preferredWidth="850">
               <fx:props>
                  <mx:Object trusted="true" multiversioned="true"
               url="http://127.0.0.1:8080/Feeds/GoogleFinancialNews.swf"/>
               </fx:props>
            </fx:PortletConfig>
         </fx:NavigationItem>
         <fx:NavigationItem>
            <fx:PortletConfig title="DifferentSandbox - Different Domain"
               preferredHeight="400" preferredWidth="850" >
               <fx:props>
                  <mx:Object trusted="false" multiversioned="true"
               url="http://127.0.0.1:8080/Feeds/YahooFinancialNews.swf"/>
               </fx:props>
            </fx:PortletConfig>
         </fx:NavigationItem>
      </fx:navItems>
   </fx:PortalCanvas>

   <mx:Script>
      <![CDATA[
         import mx.managers.PopUpManager;PopUpManager;
         import PortletInfo;PortletInfo;
      ]]>
   </mx:Script>
</mx:Application>

A click on the Show Chart button loads Chart1 or Chart2 into a sibling domain and flips the portlet’s content. Each portlet allows you to send messages to the portal, and from the portal itself you can broadcast a text message to all active portlets, shown in Figure 7.21, “SamplePortal”.

Figure 7.21. SamplePortal

SamplePortal

Integrating Flex into Legacy JEE Portals

If you are the owner of a legacy Web 1.0 portal, you can consider integrating Flex applications into your portal space in an entirely different way.

The good news is that any Flex .swf file is valid content for a generic Flex portlet prewritten by Adobe. Open the resources/wsrp/lib folder from the root of the installed LiveCycle Data Services; you will find flex-portal.jar with flex.portal.GenericFlexPortlet inside. Add the .jar to the class path of your web application (WebContent/lib) and also copy the resources/wsrp/wsrp-jsp folder to the deployment root of your project (WebContent).

Now take the portlet.xml of your legacy portal, and inject Example 7.47, “Registering a Flex application as a portlet via flex.portal.GenericFlexPortlet”’s code to instantly add the YahooFinancialNews portlet.

Example 7.47. Registering a Flex application as a portlet via flex.portal.GenericFlexPortlet

<?xml version="1.0" encoding="UTF-8"?>
<portlet-app version="1.0"
   xmlns="http://java.sun.com/xml/ns/portlet/portlet-app_1_0.xsd"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
. . . .
<!-Descriptor of Flex portlet YahooFinancialNews -->
<portlet>
   <portlet-name>YahooFinancialNews</portlet-name>
   <portlet-class>flex.portal.GenericFlexPortlet</portlet-class>
   <init-param><name>wsrp_folder</name><value>/Portal</value></init-param>
   <supports>
      <mime-type>text/html</mime-type>
      <portlet-mode>view</portlet-mode>
   </supports>
   <portlet-info><title>Yahoo Financial News</title></portlet-info>
   <portlet-preferences>
      <preference>
         <name>app_uri</name>
         <value>/Portal/YahooFinancialNews</value>
      </preference>
      <preference>
          <name>norm_width</name>
         <value>400</value>
      </preference>
      <preference>
         <name>norm_height</name>
         <value>400</value>
      </preference>
   </portlet-preferences>
</portlet>
<portlet>
</portlet-app>

The preference app_uri points to the URL of the YahooFinancialNews.swf, stripped of the “.swf”, and the parameter wsrp_folder points to the parent URL of the wsrp-jsp.

That’s all it takes to have your Flex application running inside a Web 1.0 portal! Because YahooFinancialNews has been compiled to communicate with the MessageBroker of the Feeds web application, however, you do have to make sure that Feeds is deployed in the same domain.

But don’t get carried away. First of all, you can’t flexibly control the real estate dedicated to your portlet. Look at the rigid layout of Figure 7.22, “A Flex application’s ad portlets in a WebLogic portal 10.2”, which illustrates a BEA WebLogic portal with the mixture of two instances of GenericFlexPortlet (running YahooFinancialNews and GoogleFinancialNews), SingleVideoPortlet, and ShowTimePortet; you can download the second two from Portlet Repository Downloads. The Flex applications appear squeezed and cumbersome to use.

Second, and even more important, mixing Web 2.0 portlets based on Flash or AJAX with Web 1.0 ones (such as ShowTimePortlet in the example) is outright dangerous, if you consider that Flex applications and Web 2.0 portlets maintain state on the client, but rerendering of the Web 1.0 ones eliminates the entire HTML page.

As a result, the only way to integrate a Flex application in your legacy portal may be to run a single application per page.

Figure 7.22. A Flex application’s ad portlets in a WebLogic portal 10.2

A Flex application’s ad portlets in a WebLogic portal 10.2

Summary

Understanding how Flex loaders work, combined with the knowledge of different ways of linking modules and libraries to your main application, is crucial for the creation of Flex portals. Even if you are not concerned with portals, the chances are high that your application size will increase, and sooner or later you’ll need to decide how to cut it into pieces. The sooner you start planning for modularizing your application, the better.

If you enjoyed this excerpt, buy a copy of Enterprise Development with Flex.

Copyright © 2009 O'Reilly Media, Inc.