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


Embedding Flex Applications in a Browser: Chapter 20 - Programming Flex 3

by Chafic Kazoun and Joey Lott

This excerpt is from Programming Flex 3. If you want to try your hand at developing rich Internet applications with Adobe's Flex 3, and already have experience with frameworks such as .NET or Java, this is the ideal book to get you started. Programming Flex 3 gives you a solid understanding of Flex 3's core concepts, and valuable insight into how, why, and when to use specific Flex features. Learn to get the most from this amazing and sophisticated technology.

buy button

Although it is possible to build Flex applications to deploy on the desktop (see Chapter 22), the vast majority of Flex applications today are deployed on the Web. To deploy a Flex application on the Web you should embed it in an HTML page. Although this may seem like a fantastically simple task (and it can be), it has larger implications. In this chapter, we'll look at these implications and the options available to you for embedding a Flex application in HTML and how a Flex application can interact with the web browser environment.

Section 20.1: Embedding a Flex Application in HTML

By now, you should be aware that Flex applications get compiled to .swf files. When you deploy a Flex application to the Web, you must place the .swf file for the Flex application on the web server. At a minimum, this one file is always required. However, in almost all cases you will need to provide at least one additional file: an HTML file that embeds the .swf. Although it is possible for users to access an .swf file directly in a web browser, such an approach is inadvisable. Keep in mind that when users access a Flex application in a browser, you will still want the browser to display basic information such as a title, information that only an HTML page can provide. Furthermore, by using an HTML page, it is possible to do much more with a Flex application than if the user was accessing the application's .swf file directly in the browser. For example, using an HTML page you can utilize JavaScript. And by embedding a Flex application in HTML, you can place the application alongside other non-Flex content. Not only that, but by using JavaScript and HTML, you can detect whether the user even has Flash Player installed. Therefore, embedding Flex applications in HTML is a fundamental skill for creating Flex applications. In the next few sections, we will look at various ways in which you can embed Flex content in HTML pages.

Note: Any technology that writes HTML to a browser can be used interchangeably with HTML files, as we describe in this chapter. For example, you can use the same principles described in this chapter with PHP, .NET, and Ruby to embed a Flex application.

Using HTML Tags to Embed Flex Content

Flex applications are .swf files that must be run by an ActiveX control or a browser plug-in. Therefore, you embed Flex applications in HTML pages in much the same way that you can embed other types of applications or media that requires an ActiveX control or a browser plug-in.

Note: Flash Player runs as an ActiveX control in Internet Explorer and as a plug-in for Firefox and other browsers.

Two tags allow you to embed content in an HTML page: object and embed. There is much debate among developers as to which is better and how they should and shouldn't be used. For instance, the embed tag is supported in all modern browsers, and that might sound like a good enough reason to use it exclusively. However, there are drawbacks to using the embed tag. For one, the embed tag is not standards-compliant; it is not the W3C-recommended way in which to embed content. This is because the embed tag is proprietary and is covered by patents. Furthermore, there is the issue of alternative content. Although some popular search engines (notably Google) are capable to some degree of indexing Flash and Flex content, it is still customary (and recommended) that you provide alternative content whenever you have a Flex application. This allows you to create content that will be visible not only to users who do not have Flash Player, but also to search engines, allowing you greater control over how search engines index the site. The embed tag simply doesn't allow this to happen. Although the embed tag does allow alternative content using a noembed tag, only users using a browser that doesn't support the embed tag can view the alternative content, and this doesn't include search engines. For these reasons, we recommend that you use only the object tag, which is the W3C recommendation. Also, the object tag allows you to specify alternative content that is indexable.

Although there is one W3C recommendation for how to embed such content using an object tag, as we'll see not all browsers are designed to work according to the same specification. One of the strengths of Flex is that it shields application developers from the majority of browser inconsistencies. However, the only time you'll have to deal with these issues is when you embed the Flex application in an HTML page.

All browsers are consistent in that when you use the object tag, you must specify width and height attributes, indicating the dimensions (in pixels) of the content to embed in the page. However, beyond that they differ on two important points:

Microsoft Internet Explorer determines the control to use by way of an attribute called classid. The classid value for Flash Player is a long value that you are unlikely to memorize. Instead, you'll simply want to copy and paste it from one document to another. Therefore, an object tag that will embed a Flash Player control in a page for Internet Explorer (at 400 by 400 pixels) looks like the following:

<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
        width="400" height="400">
</object>

Note: The classid value is case-insensitive.

Internet Explorer relies on a nested param tag with a name of movie to specify the .swf that should be loaded into the control. The following example will load an .swf file called example.swf:

<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
        width="400" height="400">
    <param name="movie" value="example.swf">
</object>

Note: You can also specify a codebase attribute for Internet Explorer to tell the browser where it can go to download the control if it's not already installed. This is generally not advised because it interferes with alternative content and there are better ways to allow users to install Flash Player. The HTML templates that ship with the Flex SDK (and Flex Builder) use the codebase attribute. However, as you'll read later in this chapter, we advise against using the Flex HTML templates for this and other reasons.

Internet Explorer is alone in its answers to these questions. All other major browsers take a different approach and have standardized on specifying the plug-in to use via the MIME type as indicated by the value of a type attribute. They also have standardized on specifying the content to load into the plug-in via an attribute called data.

The following is an example of an object tag that accomplishes the same thing as the preceding example, except that it works for all browsers excluding Internet Explorer:

<object type="application/x-shockwave-flash" data="example.swf"
width="400" height="400" />

The obvious question is how can you support all browsers at the same time if different browsers require different implementations? One of the preferred answers to this question is to nest the object tags with the Internet Explorer version on the outside. Browsers that don't use the classid attribute will ignore the outer object tag, using only the inner tag. Internet Explorer has a bug with nested object tags, but to resolve this you can use Internet Explorer conditional statements to effectively hide the inner tag. The result looks like the following:

<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
        width="400" height="400">
    <param name="movie" value="example.swf">
    <!--[if !IE]>-->
    <object type="application/x-shockwave-flash" data="example.swf"
width="400" height="400" />
    <!--<![endif]-->
</object>

You can improve this further by adding alternative content, as in the following example:

<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
        width="400" height="400">
    <param name="movie" value="example.swf">
    <!--[if !IE]>-->
    <object type="application/x-shockwave-flash" data="example.swf"
width="400" height="400">
    <!--<![endif]-->
        <p>Alternative Content</p>
    <!--[if !IE]>-->
    </object>
    <!--<![endif]-->
</object>

Any additional parameters that you'd like to pass to the object tag you should place inside the nested object tag but outside the conditional statements, right next to the alternative content. When using the object tag to embed Flex content, you can specify a lot of different parameters, all of which are listed at the Adobe web site (http://livedocs.adobe.com/flex/3/html/help.html?content=wrapper_13.html). You'll see an example of how to use one such parameter in the next section.

Note: The Flex HTML templates use an object tag for Internet Explorer and a nested embed tag for all other browsers. Although that approach does work, it is not ideal because the embed tag is a proprietary tag that is not part of the W3C recommendation for standards.

Passing Values to Flex Applications from HTML

