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:
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.
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.)
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.
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.
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);
. . .
}
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.
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.
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" ] } } } }
Read the blog post “Avoiding pitfalls of Flex RSL with Self Initialized Libraries” for more information.
18.224.62.105