A Flex Implementation using Modules with URLKit Deep-Linking

At work, we recently had an opportunity to rebuild the Flex front end to our web application. Having started from scratch with the initial version of the application a year and a half ago (with little prior Flex experience in our staff), this “greenfield” opportunity gave us a chance to take the lessons learned (and new stuff available in Flex 3) and apply them to the new project. One important change, from an architecture point-of-view, was the introduction of modules and how to make them work with deep-linking.

With the first version of the app, we quickly realized that a monolithic SWF soon becomes too large to load over not-so-fast internet connections. By modularizing the app, loading of content that is not immediately needed can be deferred until later. There is an excellent presentation/demo of what modules are and how to use them at Alex Harui’s blog.

To get around the complexities of modules and ApplicationDomains, we decided to simply load all modules into the top-level ApplicationDomain. We also wanted to handle deep-linking in a standard way across all modules. We use URLKit to do most of this work — there’s a detailed explanation of how URLKit works here.

We started by creating a base class that extends ModuleLoader. It looks something like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
    public class ModuleLoaderBase extends ModuleLoader {
 
        protected var isReady:Boolean = false;
 
        public function ModuleLoaderBase() {
            super();
            applicationDomain = ApplicationDomain.currentDomain;
            this.addEventListener(ModuleEvent.READY, onReady);
        }
 
        protected function onReady(event:Event):void {
            isReady = true;
        } 
 
        /**
         * This function is called from ModuleBase when upon module creationComplete event.
         */
        public function onModuleCreationComplete():void {
            initializeDeepLinking();
            MainApplicationFacade.getInstance().appEnabledQueuePop(true);
        }
 
        /**
         * This is where sub-classes can implement deeplinking using urlKit.
         */
        protected function initializeDeepLinking():void {
        }
 
        /**
         *  Add a noCache parameter to the module URL to force the browser to reload the module.
         */
        public override function set url(u:String):void {
            super.url = u + "?nocache=" + new Date().time.toString();
        }
 
    }

Notice on line 7 how we’re pointing the application domain of the module loader to the current or top-level domain. So all modules we’re using simply load their class definitions into the top-level ApplicationDomain. We can do this as long as we’re not concerned with unloading modules. If that is a need, this strategy will not work and you’d have to implement a more complex solution (probably using a “shared classes” module as outlined in the presentation linked above).

Using this base class, we then create a new module loader class for each one of our modules. For instance, here’s one that loads a book details module:

<module:ModuleLoaderBase
    xmlns:url="http://www.allurent.com/2006/urlkit"
    xmlns:module="com.acme.core.module.*"     
    xmlns:mx="http://www.adobe.com/2006/mxml">
 
    <mx:Script>
        <![CDATA[
 
            import com.acme.module.IBookDetailsModule;
            import mx.binding.utils.BindingUtils;
 
            [Bindable] public var bookId:String;
            private var module:IBookDetailsModule;
 
            protected override function initializeDeepLinking(): void {
                module = this.child as IBookDetailsModule;
                mx.binding.utils.BindingUtils.bindProperty(module, "bookId", this,  "bookId");
                mx.binding.utils.BindingUtils.bindProperty(this, "bookId", module,  "bookId");
            }
 
 
        ]]>
    </mx:Script>
 
 
 
    <url:UrlRuleSet id="urls">
        <url:UrlValueRule id="valueRule" urlFormat="/*" sourceValue="bookId"/>
    </url:UrlRuleSet>      
 
</module:ModuleLoaderBase>

Notice the use of an interface (IBookDetailsInterface) to get a handle on the loaded module. This enables us to “talk” to the module without compiling it into the main application (the entire reason for going with modules in the first place). Also note the two-way binding of the bookId property into and out of the module. This enables us to set the bookId property on the module loader or the module, and the change will be reflected either way in the browser nav bar.

So now on to the actual module. First, we have a base class that all modules extend from:

    public class ModuleBase extends Module {
 
        public function ModuleBase() {
            super();
            this.addEventListener(FlexEvent.CREATION_COMPLETE, onCreationComplete);
        }
 
        private function onCreationComplete(event:Event): void {
            var loader:ModuleLoaderBase = ModuleLoaderBase(this.parent);
            loader.onModuleCreationComplete();
        }
    }
}

Really, the sole purpose of this base class is to call the onModuleCreationComplete function on the module loader base class. This so that our urlKit properties can be created at the appropriate time.

The sample BookDetailsModule then looks like this:

<module:ModuleBase
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns:module="com.acme.core.module.*"
    implements="com.acme.module.IBookDetailsModule">
 
    <mx:Script>
        <![CDATA[
 
            import com.acme.book.event.GetBookDetailsEvent;
 
            private var _bookId:String;
            [Bindable] 
            public function get bookId():String {
                return this._bookId;
            }
 
            public function set bookId(newValue:String):void {
                this._bookId = newValue;
                if (bookId) {
                    new GetBookDetailsEvent(bookId).dispatch();
                }
            }
 
        ]]>
    </mx:Script>
 
    <container:ViewStack id="viewStack" width="100%" resizeToContent="true">
 
        <container:HBox id="VIEW_LOADING" width="100%">
            <control:Label htmlText="Loading..."/>
        </container:HBox>
 
        <view:BookDetailsView id="VIEW_DETAILS" width="100%"/>
 
    </container:ViewStack>
 
</module:ModuleBase>

So when the module has loaded and receives a change in the bookId property, the module is responsible for the logic of going out to get the data (dispatches the GetBookDetailsEvent). All logic related to displaying book details is then contained in the module and is not loaded until explicitly referenced by the main app.

In the main application, the module can then be used like this:

. . .
        <container:ViewStack id="viewStack" resizeToContent="true" width="100%">
 
            <view:HomePageView id="VIEW_HOME_PAGE" label="home"/>
 
            <module:BookDetailsModuleLoader id="VIEW_BOOK_DETAILS" 
                url="BookDetailsModule.swf" label="bookDetails" 
                width="100%" height="100%"/>
. . .

In this case, when the VIEW_BOOK_DETAILS module loader is selected as the current child in the viewstack, the module is loaded. You can now use the urlKit features to load the details for a particular book (with id 12) by deep-linking your browser to:

http://myhost/myapp/#/bookDetails/12

Or you can reference the module via the module loader in code:

. . .
VIEW_BOOK_DETAILS.bookId = 12;
VIEW_BOOK_DETAILS.loadModule();
...

In real life, you’d probably also have a view facade or command that ends up setting the bookId property and selecting the VIEW_BOOK_DETAILS as the active view in the viewstack. That way, you can very simply load up the book details from code, even from another module.

Clearly, there’s a bunch more “meat” behind the real application than what is shown here, but hopefully this demonstrates a solution to working with modules and deep-linking. If not, please post comments with questions and I’ll follow up.