In earlier chapters, you learned the various ways in which a Flex application loads data. For example, you learned how to use the WebService component to load data from web services. However, a Flex application can acquire data at runtime in yet another way: you can pass values to the Flex application from HTML. Just as web services have their uses and purposes, this technique of passing values from HTML to Flex has its uses and purposes, which are generally different from those of web services (or any other remote procedure call [RPC] techniques). Typically, you will want to pass only small amounts of data to a Flex application from HTML, and the data is typically . For example, you may pass values to a Flex application telling it where the web services can be found on a specific environment (i.e., testing versus ).

Note: One major advantage to passing data to the Flex application from HTML over other techniques is that the data is available immediately when the application starts.

This technique of passing values to a Flex application from HTML is sometimes referred to as using flashvars because the object tag parameter used to pass the values to the Flex application is called flashvars. Values passed to a Flex application using the flashvars parameter must be in URL-encoded name-value pairs delimited by equals signs and ampersands exactly as they would be in a URL query string. For example, the following code embeds a Flex .swf file and uses flashvars to pass in two variables called variable1 and variable2:

<object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
        width="400" height="400">
    <param name="movie" value="example.swf">
    <!--[if !IE]>-->
    <object type="application/x-shockwave-flash" data="example.swf"
width="400" height="400">
    <!--<![endif]-->
        <param name="flashvars" value="variable1=a&variable2=b" />
        <p>Alternative Content</p>
    <!--[if !IE]>-->
    </object>
    <!--<![endif]-->
</object>

When values are passed to a Flex application via flashvars, they are available via using the parameters property of the Application instance. You can access this property from anywhere in a Flex application using . The parameters property is an associative array where the keys correspond to the names of the variables passed in through flashvars.

For example, you could use the following code to output the values passed in via the preceding HTML example:

trace(Application.application.parameters.variable1);
trace(Application.application.parameters.variable2);

Detecting Flash Player

