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

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:

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 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.)

RSL link type defaults to autoextraction of the RSL SWF

Figure 7-9. RSL link type defaults to autoextraction of the RSL SWF

According to Figure 7-9, 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 Bootstrapping Libraries As Applications.

Here you will create a Flex library project, ComponentLibrary, with a single component, CustomPanel (Example 7-20).

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.

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

If, however, you uncomment this line:

//import mx.containers.Panel;Panel;

the application will run successfully, as shown in Figure 7-11.

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. 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);
      .  .  .
}
If you link in the Panel class, LibraryDemo works well

Figure 7-11. 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).

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. 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). 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, 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 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 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 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). Finally, Example 7-28 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.

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

Buildfile: C:workspacesfarata.samplesComponentLibraryuild.xml
compile:
     [exec] Loading configuration file C:Program FilesAdobeFlash Builder 3
            Plug-insdks3.2.0frameworksflex-config.xml
     [exec] C:workspacesfarata.samplesComponentLibraryinComponentLibrary.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

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, 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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.224.62.105