Flex applications require that the user has Flash Player installed if he is to use the application. Not only do Flex applications require that the user has Flash Player in general, but they specifically require that the user has Flash Player 9 or later. Therefore, it is important that you account for users who might not have Flash Player installed (according to Adobe's studies, this accounts for just around 1% of users) as well as users who might have Flash Player installed, but don't have the necessary version (i.e., they need to update their Flash Player to Version 9 or later). Additionally, for users connecting via devices that don't support Flash Player (e.g., some mobile devices), you can display alternative content.

You can detect Flash Player and/or the Flash Player version in a variety of ways, ranging from client-side JavaScript to server-side scripting to ActionScript. Adobe has a Detection Kit (http://www.adobe.com/products/flashplayer/download/detection_kit) that is available as a free download (all the necessary files are also included in the Flex SDK with the HTML templates, as you'll read in " later in this chapter). The Flash Player Detection Kit is Adobe's current official solution to Flash Player detection, and it is worth downloading the kit just to read the documentation that is included because it provides a helpful context and a fair amount of useful information. However, plenty of criticisms have been leveled at the Flash Player Detection Kit, most of which are warranted. The key criticisms are as follows:

There is nothing necessarily wrong with using the Flash Player Detection Kit. However, for the reasons just mentioned, we do not recommend it as the best solution. Instead, we recommend using SWFObject, which we describe in "Using SFWFObjact" later in this chapter.

Although we don't recommend its use outright, the Flash Player Detection Kit is not without value. It contains one component that highlights an important feature of Flash Player, called Express Install. Express Install is available in Flash Player 6.0.65 and later, and it allows Flash Player to download and install the latest version of itself. That means that if a user has Flash Player 6.0.65 or later installed but she doesn't have the latest version required by your Flex application, it is possible to allow the user to download and install the latest version from within your site. The user will never have to leave the site, and once the installation is complete, the browser will restart and open to the same page the user was just on (the page with the Flex application).

Technically, Express Install is a feature of Flash Player, and it doesn't require the Flash Player Detection Kit. However, practically speaking, the detection kit provides an Install .swf file that you can use to trigger Express Install if necessary. You'd need the Flash IDE to create a custom Express Install .swf file, and for most purposes the version that is included in the detection kit is all you need.

The general process for using Express Install is as follows:

  1. Use a detection method (client-side, server-side, or Flash-based) to detect whether the user has Flash Player installed. If the user has no version of Flash Player or a version prior to 6.0.65, direct the user to alternative content.
  2. If the user has the version of Flash Player required by your Flex application, simply display the application. However, if the user has a version that is earlier than what your application requires, load the Express Install .swf file into Flash Player. This will automatically prompt the user to download and install the latest version of Flash Player right from within the browser.
  3. Once the installation is complete, the browser will restart and will open to the page that was open prior to installation. This time the detection method will detect that the user has the necessary version of Flash Player.

In the templates directory of the Flex 3 SDK or in the Flash Player Detection Kit, the Express Install .swf is called playerProductInstall.swf.

Using the Standard Templates

Flex ships with a variety of standard templates that you can use. The templates are located in the templates directory of the SDK. These templates include the following:

All of these templates use the same JavaScript file and the same markup as the Flash Player Detection Kit, which puts the templates in the same boat with the detection kit as far as the previously mentioned criticisms are concerned. Therefore, we do not recommend using the templates, except perhaps in the early stages of application development, while testing applications locally using Flex Builder.

Note: By default, when you run or debug an application in Flex Builder, you are using the templates.

Using SWFObject

SWFObject is a method for embedding Flash and Flex content in an HTML page. It uses JavaScript for Flash Player detection and for a variety of other features. SWFObject is the unofficial standard for embedding Flash and Flex content, and it is the method we recommend in this book. SWFObject is easy to use, and it uses standards-compliant markup and unobtrusive JavaScript. You can download SWFObject from http://code.google.com/p/swfobject.

The SWFObject documentation is excellent at explaining both how and why to use SWFObject, and we won't attempt to repeat too much of that here in this book. However, we'll give you a brief introduction that will provide you with enough information to get started, and you can consult the documentation for further details.

SWFObject 2.0 (the current version as of this writing) provides two ways for you to embed content: static publishing and dynamic publishing. Static publishing uses markup directly in the HTML, and it uses JavaScript to resolve issues that cannot be dealt with by markup (such as player detection). The biggest advantage of static publishing is that because it doesn't rely on JavaScript to embed the content, your Flex application will be available to browsers that don't support JavaScript or that have JavaScript disabled; devices that have poor JavaScript support; and automated tools. Dynamic publishing, on the other hand, uses JavaScript to write the Flex application to the page. The advantage to this is that it sidesteps click-to-activate issues related to the Eolas patent (note that this primarily affected Internet Explorer, and Microsoft has since reversed this change) and it allows for easier integration with scripted applications. Dynamic publishing works only if enough JavaScript support is available. Otherwise, the user will see only alternative content.

Note: The Eolas patent is a patent awarded in 1998 to Eolas Technologies, a U.S. company. The patent ostensibly covers embedding content (such as Flash Player content) in HTML pages. Eolas sued Microsoft for infringement on the patent because Microsoft didn't license its embedding technology from Eolas.

Clearly, sometimes dynamic publishing is advantageous, and we encourage you to learn more about it by reading the SWFObject documentation. However, for most purposes static publishing is a better option, and it is the option we will look at in more detail in this chapter.

To use static publishing follow these steps:

  1. Place the swfobject.js file in the same directory as your HTML page and .swf file.
  2. Use the nested object tag markup discussed earlier in this chapter to embed the Flex application.

    Your HTML page might look like the following:

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
        <head>
            <title>Flex Example</title>
            <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
        </head>
        <body>
            <div>
                <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
                        width="400" height="400">
                    <param name="movie" value="Example.swf" />
                    <!--[if !IE]>-->
                    <object type="application/x-shockwave-flash" data="Example.swf"
                            width="400" height="400">
                    <!--<![endif]-->
                        <p>This site is best viewed as a Flex application, which requires
                           Flash Player 9. For users who prefer not to use Flash Player
                           we have provided a <a href='textVersion.html'>text-only
                           version of the site</a/>.</p>
                    <!--[if !IE]>-->
                    </object>
                    <!--<![endif]-->
                </object>
            </div>
        </body>
    </html>
  3. Next, you must set an id attribute for the outer object tag:
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
        <head>
            <title>Flex Example</title>
            <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
        </head>
        <body>
            <div>
                <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
                        width="400" height="400" id="flexApplication">
                    <param name="movie" value="Example.swf" />
                    <!--[if !IE]>-->
                    <object type="application/x-shockwave-flash" data="Example.swf"
                            width="400" height="400">
                    <!--<![endif]-->
                        <p>This site is best viewed as a Flex application, which requires
                           Flash Player 9. For users who prefer not to use Flash Player
                           we have provided a <a href='textVersion.html'>text-only
                           version of the site</a/>.</p>
                    <!--[if !IE]>-->
                    </object>
                    <!--<![endif]-->
                </object>
            </div>
        </body>
    </html>
  4. Add a script tag to the head of the document. The script tag should include the SWFObject JavaScript file, as in the following code:
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
        <head>
            <title>Flex Example</title>
            <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
            <script type="text/javascript" src="swfobject.js"></script>
        </head>
        <body>
            <div>
                <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
                        width="400" height="400" id="flexApplication">
                    <param name="movie" value="Example.swf" />
                    <!--[if !IE]>-->
                    <object type="application/x-shockwave-flash" data="Example.swf"
                            width="400" height="400">
                    <!--<![endif]-->
                        <p>This site is best viewed as a Flex application, which requires
                           Flash Player 9. For users who prefer not to use Flash Player
                           we have provided a <a href='textVersion.html'>text-only
                           version of the site</a/>.</p>
                    <!--[if !IE]>-->
                    </object>
                    <!--<![endif]-->
                </object>
            </div>
        </body>
    </html>
  5. Add a second script tag following the first, and inside this script tag add a line of code that calls swfobject.registerObject(). This method requires that you specify at least two parameters: the ID of the outer object tag and the minimum required Flash Player version in the form of major.minor.release. For example, if your application requires Flash Player 9 and it does not require any later releases, you can use the string 9.0.0:
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
        <head>
            <title>Flex Example</title>
            <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
            <script type="text/javascript" src="swfobject.js"></script>
            <script type="text/javascript">
                swfobject.registerObject("flexApplication", "9.0.0");
            </script>
        </head>
        <body>
            <div>
                <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
                        width="400" height="400" id="flexApplication">
                    <param name="movie" value="Example.swf" />
                    <!--[if !IE]>-->
                    <object type="application/x-shockwave-flash" data="Example.swf"
                            width="400" height="400">
                    <!--<![endif]-->
                        <p>This site is best viewed as a Flex application, which requires
                           Flash Player 9. For users who prefer not to use Flash Player
                           we have provided a <a href='textVersion.html'>text-only
                           version of the site</a/>.</p>
                    <!--[if !IE]>-->
                    </object>
                    <!--<![endif]-->
                </object>
            </div>
        </body>
    </html>
  6. You can also optionally use Express Install with SWFObject. If you want to use Express Install, you will need to specify a third parameter for registerObject(), indicating the location of the .swf to use. You can use playerProductIinstall.swf from the Flex SDK, or you can use the expressinstall.swf file that is included with SWFObject.

Managing New Flex Application Builds

It is important to remember that when you embed Flex applications in HTML pages, web browsers will likely cache the .swf files. That is both advantageous and disadvantageous. The upside is that because a Flex application gets cached, users will not have to download the .swf file upon subsequent visits to a site. However, the downside is that when you deploy updates to the Flex application, it is possible that users will still see cached versions instead of the new version.

One of the simplest and most effective ways to deal with this caching issue is to append a variable to the name of the .swf file that you are embedding and increment that variable value with each new build. This simple strategy will ensure that users can use cached versions of the application until you deploy a new version, and at that point they will be forced to download the new version when they next visit the site. To add a variable to the .swf file reference you should add a question mark (?) and a name-value pair following the name of the .swf file. For example, if you normally reference a file as example.swf, you could instead reference the same file as example.swf?build=1. When you deploy a new build, you need only update the build variable such that the reference is now example.swf?build=2. This way, you don't have to change the name of the .swf file (simplifying build scripts and versioning), but you still can address the caching issue.

Section 20.2: Integrating with Browser Buttons and Deep Linking

Web browsers are designed primarily to render HTML content. Traditionally, web sites and web applications were built around the page metaphor whereby each page was a unique HTML file. The implications of this are important because the way in which browsers are designed to navigate the Web is built around the page metaphor. For example, browsers have Back buttons that allow users to navigate to the page they were viewing previously. Additionally, browsers inherently support a concept known as deep linking, which is simply a matter of allowing a user to navigate directly to a URL such as http://www.adobe.com/go/flex. The deep part of the deep link is the path following the domain name (/go/flex).

Users are accustomed to using the Back button and deep linking. These features are such an important part of the web experience that you may even wonder why we're mentioning them in this chapter. After all, you may think these features are incredibly obvious. However, the new generation of web applications, including Flash, Ajax, and Flex applications, breaks these features. The result is that users of these applications can feel frustrated when they habitually use the Back button or copy and paste a link and these actions don't work as expected.

Exactly how do these behaviors break in Flex applications? First, consider how the Back button works. The browser maintains a history of pages the user has viewed. When the user clicks the Back button, the browser simply goes to the previous page in the history. Flex applications don't use pages, though they may have many distinct screens or states. When the user navigates through the sections of the Flex application, she may feel that clicking the browser's Back button should move her back through the sections of the Flex application. However, because the Flex application resides in one HTML page, the default behavior of the Back button will be to simply take the user back to the previous HTML page she had viewed, and it will not navigate through sections of the Flex application.

The same issues affect deep linking as well. When a user navigates through a Flex application, she may get to a section she'd like to bookmark or email to a friend. Most users are accustomed to being able to simply add a bookmark or copy and paste the URL from the browser's address bar. Yet, as a user navigates through a Flex application, the URL in the address bar doesn't change. That means distinct sections of the Flex application don't have distinct URLs that can be bookmarked or emailed. The result is that returning to a Flex application's URL means returning to the starting point of that Flex application, not to a specific section.

The solution to the Back button and deep linking dilemmas is to use a bit of JavaScript to update the URL as the user navigates the Flex application. Every time the URL changes, the browser will register a new element in the history, which enables the Back button functionality. This also helps to provide unique URLs corresponding to different sections of the Flex application, which allows deep linking.

Normally, when you change the URL in a browser a new page loads. However, there is an exception to that rule. Changing the anchors will not cause a new page to load. An anchor in a URL is indicated by a pound sign (#). For example, consider that your Flex application is at http://www.example.com/index.html. If you update the URL with JavaScript to http://www.example.com/index.html#a, the page will not reload, but the Back button will be enabled and you also will have created a distinct, new URL that you can use to access the same Flex application.

Flex 3 includes an mx.managers.BrowserManager class that is intended to create a simple solution both for Back button integration and deep linking using the aforementioned technique. The BrowserManager solution is not as elegant as it could be, but it does work, and in the next section we'll look at how you can use it.

Working with BrowserManager

The BrowserManager class is a Singleton class for which the getInstance() method return type is set to mx.managers.IBrowserManager, an interface. That means that when you want to work with BrowserManager you should declare a variable of type and assign BrowserManager.getInstance() to that variable.

The BrowserManager instance does the following things, which are of interest in this section:

Initializing BrowserManager

When working with BrowserManager, you must always initialize the instance by calling the init() method. The init() method requires two parameters: a fragment value and a page title. The application uses the first parameter (the fragment) when the user clicks the Back button to return to a point at which the URL has no fragment. In that sense, the first parameter defines the default fragment value, though it has an effect only when the user is clicking the Back button. The second parameter sets the title of the web page in the browser. In addition, the init() method also sets the property of the application to false, which is necessary for the correct history management and deep linking to work correctly.

It may seem strange that historyManagementEnabled must be set to false for history management to work correctly. That is because is wired to the older HistoryManager, not to . BrowserManager supersedes HistoryManager, and setting to true can interfere with BrowserManager.

The following is an example of how you can use the init() method. In this example, the init() method is being called on creationComplete, which is appropriate because this method must be called before you run any other BrowserManager code.

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
creationComplete="creationCompleteHandler();">

    <mx:Script>
        <![CDATA[

            import mx.managers.IBrowserManager;
            import mx.managers.BrowserManager;

            private var _browserManager:IBrowserManager =
BrowserManager.getInstance();

            private function creationCompleteHandler():void {
                // When the user clicks the Back button to the point
                // where there is no fragment, the application will use
                // the default fragment value of example.
                _browserManager.init("example", "Example Page");
            }

        ]]>
    </mx:Script>
</mx:Application>

Setting and retrieving a URL fragment

In BrowserManager lingo, a fragment is the portion of the URL that follows the # sign. For example, in the URL http://www.example.com/#flex/page2, the fragment is . You can set the fragment using the setFragment() method. The following example will set the fragment to exampleTwo as soon as the application starts:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
creationComplete="creationCompleteHandler();" historyManagementEnabled="false">

    <mx:Script>
        <![CDATA[

            import mx.managers.IBrowserManager;
            import mx.managers.BrowserManager;

            private var _browserManager:IBrowserManager =
BrowserManager.getInstance();

            private function creationCompleteHandler():void {
                _browserManager.init("example", "Example Page");
                _browserManager.setFragment("exampleTwo");
            }

        ]]>
    </mx:Script>
</mx:Application>

On the flip side, you often will want to retrieve the current fragment value. The property of BrowserManager returns that value.

Setting the page title

You already saw how to set the page title for the browser using the init() method. However, the init() method is designed to be called just once, when the application starts. Normally, you'll want to change the page title as the user interacts with the application. You can change the page title at any time using the setTitle() method by simply passing it the title as a parameter.

Handling BrowserManager events

When working with BrowserManager, two events are of interest: applicationUrlChange and browserUrlChange. The applicationUrlChange event occurs when the URL changes programmatically, such as when it is changed via the setFragment() method. Otherwise, when the URL changes because the user clicks the Back or Forward button or because the user changes the URL in the address bar, BrowserManager dispatches the browserUrlChange event. Both events are of type mx.events.BrowserChangeEvent. Typically, you'll handle both events using the same method because most applications should behave identically in all cases regardless of how the URL changes.

Note: The browserUrlChange event does not occur when testing applications locally using Internet Explorer. However, when run from a web server, the event does get dispatched. That means that if you test your application locally using Internet Explorer while developing the application, you will not be able to use the Back and Forward buttons or deep linking features, but it will work when deployed on a web server. Consider testing using another browser, such as Firefox.

Building a Sample BrowserManager Application

In this section, we'll look at a simple example application that uses BrowserManager to enable deep linking and integration with the browser's Back and Forward buttons. The application merely consists of four simple MXML application components corresponding to four screens or pages within the application, and a navigational button bar for navigating between the screens. The four screens are called Home, Books, Authors, and Events.

We'll create these four components first. Three of the four components will consist of nothing more than a label component. One of them will contain an accordion component, and we'll later see how to integrate that into BrowserManager as well. You should define the Home screen component in HomeScreen.mxml using the code shown in Example 20-1.

Example 20-1. HomeScreen.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Label text="Thank you for visiting O'Reilly's Flex site" />
</mx:Canvas>

Next, you can define the Books screen in BooksScreen.mxml, as shown in Example 20-2.

Example 20-2. BooksScreen.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Label text="O'Reilly books catalog" />
</mx:Canvas>

The Authors screen is defined in AuthorsScreen.mxml, as shown in 20-3. This is the screen with the accordion.

Example 20-3. AuthorsScreen.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Accordion id="authorsAccordion" width="400" height="400">
        <mx:VBox label="Joey Lott" />
        <mx:VBox label="Chafic Kazoun" />
    </mx:Accordion>
</mx:Canvas>

The Events screen is defined in EventsScreen.mxml, as shown in 20-4.

Example 20-4. EventsScreen.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Label text="Events this month" />
</mx:Canvas>

Now we can assemble all the screens in the main application MXML file with a navigational button bar, as shown in 20-5.

Example 20-5. Main.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
creationComplete="creationCompleteHandler();" xmlns:local="*" currentState="Home">

    <mx:Script>
        <![CDATA[
            import mx.core.UIComponent;
            import mx.collections.ArrayCollection;
            import mx.managers.IBrowserManager;
            import mx.managers.BrowserManager;
            import mx.events.BrowserChangeEvent;
            import mx.events.ItemClickEvent;

            private var _browserManager:IBrowserManager =
BrowserManager.getInstance();
            private var _navigationData:ArrayCollection;

            private function creationCompleteHandler():void {
                _navigationData = new ArrayCollection();
                _navigationData.addItem({section: "Home",
title: "O'Reilly Publishing", component: "homeScreen"});
                _navigationData.addItem({section: "Books",
title: "O'Reilly Publishing - Our Catalog", component: "booksScreen"});
                _navigationData.addItem({section: "Authors",
title: "O'Reilly Publishing - Meet the Authors", component: "authorsScreen"});
                _navigationData.addItem({section: "Events",
title: "O'Reilly Publishing - Current Events", component: "eventsScreen"});
                navigation.dataProvider = _navigationData;
                _browserManager.init("Home", _navigationData.getItemAt(0).title);
            }

            private function itemClickHandler(event:ItemClickEvent):void {
            }

        ]]>
    </mx:Script>

    <mx:VBox>
        <mx:ToggleButtonBar id="navigation" labelField="section"
itemClick="itemClickHandler(event);" />
        <mx:Canvas id="sections" />
    </mx:VBox>

    <mx:states>
        <mx:State name="Home">
            <mx:AddChild relativeTo="{sections}">
                <local:HomeScreen id="homeScreen" />
            </mx:AddChild>
        </mx:State>
        <mx:State name="Books">
            <mx:AddChild relativeTo="{sections}">
                <local:BooksScreen id="booksScreen" />
            </mx:AddChild>
        </mx:State>
        <mx:State name="Authors">
            <mx:AddChild relativeTo="{sections}">
                <local:AuthorsScreen id="authorsScreen" />
            </mx:AddChild>
        </mx:State>
        <mx:State name="Events">
            <mx:AddChild relativeTo="{sections}">
                <local:EventsScreen id="eventsScreen" />
            </mx:AddChild>
        </mx:State>
    </mx:states>

</mx:Application>

In this code, we define an ArrayCollection called _navigationData, and we add four elements to it. Each element corresponds to a screen in the application. Each element has three properties: section, title, and component. The section corresponds to the name of the state for the screen, the title is the page title, and the component is the ID of the screen component instance. Then we assign the _navigationData collection to the dataProvider property of the ToggleButtonBar instance. This will create four buttons corresponding to the four screens.

At this point, nothing happens when you click the buttons because we haven't defined the behavior. Typically, if you wanted to change the state when the user clicked on a button, you would simply set the currentState property. However, in this case we want to route all requests for state changes through BrowserManager. That means we need to call setFragment() instead. And that means the new, revised method now looks like the following:

private function itemClickHandler(event:ItemClickEvent):void {
    _browserManager.setFragment(event.item.section);
}

This code sets the fragment to the value of the section property of the dataProvider element corresponding to the button. The result is that the fragment will be one of the following: Home, Books, Authors, or Events, which just happen to also correspond to the names of the states.

If you were to test the application at this point, you'd see that the fragment does indeed update when you click the buttons, but the application state doesn't change. To change the application state we need to handle the applicationUrlChange event. We can do that by first registering a listener for the event in the creationCompleteHandler() method with the following code:

_browserManager.addEventListener(BrowserChangeEvent.APPLICATION_URL_CHANGE,
urlChangeHandler);

Then we need only to define the urlChangeHandler() method. This new method looks like the following:

private function urlChangeHandler(event:BrowserChangeEvent):void {
    var fragment:String = _browserManager.fragment;
    var item:Object;
    for(var i:int = 0; i < _navigationData.length; i++) {
        if(_navigationData.getItemAt(i).section == fragment) {
            item  = _navigationData.getItemAt(i);
            navigation.selectedIndex = i;
        }
    }
    _browserManager.setTitle(item.title);
    currentState = item.section;
}

This code loops through the elements in the _navigationData collection to find the one that corresponds to the fragment. It then sets the page title and the current state for the application.

Note: Remember that with some browsers you will see the correct behavior only when running the application from a web server.

At this point, the application will allow you to click the buttons to navigate to different sections. However, if you try to use deep linking or the browser's Back button, you will find that neither one works. That is because the application is handling only the event. To enable the Back button and deep linking features all you need to do is handle the browserUrlChange event in the same way you handled the applicationUrlChange event. Therefore, you need to add only one line of code to the creationCompleteHandler() method:

_browserManager.addEventListener(BrowserChangeEvent.BROWSER_URL_CHANGE,
urlChangeHandler);

Enabling BrowserManager to Manage Granular States

In the preceding section, you saw how to build an application in which BrowserManager mediated all state changes at the application level. However, you might want to build an application that has state changes occurring within screens, not just between them. In this section, we'll continue the example application from the previous section. We'll enable state changes in the accordion component on the Authors screen to be managed by BrowserManager.

The first change we'll make is to change the URL fragment when the user clicks on an accordion section. Example 20-6 shows these changes to AuthorsScreen.mxml.

Example 20-6. AuthorsScreen.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml">
    <mx:Script>
        <![CDATA[
            import mx.events.BrowserChangeEvent;
            import mx.managers.IBrowserManager;
            import mx.managers.BrowserManager;

            private var _browserManager:IBrowserManager =
BrowserManager.getInstance();

            private function changeAuthorHandler(event:Event):void {
                _browserManager.setFragment("Authors/" +
authorsAccordion.selectedIndex);
            }

        ]]>
    </mx:Script>
    <mx:Accordion id="authorsAccordion" width="400" height="400"
change="changeAuthorHandler(event);">
        <mx:VBox label="Joey Lott" />
        <mx:VBox label="Chafic Kazoun" />
    </mx:Accordion>
</mx:Canvas>

You can see that when the user clicks on an accordion section the URL fragment updates to Authors/0 or Authors/1 depending on which section the user clicks.

If you run the application now, you'll see that clicking on one of the accordion sections actually causes an error. That's because Authors/0 and Authors/1 cannot be found in the navigational data at the application level. Therefore, we need to make a change to the urlChangeHandler() method in the application MXML file. Instead of simply using the fragment as is, we'll extract each piece using the slash as the delimiter. Example 20-7 shows the changes.

Example 20-7. Updated urlChangeHandler()

private function urlChangeHandler(event:BrowserChangeEvent):void {
    var fragment:Array = _browserManager.fragment.split("/");
    var item:Object;
    for(var i:int = 0; i < _navigationData.length; i++) {
        if(_navigationData.getItemAt(i).section == fragment[0]) {
            item  = _navigationData.getItemAt(i);
            navigation.selectedIndex = i;
        }
    }
    _browserManager.setTitle(item.title);
    currentState = item.section;
}

Now the application works again without error. However, it still doesn't handle state changes within the Authors screen correctly when the user clicks the Back or Forward button or uses deep linking. There are lots of strategies for how to handle setting state in these cases. We'll tackle the issue by defining an interface called IScreen that will allow fragment data to be passed to screens from the application level. Example 20-8 shows IScreen.

Example 20-8. IScreen.as

package {
    public interface IScreen {

        function setScreenFragment(value:String):void;

    }
}

Next we'll update AuthorsScreen.mxml to implement IScreen. Example 20-9 shows the new AuthorsScreen.mxml.

Example 20-9. AuthorsScreen.mxml implementing IScreen

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" implements="IScreen">
    <mx:Script>
        <![CDATA[
            import mx.events.BrowserChangeEvent;
            import mx.managers.IBrowserManager;
            import mx.managers.BrowserManager;

            [Bindable]
            private var _accordionIndex:int;

            private var _browserManager:IBrowserManager =
BrowserManager.getInstance();

            public function setScreenFragment(value:String):void {
                if(value == "") {
                    _accordionIndex = 0;
                }
                else {
                    _accordionIndex = parseInt(value);
                }
            }

            private function changeAuthorHandler(event:Event):void {
                _browserManager.setFragment("Authors/" +
authorsAccordion.selectedIndex);
            }

        ]]>
    </mx:Script>
    <mx:Accordion id="authorsAccordion" width="400" height="400"
change="changeAuthorHandler(event);" selectedIndex="{_accordionIndex}">
        <mx:VBox label="Joey Lott" />
        <mx:VBox label="Chafic Kazoun" />
    </mx:Accordion>
</mx:Canvas>

All we did was implement IScreen, and in the setScreenFragment() method we parsed the index value and assigned it to a bindable _accordionIndex property, which will set the accordion's selectedIndex correctly.

Next we need to update the urlChangeHandler() method of the application again. This time we'll test whether the selected screen implements IScreen, and if it does we'll pass along the fragment. Example 20-10 shows this new code.

Example 20-10. Updated urlChangeHandler()

private function urlChangeHandler(event:BrowserChangeEvent):void {
    var fragment:Array = _browserManager.fragment.split("/");
    var item:Object;
    for(var i:int = 0; i < _navigationData.length; i++) {
        if(_navigationData.getItemAt(i).section == fragment[0]) {
            item  = _navigationData.getItemAt(i);
            navigation.selectedIndex = i;
        }
    }
    _browserManager.setTitle(item.title);
    currentState = item.section;
    var screen:UIComponent = this[item.component];
    if(screen is IScreen) {
        (screen as IScreen).setScreenFragment(fragment[1]);
    }
}

If you test the application, you'll see that you can navigate between the accordion sections using the Back and Forward browser buttons.

Deploying BrowserManager Flex Applications

One of the big drawbacks of BrowserManager is that Adobe has tied BrowserManager to the Flex HTML templates. Therefore, if you use BrowserManager, the easiest way to deploy the application is to use the Flex HTML template. As we stated earlier in this chapter, the Flex HTML templates are not ideal, and we generally advise that you not use them, if possible. However, in this case, using the Flex templates is the simplest solution. You need to use one of the templates with history management enabled. Then, when you deploy the application, you need to deploy the HTML file, the .swf file, and the history directory containing history.js, history.css, and historyFrame.html. If you omit any of those files, the application will not work correctly.

Because we think that SWFObject is a far better way to embed Flex applications, we think it's important to explain how to use BrowserManager applications with SWFObject as well. Although it is possible, it does require that you make a few edits to the JavaScript code provided by Adobe. This is not due to any failure on the part of SWFObject, but rather because of oversights in the history.js code.

When embedding a BrowserManager application using SWFObject, you should embed the application normally. In addition to the normal HTML and JavaScript code, you'll need to include history.css and history.js. An example that does this follows:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
    <head>
        <title>Flex Example</title>
        <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
        <script type="text/javascript" src="swfobject.js"></script>
        <link rel="stylesheet" type="text/css" href="history/history.css"/>
        <script src="history/history.js" language="javascript"></script>
        <script type="text/javascript">
            swfobject.registerObject("flexApplication", "9.0.0");
        </script>
    </head>
    <body>
        <div>
            <object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
                    width="400" height="400" id="flexApplication">
                <param name="movie" value="Example.swf" />
                <!--[if !IE]>-->
                <object type="application/x-shockwave-flash" data="Example.swf"
                        width="400" height="400">
                <!--<![endif]-->
                    <param name="allowScriptAccess" value="always" />
                    <p>This site is best viewed as a Flex application, which requires
                       Flash Player 9. For users who prefer not to use Flash Player
                       we have provided a <a href='textVersion.html'>text-only
                       version of the site</a/>.</p>
                <!--[if !IE]>-->
                </object>
                <!--<![endif]-->
            </object>
        </div>
    </body>
</html>

As we already mentioned, the history.js file code is shortsighted in the way it works, and because of this you'll need to edit it for it to work with SWFObject-embedded applications. The primary error is that history.js assumes that all applications are embedded using object tags for Internet Explorer and embed tags for other browsers. That assumption doesn't work for SWFObject-embedded applications that use nested object tags. Therefore, you need to adapt the history.js code to be able to find applications embedded using nested object tags. The authors of this book don't claim to be JavaScript experts, and we don't claim that our solution is the best or most elegant, but it does work and it is simple. All you need to do is edit history.js and add a few lines of code to the getPlayer() method, as shown in Example 20-11. This code returns if it is defined. We'll set this property in the HTML file using SWFObject to retrieve the reference.

Example 20-11. Changes to getPlayer() in history.js

function getPlayer(objectId) {
         if(BrowserHistory.flexApplication != null) {
              return BrowserHistory.flexApplication;
         }
        var objectId = objectId || null;
        var player = null;
        if (browser.ie && objectId != null) {
            player = document.getElementById(objectId);
        }
        if (player == null) {
            player = document.getElementsByTagName('object')[0];
        }

        if (player == null || player.object == null) {
            player = document.getElementsByTagName('embed')[0];
        }

        return player;
    }

Next, in the HTML, we need to set BrowserHistory.flexApplication to reference the Flex application instance. We need to use SWFObject's getObjectById() method to retrieve the reference. We'll also use the addLoadEvent() method of SWFObject to ensure that we assign the reference once the application is defined in the page. Example 20-12 shows the new code.

Example 20-12. Setting BrowserHistory.flexApplication in the HTML file

<script type="text/javascript">
            swfobject.registerObject("flexApplication", "9.0.0");
            swfobject.addLoadEvent(loadEventHandler);
            function loadEventHandler() {
                BrowserHistory.flexApplication =
swfobject.getObjectById("flexApplication");
            }
        </script>

With these changes, SWFObject will work with BrowserManager. You need to make sure that when you deploy the application, you deploy the .swf file, the HTML file, swfobject.js, and the history directory with history.js, history.css, and historyFrame.html.

Section 20.3: Flash Player Security

Flash Player enforces security rules for what and how applications can access data, and you'll notice this especially when embedding a Flex application in an HTML page. Flex applications can typically access all data resources in the same domain as the .swf. For example, if the .swf is deployed to www.example.com, it can access a web service that is also deployed at www.example.com. However, access to data resources at different domains is disallowed by Flash Player unless that domain explicitly gives permission. The Flash Player security rules disallow access to data resources unless the domains match exactly, including subdomains, even if the domain names resolve to the same physical address. That means an .swf deployed at www.example.com cannot access data from test.example.com or even example.com unless the server explicitly allows access. The domain can give permission by way of a cross-domain policy file.

Note: When working with the socket class and loading data through sockets rather than through request/response mechanisms, Flash Player 9,0,115,0 and later introduce an additional socket policy file requirement. Since Flash Player security is continually evolving, we recommend that you review the latest articles at http://www.adobe.com/devnet/flashplayer/ for any changes that may impact your application.

A cross-domain policy file is an XML file that resides on the server that hosts the data resources. The format for a cross-domain policy file is as follows:

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

The root <cross-domain-policy> node can contain one or more <allow-access-from> elements. The <allow-access-from> elements specify the domains that can access the resources on the server. You can use an * wildcard in place of the subdomain, which means that any subdomain can access the data resources. For example, the following policy allows access from www.example.com, beta.example.com, test.example.com, etc.:

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

You can also use the * wildcard in place of the entire domain to allow access from all domains:

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

If the server uses HTTPS and wants to allow access to .swf files deployed on nonsecure domains, it must specify a value for the secure attribute. The following allows access to .swf files deployed at www.example.com:

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

By default, Flash Player looks for a policy file named crossdomain.xml at the root of the web server from which it is requesting the data resources. If Flash Player attempts to load an XML document from http://www.example.com/data/xml/data.xml, it will look for http://www.example.com/crossdomain.xml. If you want to set different permissions for different resources on a server, you can optionally deploy different policy files in different locations on the server. For example, a policy file located at http://www.example.com/data/xml would apply only to the resources in that directory. However, when you place policy files in nondefault locations, you must use ActionScript to load the policy file in your Flex application. The ActionScript code uses the static loadPolicyFile() method of the flash.system.Security class. The following loads a policy file:

Security.loadPolicyFile("http://www.example.com/data/xml/policy.xml");

Deploying a cross-domain policy file presupposes that you have access to the server with the data resources—or that you can persuade those with the server to deploy the policy file. In the few cases where you cannot deploy a policy file on a server whose data resources you need to utilize, you have the option of deploying a proxy file on your server. A proxy file is a file that exists on your server (a .jsp, an ASP.NET page, a ColdFusion page, a PHP page, etc.) to which your Flex application can make requests. The proxy file then makes the requests to the remote resource and relays the data back to Flash Player.

Section 20.4: Using Runtime Shared Libraries

Runtime shared libraries are a way to share assets and libraries among multiple .swf files on the same domain. This is useful when you have several .swf files that comprise an application or that span several applications deployed in the same domain in which each .swf file utilizes many common assets and/or libraries. For example, if a.swf and b.swf both utilize the same subset of 25 classes and embedded images that add up to 100 KB, the user has to download the same 100 KB twice, once for each .swf.

The theory behind runtime shared libraries involves a concept called linking. All .swf files employ one or both forms of linking: static and dynamic. By default, all linking is static. When an asset or source file is statically linked with an .swf, it is compiled into the .swf. Dynamic linking means that the asset or source file is not compiled into the .swf, but the .swf has a reference to an .swf into which it has been compiled. Through dynamic linking, you can specify certain elements that should not be compiled into an .swf to reduce the total file size of the .swf. The .swf is then linked to another .swf where the elements have been compiled. This allows you to extract common elements from two or more .swf files and place them into another .swf to which all the .swf files are linked dynamically. This new .swf is called a runtime shared library.

We can understand the benefit of runtime shared libraries by looking at the a.swf and b.swf example in more detail. In this example, a.swf is 200 KB and b.swf is 400 KB. Both .swf files are deployed on the same domain. The two .swf files happen to use of common elements. That means that if a user uses both a.swf and b.swf, she downloads 600 KB, of which 100 KB is duplicate content. Using a runtime shared library, you can introduce a new .swf, library.swf, which contains the 100 KB of common content. Although there's some overhead in creating a runtime shared library for our purposes, we'll keep the numbers simple: a.swf will now be 100 KB and b.swf will now be 300 KB. Each will be dynamically linked to library.swf, which also has to download. However, the second time the user requests library.swf it will be retrieved from client cache rather than from the server, effectively saving 100 KB.

The underlying manner in which you create and use runtime shared libraries is always the same. However, if you are working with the compiler from a command line or from a custom build script using Ant, the workflow is different from using Flex Builder, which automates a lot of the work involved with runtime shared libraries.

Creating Runtime Shared Libraries with the Command-Line Compilers

When you want to create a runtime shared library with the command-line compilers, you need to use both the mxmlc application compiler and the compc component compiler. First you must use the compc compiler to compile all the common elements into an .swc file. An .swc is an archive format, and in the case of a runtime shared library it contains two files: library.swf and catalog.xml. The .swf file contained within the .swc is the runtime shared library file. You then use the mxmlc compiler to compile the application as usual, but this time you notify the compiler to dynamically link to the runtime shared libraries.

Note: Creating runtime shared libraries is an advanced feature. You may want to return to this section only after you're comfortable creating Flex applications and you want to optimize an application or applications that would benefit from runtime shared libraries.

Using compc

Like the mxmlc compiler, the compc compiler has options that you can use to determine what gets compiled and how. The first option you'll need to specify is source-path, which tells the compiler where to look for the files you want to compile. If you are compiling classes in packages, the source-path should be the root directory of the packages. If you want to use the current directory you must still specify a value, using a dot. If you want to use more than one directory, you can list the directories delimited by spaces.

You must compile one or more classes into a runtime shared library. You have to list each class using the include-classes option. There is no option to simply include all the classes in a directory. You must list each class individually. You must list each class using the fully qualified class name, and you can list multiple classes by separating them with spaces.

You must also specify an output file when calling compc. Use the output option, and specify the path to an .swc file that you want to export. The following example compiles the class com.oreilly.programmingflex.A into an .swc file called example.swc:

compc -source-path . -include-classes com.oreilly.programmingflex.A
-output example.swc

Compiling many classes into a runtime shared library can result in a very long command. To simplify this you can use either configuration files or manifest files.

Like mxmlc, you can use configuration files with compc by specifying a load-config option. Also like mxmlc, the compc compiler automatically loads a default configuration file called flex-config.xml, and unless you want to duplicate all the contents of flex-config.xml (much of which is required), it generally is better to specify a configuration file in addition to the default by using the += operator, as in the following example:

compc -load-config+=configuration.xml

The following example configuration file is the equivalent of the earlier command, which specified the source path and output, and included classes from the command line:

<flex-config>
  <compiler>
    <source-path>
      <path-element>.</path-element>
    </source-path>
  </compiler>
  <output>example.swc</output>
  <include-classes>
    <class>com.oreilly.programmingflex.A</class>
  </include-classes>
</flex-config>

If you want to include many classes, you can simply add more <class> nodes, as in the following example:

<flex-config>
  <compiler>
    <source-path>
      <path-element>.</path-element>
    </source-path>
  </compiler>
  <output>example.swc</output>
  <include-classes>
    <class>com.oreilly.programmingflex.A</class>
    <class>com.oreilly.programmingflex.B</class>
    <class>com.oreilly.programmingflex.C</class>
    <class>com.oreilly.programmingflex.D</class>
  </include-classes>
</flex-config>

You can use manifest files to achieve the same result of simplifying the compiler command. However, manifest files also have an added benefit in that they allow you to create a namespace for components that you compile into the runtime shared library. This is more useful when the runtime shared library contains user interface components that you want to be able to add to an application using MXML tags. However, using a manifest file is not hurtful in any case, because it lets you simplify the compiler .

A manifest file is an XML file in the following format:

<?xml version="1.0"?>
<componentPackage>
    <component id="Identifier" class="ClassName"/>
</componentPackage>

The following example will tell the compiler to add classes A, B, C, and D to the library:

<?xml version="1.0"?>
<componentPackage>
    <component id="A" class="com.oreilly.programmingflex.A"/>
    <component id="B" class="com.oreilly.programmingflex.B"/>
    <component id="C" class="com.oreilly.programmingflex.C"/>
    <component id="D" class="com.oreilly.programmingflex.D"/>
</componentPackage>

Once you've defined a manifest file, you need to tell the compiler to use the file. You can achieve that with the namespace and include-namespaces options. A namespace is an identifier that you can use within your MXML documents that will map to the manifest file contents. The namespace option requires that you specify two values: first the namespace identifier and then the manifest file to which the identifier corresponds. The include-namespaces option requires that you list all the identifiers for which you want to compile the contents into the .swc file. The following example compiles the classes specified in manifest.xml into the .swc:

compc -namespace http://oreilly.com/programmingflex manifest.xml
-include-namespaces http://oreilly.com/programmingflex -output example.swc

You can also combine the use of a manifest file with a configuration file. The following configuration file uses the manifest file:

<flex-config xmlns="http://www.adobe.com/2006/flex-config">
  <compiler>
    <source-path>
      <path-element>.</path-element>
    </source-path>
    <namespaces>
      <namespace>
        <uri>http://oreilly.com/programmingflex</uri>
        <manifest>manifest.xml</manifest>
      </namespace>
    </namespaces>
  </compiler>
  <output>example.swc</output>
  <include-namespaces>
    <uri>http://oreilly.com/programmingflex</uri>
  </include-namespaces>
</flex-config>

When you use a runtime shared library, you'll need two files: the .swc and the library .swf file contained within the .swc file. You need the .swc file because the mxmlc compiler uses the .swc file to determine which classes to dynamically link. You need the .swf file because it's the file you deploy with the application and from which the application loads the libraries. The SWC format is an archive format—essentially a ZIP format. You can use any standard unzip utility to extract the .swf file from the .swc. The .swc always contains a file called library.swf that you should extract and place in the deploy directory for the application. If you plan to use several runtime shared libraries with an application, you need to either place the library.swf files in different subdirectories or rename the files.

Compiling an application using a runtime shared library

Once you've compiled an .swc file containing a runtime shared library and extracted the .swf file, you next need to compile the application that uses the library. To accomplish that you'll use mxmlc in much the same way as you'd compile an application that uses only static linking. However, when you use a runtime shared library, you need to dynamically link the relevant classes in the main application and tell the application where to find the runtime shared library .swf file at runtime. The external-library-path option specifies the .swc file or files that tell the compiler which classes to dynamically link. Use the runtime-shared-libraries option to tell the compiler where it can find the runtime shared library file(s) at runtime. The following tells the compiler to compile the application using example.swc for dynamic linking and example.swf as the URL for the shared library:

mxmlc -external-library-path=example.swc
-runtime-shared-libraries=example.swf Example.mxml

You can use configuration files for these purposes as well. The following configuration file achieves the same result as the preceding command:

<flex-config>
  <compiler>
    <external-library-path>
      <path-element>example.swc</path-element>
    </external-library-path>
  </compiler>
  <file-specs>
    <path-element>RSLClientTest.mxml</path-element>
  </file-specs>
  <runtime-shared-libraries>
    <url>example.swf</url>
  </runtime-shared-libraries>
</flex-config>

When you deploy the application, you must also deploy the runtime shared library .swf file. You do not need to deploy the .swc file along with the rest of your application.

Using Ant to build runtime shared library applications

As you've seen, building an application that uses a runtime shared library requires quite a few steps. To summarize:

  1. Compile the .swc.
  2. Extract the .swf.
  3. Move the .swf.
  4. Compile the application.

Using Ant can simplify things because you can write just one script that will run all the tasks. The following is an example of such a script:

<?xml version="1.0"?>
<project name="RSLExample" basedir="./">

  <property name="mxmlc" value="C:\FlexSDK\bin\mxmlc.exe"/>
  <property name="compc" value="C:\FlexSDK\bin\compc.exe"/>

  <target name="compileRSL">
    <exec executable="${compc}">
      <arg line="-load-config+=rsl/configuration.xml" />
    </exec>
    <mkdir dir="application/rsl" />
    <move file="example.swc" todir="application/rsl" />
    <unzip src="application/rsl/example.swc" dest="application/rsl/" />
  </target>

  <target name="compileApplication">
    <exec executable="${mxmlc}">
      <arg line="-load-config+=application/configuration.xml" />
    </exec>
  </target>

  <target name="compileAll" depends="compileRSL,compileApplication">
  </target>

</project>

Using Flex Builder to Build Runtime Shared Libraries

Flex Builder automates a lot of the tasks and provides dialog boxes for steps to create and use runtime shared libraries. Working with runtime shared libraries in Flex Builder comprises two basic steps: creating a Flex Library Project and linking your main application to the library project.

Creating a Flex Library Project

The first step in creating a Flex Library Project is to create the project by selecting File→New→Flex Library Project. Every Flex Library Project needs to have at least one element—generally a class. You can add classes to the project as you would any standard Flex project. Once you've defined all the files for the project, you'll next need to tell Flex Builder which of those classes to compile into the .swc file. You can do that by way of the project properties. You can access the properties by selecting Project→Properties. Then select the Flex Library Build Path option from the menu on the left of the dialog. In the Classes tab you should select every class that you want to compile into the library. This is all that is necessary to create a Flex Library Project.

Linking an application to a library

When you want to use a library from a Flex application, you need to tell Flex Builder to link to the corresponding Flex Library Project. You can accomplish this by selecting Project→Properties for the Flex project. Then select the Flex Build Path option from the menu in the dialog, and select the "Library path" tab. Within the "Library path" tab you click the Add Project button. This opens a new dialog that prompts you to select the Flex Library Project you want to link to your application. When you select the library project and click OK, the project will show up in the "Library path" tab list. By default, libraries are statically linked rather than dynamically linked. You must tell Flex Builder to dynamically link the library by expanding the library project icon in the list, selecting the Link Type option and then selecting the Runtime Shared Library (RSL) option from the menu.

When you add a library project to the library path for a Flex project, the application can use any of the classes defined in the library project.

Adding Nonclass Assets to Runtime Shared Libraries

Runtime shared libraries do not directly allow you to dynamically link anything other than classes. That means you cannot directly add a dynamic link to an asset such as an image, a sound, or a font. However, if you can embed an asset in an ActionScript class, you can add indirect dynamic linking. (See Chapter 11 for more details on general embedding.) The following example embeds an image using a class constant:

package com.oreilly.programmingflex {
  public class Images {

      [Embed(source="image.jpg")]
      public static const IMAGE_A:Class;

  }
}

You can compile such a class into a runtime shared library, and the asset (an image in this case) is also embedded into the runtime shared library. The following example illustrates how you could use the dynamically linked image from an application:

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

  <mx:Script>
    <![CDATA[

      import com.oreilly.programmingflex.Images;

    ]]>
  </mx:Script>
  <mx:VBox>
    <mx:Image source="{Images.IMAGE_A}" scaleContent="true"
              width="100" height="100" />
  </mx:VBox>
</mx:Application>

Section 20.5: Summary

In this chapter, you learned how to embed Flex applications in HTML pages. You learned how to use the industry-standard SWFObject to embed Flex applications using the static publishing method employing standards-compliant nested object tags. You also learned how to enable deep linking within Flex applications and how to integrate an application with the browser's Back and Forward buttons using BrowserManager. Furthermore, you learned how to work with cross-domain loading issues, and you learned about creating runtime shared libraries to improve loading times on applications sharing common libraries and assets.

This excerpt is from Programming Flex 3. If you want to try your hand at developing rich Internet applications with Adobe's Flex 3, and already have experience with frameworks such as .NET or Java, this is the ideal book to get you started. Programming Flex 3 gives you a solid understanding of Flex 3's core concepts, and valuable insight into how, why, and when to use specific Flex features. Learn to get the most from this amazing and sophisticated technology.

buy button

Copyright © 2009 O'Reilly Media, Inc.