Chapter 7. HTML in AIR

 

This chapter covers

  • Loading and displaying HTML
  • Controlling the loading of HTML
  • Referencing JavaScript objects from ActionScript
  • Referencing ActionScript from JavaScript

 

Normally, we try to temper our enthusiasm for a particular AIR feature with calm reason. But in the case of HTML in AIR, the feature is just too darn cool for us to bottle up all our excitement. In this chapter, we’ll share with you what we know about working with HTML in AIR, and once you’ve reviewed the facts for yourself, we think you’ll share our enthusiasm.

As a Flash or Flex developer, you’ve undoubtedly placed many an .swf file in an HTML page. Wouldn’t it be neat if you could do the inverse: render HTML inside a Flash or Flex application? Using AIR, you can do exactly that, because AIR includes the WebKit (www.webkit.org) web browser engine, the same engine that drives the popular cross-platform browser, Safari. The WebKit engine allows AIR applications to render HTML and execute JavaScript almost exactly as it would in a standard web browser. But what makes this feature even better is that, when the HTML is rendered in the AIR application, it is not only interactive, as HTML in a browser would be, but is also treated as a display object in the AIR application. That means you can do all the things you can do with a display object, including scaling, rotating, blurring, masking, and so forth.

Throughout this chapter, we’ll look at important topics relevant to working with HTML in AIR applications. Initially we’ll look at the basics, such as how to render and display HTML content in an AIR application. We’ll also talk about how to work with the HTML content to do things such as scroll, navigate history, and interact with JavaScript.

7.1. Displaying HTML in AIR

The first thing you need to know when working with HTML is how to load it, render it, and display it in an AIR application. AIR makes all of this remarkably simple. All you have to do is create an instance of a native class called flash.html.HTMLLoader and tell it where to find the HTML to render.

If you’re creating an AIR application using Flash CS3, you’ll only ever work with HTMLLoader instances, and you should read the next subsection (section 7.1.1) but can skip section 7.1.3. If you’re creating AIR applications using Flex, you’ll find that reading both of these subsections will be useful to you. Although Flex allows you to use a component to work with HTML, that component still uses HTMLLoader behind the scenes, and the more you understand about the lower-level HTMLLoader class, the better.

7.1.1. Using native Flash HTML display objects

The flash.html.HTMLLoader class provides practically everything you need for working with HTML in an AIR application. This class is a display object type, meaning you can add instances to the display list. The HTMLLoader class also defines functionality that allows you to tell it where to load HTML content, and it then knows how to render the content.

There are two basic ways you can tell an HTMLLoader object what HTML to render:

  • Use the load() method to tell the HTMLLoader object to load HTML from a resource such as a page from a server.
  • Use the loadString() method to pass the HTMLLoader object a string of HTML to render.

As you might imagine, it’s much more common to load HTML from a resource, so we’ll look at how to do that first. The load() method requires just one parameter: a flash.net.URLRequest object specifying where the HTMLLoader object can find the HTML to load. The following example illustrates how simple it is to load and display HTML content. Listing 7.1 loads an HTML page from www.manning.com and renders it in an HTMLLoader object.

Listing 7.1. Loading and rendering HTML pages using HTMLLoader
package com.manning.airinaction.html {

   import flash.display.MovieClip;
   import flash.html.HTMLLoader;
   import flash.net.URLRequest;

   public class Main extends MovieClip {

      private var _htmlLoader:HTMLLoader;

      public function Main() {
         _htmlLoader = new HTMLLoader();
         _htmlLoader.width = stage.stageWidth;
         _htmlLoader.height = stage.stageHeight;
         addChild(_htmlLoader);
         _htmlLoader.load(new URLRequest("http://www.manning.com/lott"));
      }
   }
}

The results of the above code are shown in figure 7.1.

Figure 7.1. Loading an HTML page into an HTMLLoader object using the load() method

As you can see in this example, because the HTMLLoader class is a display object type, it has standard display object properties such as width and height, and you can work with those properties as you would any other display object. In this example, we set the width and height of the HTMLLoader object, but we could also set things such as the rotation, alpha, and filters (blur, drop shadow, and so on).

As we mentioned earlier, you can also “load” HTML by programmatically assigning strings to an HTMLLoader. You can do that using the loadString() method, as shown in listing 7.2.

Listing 7.2. Rendering HTML strings as well
package com.manning.airinaction.html {

   import flash.display.MovieClip;
   import flash.html.HTMLLoader;
   import flash.net.URLRequest;

   public class Main extends MovieClip {

      private var _htmlLoader:HTMLLoader;

      public function Main() {
         _htmlLoader = new HTMLLoader();
         _htmlLoader.width = stage.stageWidth;
         _htmlLoader.height = stage.stageHeight;
         addChild(_htmlLoader);
         _htmlLoader.loadString("<html><body><h1>HTML in AIR</h1> </body></html>");
      }
   }
}

The result of this code is shown in figure 7.2.

Figure 7.2. Rendering HTML strings using the loadString() method

The loadString() method is useful when you want to programmatically dictate the HTML that you’d like the AIR application to render. For example, you could create HTML templates that you store in files in the AIR application directory, and you can programmatically load the HTML templates, parse them, assign values to variables within the templates, and then assign the HTML to an HTMLLoader object using loadString().

7.1.2. Loading PDF content

AIR can use the Adobe Acrobat 8.1 or higher plug-in to render PDF content. If Acrobat Reader 8.1 or higher is installed on the system, loading and rendering a PDF is identical to loading and rendering HTML. All you need to do is specify the PDF URI when requesting the content to load into the HTMLLoader instance. However, if the system doesn’t have Acrobat Reader 8.1 or higher, the operation won’t work. Therefore, AIR provides a way for you to test whether the system is capable of rendering PDF content via the static HTMLLoader.pdfCapability property.

The HTMLLoader.pdfCapability property returns one of four values. These values map to four constants of the flash.html.HTMLPDFCapability class: STATUS_OK, ERROR_INSTALLED_READER_NOT_FOUND, ERROR_INSTALLED_READER_TOO_OLD, ERROR_PREFERRED_READER_TOO_OLD. If the value is STATUS_OK, then AIR will be able to render the PDF. Otherwise, AIR can’t render the PDF, for various reasons such as these: no Acrobat Reader is found, an outdated version is found, or the user has set his preferences to use an older version even if he has 8.1 or higher.

7.1.3. Using the Flex component

When you’re building Flex-based AIR applications, you’ll usually want to use a Flex component in place of a standard display object. For example, for Flex applications, you use a Flex Text component to display text instead of using a lower-level TextField object directly. The same is true when working with HTML in a Flex application: instead of using an HTMLLoader object directly, you’ll use a Flex component instead. In this case, the Flex component is called HTML.

Just as a Text component is a wrapper for a lower-level TextField object, the HTML component is a wrapper for an HTMLLoader object. As such, the HTML component enables exactly the same sorts of behavior as an HTMLLoader object. The difference is twofold:

  • HTML components are Flex components and can be added to other Flex components (nested in containers).
  • The HTML component has a slightly different API than HTMLLoader.

When you want to load content into an HTML component, you should use the location property. Assigning a value to the location property causes the component to automatically request, load, and render the content. For example, the code in listing 7.3 loads and renders the same web page as from listing 7.1.

Listing 7.3. The location property specifies what HTML to display
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
   layout="absolute" width="350">
   <mx:HTML width="100%" height="100%"
      location="http://www.manning.com/lott" />
</mx:WindowedApplication>

The result of this code is shown in figure 7.3.

Figure 7.3. Using an HTML component, we can render HTML content in a Flex application.

If you’re paying careful attention, you might detect a difference between the results of listing 7.1 and 7.3. The difference is that, when we use an HTML component, Flex automatically adds scrollbars to the content for us. Contrast that with using the lower-level HTMLLoader class, where we don’t get free scrollbars. Don’t worry, though. In the next section, we’ll talk about how to scroll HTML content. First we have more to discuss about using the HTML component.

You might be asking yourself how you can use the HTML component to render HTML strings in a way that’s analogous to using the loadString() method of an HTMLLoader object. The answer is that the HTML component doesn’t directly provide any such functionality. However, the HTML component does provide access to the HTMLLoader instance it wraps via an htmlLoader property. That means you can use that property to gain a reference to the HTMLLoader object, and you can then call the loadString() method on that object as shown in listing 7.4.

Listing 7.4. Accessing the HTMLLoader object using the htmlLoader property
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml"
   layout="absolute" width="350"
   creationComplete="creationCompleteHandler();">
   <mx:Script>
      <![CDATA[

         private function creationCompleteHandler():void {
            html.htmlLoader.loadString("<html><body> <h1>HTML in AIR</h1></body></html>");
         }
      ]]>
   </mx:Script>
   <mx:HTML id="html" width="100%" height="100%" />
</mx:WindowedApplication>

You can use the HTMLLoader object nested in an HTML component to do anything you’d do with any other HTMLLoader object. That means pretty much anything you learn in this chapter that relates to an HTMLLoader object is also applicable, albeit indirectly, to an HTML component if you access its underlying HTMLLoader object.

You’ve just had a chance to see how to load and render HTML in an AIR application. You may be thinking to yourself that, while loading and rendering HTML is remarkably simple, there must be a catch. Perhaps, for example, AIR shortchanges you on the ability to control aspects of loading HTML. No, you’d be wrong if you thought that. As we’ll see in the next section, AIR allows you to control various aspects of how the AIR application actually loads HTML.

7.2. Controlling how AIR loads HTML

More often than not, you don’t need to concern yourself with the details of how an AIR application loads HTML content. Generally, the default settings will work for most HTML content that your application loads. But there are cases when you’ll want to slightly alter the way in which the application requests and handles HTML content. For example, you may want to ensure that the application always retrieves content from a server rather than drawing from local cache, or you may want to automate user authentication. In the following sections, we’ll look at how you can do these things and more. All of the properties described in the following sections apply to HTMLLoader objects, and you can apply them to HTML components via the internal HTMLLoader object.

7.2.1. Controlling content caching

By default, AIR caches content that applications request via an HTMLLoader object (or HTML component). That means that, when the user views HTML content in an AIR application, copies of the HTML, images, and more are stored locally; when the AIR application requests any of those resources again, it uses the local cached versions unless they’re out of date. Caching content is standard behavior for web browsers, and it can help improve how quickly content loads for users on subsequent visits to the same pages, because the necessary resources are stored locally instead of remotely.

Even though content caching is useful, it’s not always the behavior that you want. Sometimes you want to ensure that users are always viewing the content from a server to guarantee they’re seeing the latest version. You can control content caching in two ways:

  • Tell AIR whether to cache content.
  • Tell AIR whether to use previously cached content.

If you’d like to instruct an HTMLLoader object not to cache content, you should set the cacheResponse property to false before calling the load() method. The default value of the property is true, which means the content for the object will be cached.

If you’d like to instruct an HTMLLoader object not to use content that was previously cached, then set the useCache property to false prior to calling the load() method. The default value for the property is true, which means the object will read previously cached content if it exists.

7.2.2. Controlling authentication

Sometimes a server requires authentication to grant access to certain content. For example, on many servers, it’s possible to use an .htaccess file to challenge a user for credentials when she tries to access a particular directory. In such situations, AIR applications default to displaying a dialog to the user that requests a username and password, allowing the user to authenticate herself. If you’d like to disable that behavior, effectively not allowing the user to authenticate herself, then you can do that using the authenticate property of an HTMLLoader object. Simply set the authenticate property of an HTMLLoader object to false prior to calling load(), and if the server issues a challenge, the AIR application won’t display a dialog to the user. Instead the server will return an error.

7.2.3. Specifying a user agent type

An application that requests web content from a server is called a user agent. When a user agent makes a request to a server for web content, it sends along information identifying itself. A user agent identifies itself by providing a piece of information called userAgent. Some scripts use userAgent to determine what content to provide or how to display the content (or whether to display the content at all). For this reason, user agent–spoofing is not uncommon. User agent–spoofing involves a user agent providing false identification in order to appear to be a different type of user agent. For example, one web browser could appear to be another type simply by providing a different value for userAgent.

When you make a request for HTML content using HTMLLoader, you can spoof the user agent as well by setting the userAgent property of the HTMLLoader object. The HTML component also allows you to set the userAgent property directly.

7.2.4. Managing persistent data

Some web pages store persistent data in local files managed by the web browser. These files are called cookies. Some pages rely on the use of cookies in order to function. By default, AIR stores cookies for pages that you view in an AIR application. But there are various reasons why you might want to disable cookies for HTML content in AIR. One basic reason is that you may want to give the user of your application the option to disable cookies because he might not like the idea of web pages storing information locally on his computer. Whatever the reason, you can explicitly control whether or not AIR stores cookies using the manageCookies property of an HTMLLoader object. The default value is true, and a value of false will disable cookies.

7.2.5. Setting defaults

Thus far in this section, we’ve looked at ways you can affect each individual HTMLLoader object. In most cases, we made reference to the default values that an AIR application uses if you don’t explicitly set a property. For example, we said that the default value of the useCache property is true. While that’s mostly true, it’s not entirely true. That’s because you can specify the default values that an AIR application should use for the following properties: authenticate, useCache, cacheResponse, userAgent, and manageCookies. To set the defaults, just assign values to the properties of the same names of the flash.net.URLRequestDefaults class. (All the properties of the class are static.) For example, you can disable caching by default with the following code:

URLRequestDefaults.useCache = false;

All HTMLLoader objects get their default values from URLRequestDefaults. That means that, if an HTMLLoader object’s useCache property is null, it will use the value from URLRequestDefaults. However, you can always override the default values by setting the values of the properties of an HTMLLoader object explicitly.

Now that we’ve seen how you can control the way in which AIR loads HTML, we’ll next look at what you can do with the content once it’s loaded. Namely, we’ll look at how to control scrolling of content.

7.3. Scrolling HTML content

Sometimes HTML content is larger than the area in which you’re trying to display it. For example, you may set the height of an HTMLLoader object to 500, but the content might be 1000 pixels. In these cases, you’ll most likely want to allow the user to scroll the content. In this section, we’ll look at the various issues involving scrolling HTML content in AIR applications.

7.3.1. Scrolling HTML in Flex

As we’ve seen earlier in this chapter, the Flex HTML component automatically adds scrollbars to HTML content when necessary. Therefore, if you want scrollbars to appear when the content is larger than the HTML component, you need do nothing more. If you want to control the scrollbars more explicitly, you can use the horizontal-ScrollPolicy and verticalScrollPolicy properties. These properties are standard to many Flex components with built-in scrollbars, including TextArea and List. Possible values for these properties are auto (default), on, and off. When set to on, the scrollbars are always visible, even when no scrolling is possible. When set to off, the scrollbars are never visible.

 

Note

HTMLLoader (which underlies the HTML component) automatically allows for vertical scrolling using the scroll wheel on a mouse. Even if you disable scrollbars on an HTML component, the user will still be able to scroll using the scroll wheel.

 

Because scrolling is built in to the HTML component, there isn’t much else that you need to know about scrolling if you’re working with Flex. If you’re interested in understanding the lower-level scrolling mechanisms or if you’re building AIR applications using Flash, then continue with the next few sections.

7.3.2. Scrolling HTML content using ActionScript

When you use an HTMLLoader object, you don’t get built-in scrollbars. But with a small amount of ActionScript, you can scroll content in an HTMLLoader object, as we’ll see in this section.

The width and height properties of an HTMLLoader object determine the size of the container, but they don’t tell you anything about the content of the container. Although the dimensions of the container are important for scrolling, they’re only part of what you need to know. You also need to know the dimensions of the content. How can you determine the width and height of the content of an HTMLLoader object? AIR makes this nearly as simple as querying the dimensions of the container. All you need to do is read the values of the contentWidth and contentHeight properties. But there’s a catch: you must wait until the content has loaded before you can get an accurate reading for the contentWidth and contentHeight properties. That raises the question: how can you know when the content has loaded? The answer is that you must wait until the HTMLLoader object dispatches a complete event. Once the complete event is dispatched, you can read the values of contentWidth and content-Height to determine the dimensions of the content loaded into the HTMLLoader object. If the contentWidth is greater than the width property, you know that horizontal scrolling is necessary. If the contentHeight is greater than the height property, you know that vertical scrolling is necessary.

Once you’ve determined whether scrolling is necessary, the next thing you need to do is actually enable the scrolling behavior. You can programmatically scroll content in an HTMLLoader object using the scrollH and scrollV properties. The scrollH property controls horizontal scrolling and the scrollV property controls vertical scrolling. A value of 0 in either case means the content is aligned with the container. Positive values cause the content to scroll down and to the right by as many pixels. For example, if you set scrollH to 50, the content will scroll 50 pixels to the right. You can determine the range of scrolling values by using the difference between the dimensions of the content and the container. For example, if you find the difference between contentWidth and width, that will tell you the maximum value for scrollH.

Next we’ll look at an example. Listing 7.5. shows the code that adds scrollbars that allow a user to scroll the content of an HTMLLoader object.

Listing 7.5. Scrolling content using scrollH and scrollV

In this example, we use two scrollbars to allow the user to scroll vertically and horizontally. As we learned earlier, we need to wait for the complete event before making scrolling calculations. Once the complete event occurs, we can set the scroll properties of the scrollbars based on the values of the content dimensions. When the user moves the scrollbars, we can update the scrollV and scrollH properties . In this example, we also introduce one new item, which is the scroll event. As you can see, we listen for the scroll event that the HTMLLoader object dispatches. We want to do that because the user might also scroll the content vertically using the mouse scroll wheel. If that occurs, we want to keep the value of the vertical scrollbar in sync . The result of this code is shown in figure 7.4.

Figure 7.4. Adding scrollbars to control the scrolling of HTML content

We’ve just seen how to programmatically control scrolling. Next we’ll see how you can ask AIR to automatically add scrollbars in one specific scenario.

7.3.3. Creating autoscrolling windows

There are some times when you want to launch HTML content in a new AIR window. In those cases, it’s possible to have AIR launch a new window and add scrollbars automatically all from one method. The HTMLLoader class has a static method called create-RootWindow() that does just this.

The createRootWindow() method creates a new HTMLLoader object and returns it. It also launches a new AIR window and adds the HTMLLoader object to the window. All you need to do then is call the load() method of the HTMLLoader object in order to load content. The following is an example of how you can use the method:

var htmlLoader:HTMLLoader = HTMLLoader.createRootWindow();
htmlLoader.load(new URLRequest("http://www.manning.com/lott"));

That code opens www.manning.com/lott in a new AIR window, and it automatically adds scrollbars as necessary.

The createRootWindow() method allows you to pass it a few optional parameters as well. The parameters are as follows:

  • visible A Boolean value indicating whether the window is initially visible.
  • windowInitOptions A NativeWindowInitOptions object. See chapter 2 for more information on NativeWindowInitOptions objects.
  • scrollBarsVisible A Boolean value indicating whether the scrollbars should be visible.
  • bounds A flash.geom.Rectangle object that allows you to set the x and y coordinates as well as the width and height of the new window.

The following opens a new 250-by-250-pixel window in the upper-left corner of the screen:

var htmlLoader:HTMLLoader = HTMLLoader.createRootWindow(true, null, true, new Rectangle(0, 0, 250, 250));
htmlLoader.load(new URLRequest("http://www.manning.com/lott"));

Now that we’ve wrapped up scrolling HTML content, we’ll move on to navigating HTML history.

7.4. Navigating HTML history

When you use a standard web browser, you’re probably used to navigating the history using the Back and Forward buttons. For example, when you click through to a page only to discover it wasn’t the page you thought it was, you probably click the browser’s Back button to go back to the page you were looking at previously. Up to this point, we haven’t seen this sort of functionality in AIR, but that’s not because it’s impossible. With just a bit of code, you can allow users to navigate their browsing history. All the properties and methods of this section apply equally to both HTMLLoader objects and HTML components.

HTMLLoader and HTML both keep track of the user’s browsing history using flash.html.HTMLHistoryItem objects. HTMLHistoryItem objects contain the following properties:

  • url The URL of the page.
  • originalUrl Sometimes pages are redirected, and the value of the url property might be different from the URL to which AIR originally navigated. The originalUrl property reports the value of the URL to which AIR navigated before any redirects. If there were no redirects, originalUrl will have the same value as url.
  • title The title of the page.
  • isPost A Boolean value indicating whether any POST data was submitted to the page.

HTMLLoader and HTML objects have historyLength properties that report the length of the history. That tells you how many HTMLHistoryItem objects they contain. You can use the getHistoryAt() method to request an HTMLHistoryItem with a specific index. Assuming there’s at least one history item, calling getHistoryAt() with an index of 0 returns the oldest history item, while calling getHistoryAt() with an index value equal to one less than the value of historyLength returns the most recent history item.

You can also navigate through the history relatively using the historyBack(), historyForward(), and historyGo() methods. The historyBack() method goes to the previous page in history, while the historyForward() method goes to the next page in history. (This is possible only if AIR has already navigated back.) The historyGo() method allows you to navigate in steps other than one. For example, if you want to navigate back two pages in history, you can call historyGo() with a value of –2. Calling historyGo() with negative values goes back, while calling historyGo() with positive values goes forward.

Next we’ll build an example that allows a user to navigate the browsing history. Figure 7.5 shows what this application looks like. Note that we’re adding Previous and Next buttons as well as an address bar.

Figure 7.5. Allowing a user to navigate browsing history

Listing 7.6 shows the code that we’re using to create this application. Most of the code is the same as listing 7.5, and the new code is shown in bold. Note that, in this example, we’re assuming that _next, _previous, and _go are button component instances, that _htmlUrl is a text input component instance, and that all of the components have been added to the stage in Flash.

Listing 7.6. Navigating history using historyBack() and historyForward()
package com.manning.airinaction.html {

   import flash.display.MovieClip;
   import flash.html.HTMLLoader;
   import flash.net.URLRequest;
   import fl.controls.ScrollBar;
   import flash.events.Event;
   import flash.events.MouseEvent;

   public class Main extends MovieClip {

      private var _htmlLoader:HTMLLoader;
      private var _scrollBarH:ScrollBar;
      private var _scrollBarV:ScrollBar;

      public function Main() {
         _scrollBarV = new ScrollBar();
         _scrollBarV.height = stage.stageHeight - 16 - _previous.height;
         _scrollBarV.y = _previous.height;
         _scrollBarV.x = stage.stageWidth - 16;
         _scrollBarV.addEventListener(Event.SCROLL,
                             scrollVerticalHandler);
         addChild(_scrollBarV);

         _scrollBarH = new ScrollBar();
         _scrollBarH.direction = "horizontal";
         _scrollBarH.width = stage.stageWidth - 16;
         _scrollBarH.x = 0;
         _scrollBarH.y = stage.stageHeight - 16;
         _scrollBarH.addEventListener(Event.SCROLL,
                             scrollHorizontalHandler);
         addChild(_scrollBarH);

         _htmlLoader = new HTMLLoader();
         _htmlLoader.width = stage.stageWidth - 16;
         _htmlLoader.height = stage.stageHeight - 16 - _previous.height;
         _htmlLoader.y = _previous.height;
         _htmlLoader.addEventListener(Event.COMPLETE, completeHandler);
         _htmlLoader.addEventListener(Event.SCROLL, scrollHandler);
         addChild(_htmlLoader);

         _previous.addEventListener(MouseEvent.CLICK, previousHandler);
         _next.addEventListener(MouseEvent.CLICK, nextHandler);
         _go.addEventListener(MouseEvent.CLICK, goHandler);
         _htmlUrl.text = "http://www.manning.com/lott";
         goHandler();
      }

      private function completeHandler(event:Event):void {
         _htmlUrl.text = _htmlLoader.location;
         _htmlLoader.scrollH = 0;
         _htmlLoader.scrollV = 0;
         _scrollBarV.enabled = _htmlLoader.contentHeight > _htmlLoader.height;
         _scrollBarH.enabled = _htmlLoader.contentWidth > _htmlLoader.width;
         _scrollBarV.setScrollProperties(_htmlLoader.height, 0, _htmlLoader.contentHeight - _htmlLoader.height);
         _scrollBarH.setScrollProperties(_htmlLoader.width, 0, _htmlLoader.contentWidth - _htmlLoader.width);
      }
      private function scrollVerticalHandler(event:Event):void {
         _htmlLoader.scrollV = _scrollBarV.scrollPosition;
      }

      private function scrollHorizontalHandler(event:Event):void {
         _htmlLoader.scrollH = _scrollBarH.scrollPosition;
      }

      private function scrollHandler(event:Event):void {
         _scrollBarV.scrollPosition = _htmlLoader.scrollV;
      }

      private function goHandler(event:MouseEvent = null):void {
         _htmlLoader.load(new URLRequest(_htmlUrl.text));
      }

      private function previousHandler(event:MouseEvent):void {
         _htmlLoader.historyBack();
      }

      private function nextHandler(event:MouseEvent):void {
         _htmlLoader.historyForward();
      }
   }
}

All that this code does is call the historyBack() and historyForward() methods when the user clicks on the corresponding buttons. This causes the HTMLLoader object to navigate the browsing history.

Now that you’re an expert on navigating browsing history, you’re probably wondering what new challenges are ahead. Up next, we’re going to look at how you can communicate between ActionScript and JavaScript when you load HTML content into an AIR application.

7.5. Interacting with JavaScript

AIR isn’t content to merely load HTML and render it. Once the content has loaded and rendered, you can still interact with it, and it can interact with the AIR application. That’s because the HTMLLoader object that loads the HTML exposes the entire HTML document object model (DOM). In the next few sections, we’ll look at a variety of ways in which you can cause ActionScript and JavaScript interaction.

7.5.1. Controlling HTML/JavaScript elements from ActionScript

When you load HTML content into an HTMLLoader object, the entire DOM is available via a window property. The window property of an HTMLLoader object maps directly to the JavaScript window property inside an HTML page. That means you can address all the elements of an HTML page using the HTMLLoader object’s window property, just as you would from within an HTML page using the JavaScript window property. One caveat when accessing the HTML DOM is that you must wait until the page has loaded before you try to access it.

For the next few examples, we’ll use the HTML shown in listing 7.7. For the purpose of our examples, this code is saved in a document called example.html.

Listing 7.7. HTML code saved in example.html
<html>
   <script>

      var pageTitle = "Example";
      var description = "This is an example HTML page";

      function showAlert() {
         alert(description);
      }

   </script>
   <body>
      <p id="p1">
         HTML in AIR
      </p>
      <button onclick="showAlert()">Click</button>
   </body>
</html>

As you can see, this HTML code merely does the following:

  • Defines two JavaScript variables called pageTitle and description
  • Defines a function called showAlert() that displays an alert window with the value of the description variable
  • Creates a p tag with an id of p1
  • Creates a button that calls showAlert() when clicked

First we’ll look at how to retrieve the value of a JavaScript variable from ActionScript. Because the JavaScript variables are created as properties of the window object, all we need to do is wait until the complete event occurs and then read the value of the corresponding property of the window object. In listing 7.8, we read the value of the pageTitle property and display it in the window title bar.

Listing 7.8. Reading a JavaScript variable
package com.manning.airinaction.html {

   import flash.display.MovieClip;
   import flash.html.HTMLLoader;
   import flash.net.URLRequest;
   import flash.events.Event;
   import flash.desktop.NativeApplication;

   public class Main extends MovieClip {

      private var _htmlLoader:HTMLLoader;

      public function Main() {
         _htmlLoader = new HTMLLoader();
         _htmlLoader.width = stage.stageWidth;
         _htmlLoader.height = stage.stageHeight;
         _htmlLoader.addEventListener(Event.COMPLETE, completeHandler);
         _htmlLoader.load(new URLRequest("example.html"));
         addChild(_htmlLoader);
      }

      private function completeHandler(event:Event):void {
         stage.nativeWindow.title = _htmlLoader.window.pageTitle;
      }
   }
}

You can see that, when the complete event occurs, all we need to do is read the value from _htmlLoader.window.pageTitle to get the value of the pageTitle variable from the HTML page. Figure 7.6 shows the results of this code.

Figure 7.6. Displaying the value of a JavaScript variable in the window title

Not only can you read values of JavaScript variables, but you can write them as well. If you test the code in listing 7.8 and click on the button, you’ll see an alert window that displays the value of the description variable as it’s set in the HTML page. In listing 7.9, we’ll write a different value to the variable. Then, when the user clicks on the button, the new value will show up in the alert window.

Listing 7.9. Writing the value of a JavaScript variable from ActionScript
package com.manning.airinaction.html {

   import flash.display.MovieClip;
   import flash.html.HTMLLoader;
   import flash.net.URLRequest;
   import flash.events.Event;
   import flash.desktop.NativeApplication;

   public class Main extends MovieClip {

      private var _htmlLoader:HTMLLoader;

      public function Main() {
         _htmlLoader = new HTMLLoader();
         _htmlLoader.width = stage.stageWidth;
         _htmlLoader.height = stage.stageHeight;
         _htmlLoader.addEventListener(Event.COMPLETE, completeHandler);
         _htmlLoader.load(new URLRequest("example.html"));
         addChild(_htmlLoader);
      }

      private function completeHandler(event:Event):void {
         _htmlLoader.window.description = "This is a new description from ActionScript";
      }
   }
}

As you can see, all we need to do is reference the variable from the window object after the complete event occurs and assign it a new value. Figure 7.7 shows how the new value gets displayed in the alert window.

Figure 7.7. Displaying a value in an alert that was set from ActionScript

Not only can you assign simple data types such as strings and Boolean values, but you can also assign references to more complex data types, including things like functions. In listing 7.10, we assign an ActionScript function to the showAlert() function in the HTML page. Therefore, when the user clicks on the button in the HTML page, it calls the ActionScript function instead of the function defined in the HTML page.

Listing 7.10. Assigning reference data types to JavaScript variables
package com.manning.airinaction.html {

   import flash.display.MovieClip;
   import flash.html.HTMLLoader;
   import flash.net.URLRequest;
   import flash.events.Event;

   public class Main extends MovieClip {

      private var _htmlLoader:HTMLLoader;

      public function Main() {
         _htmlLoader = new HTMLLoader();
         _htmlLoader.width = stage.stageWidth;
         _htmlLoader.height = stage.stageHeight;
         _htmlLoader.addEventListener(Event.COMPLETE, completeHandler);
         _htmlLoader.load(new URLRequest("example.html"));
         addChild(_htmlLoader);
      }

      private function completeHandler(event:Event):void {
         _htmlLoader.window.showAlert = function():void {
            trace("we've usurped control");
         };
      }
   }
}

You can also get references to objects within the document. For example, if you want to reference the p tag in the HTML, you can use _htmlLoader.window.document.getElementById("p1") to gain that reference. Listing 7.11 shows how you can change the text in the p tag.

Listing 7.11. Getting a reference to a document object
package com.manning.airinaction.html {

   import flash.display.MovieClip;
   import flash.html.HTMLLoader;
   import flash.net.URLRequest;
   import flash.events.Event;
   import flash.desktop.NativeApplication;

   public class Main extends MovieClip {

      private var _htmlLoader:HTMLLoader;

      public function Main() {
         _htmlLoader = new HTMLLoader();
         _htmlLoader.width = stage.stageWidth;
         _htmlLoader.height = stage.stageHeight;
         _htmlLoader.addEventListener(Event.COMPLETE, completeHandler);
         _htmlLoader.load(new URLRequest("example.html"));
         addChild(_htmlLoader);
      }

      private function completeHandler(event:Event):void {
         _htmlLoader.window.document.getElementById("p1").innerText = "Hello from ActionScript";
      }
   }
}

When you run the code from listing 7.11, you’ll see that the initial text shows at first, but once the content has loaded, the text is replaced by the text specified in ActionScript as shown in figure 7.8.

Figure 7.8. Replace text in an HTML page at runtime from ActionScript.

Now that we’ve seen how to reference elements of an HTML page from ActionScript, we can take it one step further. In the next section, we’ll show how you can handle events from HTML elements in ActionScript.

7.5.2. Handling JavaScript events from ActionScript

In the previous section, we saw that it’s possible to assign an ActionScript function reference to a variable in an HTML page, thus effectively usurping control. In this way, it’s possible to call ActionScript functions from JavaScript. But there’s much more you can do in regard to handling events than simply usurping existing event handlers. You can also register new event handlers.

There are two ways you can register event listeners: assign a function reference (ActionScript or JavaScript) to the event handler attribute of the HTML object or use the addEventListener() method.

First we’ll look at how to assign a function reference to the event handler attribute. If you’d like to call an ActionScript function when the user clicks on the p tag content, you can use code such as listing 7.12.

Listing 7.12. Assigning a function reference to the event handler attribute
package com.manning.airinaction.html {

   import flash.display.MovieClip;
   import flash.html.HTMLLoader;
   import flash.net.URLRequest;
   import flash.events.Event;

   public class Main extends MovieClip {

      private var _htmlLoader:HTMLLoader;

      public function Main() {
         _htmlLoader = new HTMLLoader();
         _htmlLoader.width = stage.stageWidth;
         _htmlLoader.height = stage.stageHeight;
         _htmlLoader.addEventListener(Event.COMPLETE, completeHandler);
         _htmlLoader.load(new URLRequest("example.html"));
         addChild(_htmlLoader);
      }

      private function completeHandler(event:Event):void {
         _htmlLoader.window.document.getElementById("p1").onclick = clickHandler;
      }

      private function clickHandler(event:Object):void {
         trace("click");
      }
   }
}

While there’s nothing wrong with the preceding code, you also have the option of using the standard ActionScript event-dispatching model by registering an event listener using addEventListener(), as shown in listing 7.13.

Listing 7.13. Using addEventListener() to register event listeners
package com.manning.airinaction.html {

   import flash.display.MovieClip;
   import flash.html.HTMLLoader;
   import flash.net.URLRequest;
   import flash.events.Event;
   import flash.desktop.NativeApplication;

   public class Main extends MovieClip {

      private var _htmlLoader:HTMLLoader;

      public function Main() {
         _htmlLoader = new HTMLLoader();
         _htmlLoader.width = stage.stageWidth;
         _htmlLoader.height = stage.stageHeight;
         _htmlLoader.addEventListener(Event.COMPLETE, completeHandler);
         _htmlLoader.load(new URLRequest("example.html"));
         addChild(_htmlLoader);
      }

      private function completeHandler(event:Event):void {
         _htmlLoader.window.document.getElementById("p1"). addEventListener("click", clickHandler);
      }

      private function clickHandler(event:Object):void {
         trace("click");
      }
   }
}

What you’ll notice in both listing 7.12 and 7.13 is that the method that handles the event accepts a parameter of type Object rather than type Event. The parameter acts much like an Event object, and it has target and currentTarget properties referencing the HTML element that dispatched the event, but the object isn’t of type Event.

We’ve learned how to reference JavaScript and HTML elements from ActionScript. Now we’ll look at a practical example that shows how to put it all together.

7.5.3. Building a hybrid application

You may find yourself wondering why you’d ever want to integrate HTML and JavaScript into one AIR application using what you’ve learned in the preceding sections. Consider the following scenario: you’re building an AIR application that allows the user to fill out questionnaires. Once the AIR application is built, the product owner would like to be able to update the questionnaires using a Ruby on Rails application that runs on a server and generates new questionnaires as HTML files. The AIR application should always load these HTML files. In such a case, it makes a lot of sense to integrate the ActionScript and HTML using the skills you’ve just learned. In this section, we’ll build a simple questionnaire example to illustrate how this might work.

Our simple application in this case will load an HTML page at runtime. The HTML page contains a questionnaire that might contain any number of questions and answers. The AIR application needs to be able to get the responses to the questionnaire when the user clicks on an HTML button. We’re going to impose a rule that says that the questionnaire HTML file must name the Submit button with an ID of submit-Button, and the page must have a function called getSurveyResponses() that returns an array of the responses to the questionnaire.

For our example, the HTML file is called questionnaire.html, and listing 7.14 shows what this file looks like.

Listing 7.14. The questionnaire HTML file that displays a survey to the user
<html>
   <script>

      function getSurveyResponses() {
         var response;
         var options = document.questionnaire.skyColor;
         for(var i = 0; i < options.length; i++) {
            if(options[i].checked) {
               response = options[i].value;
            }
         }
         var response1 = {question: document.getElementById("question1").innerHTML, answer: response};

         options = document.questionnaire.skySize;
         for(i = 0; i < options.length; i++) {
            if(options[i].checked) {
               response = options[i].value;
            }
         }
         var response2 = {question: document.getElementById("question2").innerHTML, answer: response};

         return [response1, response2];
      }
   </script>
   <body>
      <h1>Questionnaire</h1>
      <form name="questionnaire">
         Please complete the following survey.
         <h2 id="question1">1. What is the color of the sky?</h2>
         <input type="radio" name="skyColor" value="grey">grey
         <input type="radio" name="skyColor" value="blue">blue
         <input type="radio" name="skyColor" value="no color">no color
         <input type="radio" name="skyColor" value="brown">brown

      <h2 id="question2">2. What is the size of the sky?</h2>
         <input type="radio" name="skySize" value="big">big
         <input type="radio" name="skySize" value="really big"> really big
         <input type="radio" name="skySize" value="not very big"> not very big
         <input type="radio" name="skySize" value="smaller than a cow"> smaller than a cow

         <h2>Click the button to finish
         <button id="submitButton">Submit Answers</button>

      </form>
   </body>
</html>

Next we create a document class for an ActionScript project that uses an HTMLLoader object to load questionnaire.html. We listen for a click event on the Submit button, and when the user clicks the button, we’ll retrieve the questionnaire responses using the getSurveyResponses() function and display them using ActionScript. Listing 7.15 shows the code.

Listing 7.15. The document class for the questionnaire application

This code is rather straightforward. It uses an HTMLLoader object to load an HTML file . When the content has loaded, the code gets a reference to the button inside the HTML content and registers an event listener for the click event on that button . When the user clicks on the button, the code calls a JavaScript function from the HTML content to get the survey responses and display them in a textArea component.

Now that we’ve seen how to control HTML and JavaScript elements from Action-Script, we’ll look at how to gain access to ActionScript objects and classes from JavaScript.

7.5.4. Handling standard JavaScript commands

Many standard JavaScript commands get issued to the host application, which is typically a web browser. In the case of an AIR application, the host is the AIR application instead. You can configure an AIR application to handle these commands. For example, in a web browser, when the window.status property is set, the browser status display changes. In an AIR application, there’s no default behavior in response to changes to the window.status property from JavaScript.

You can tell AIR to handle the method/property changes shown in table 7.1 by using an object of type flash.html.HTMLHost. An HTMLHost object has methods and properties that correspond to the JavaScript methods/properties, and those correspondences are also shown in table 7.1.

Table 7.1. Correspondences between JavaScript and HTMLHost

JavaScript

HTMLHost

Window.status updateStatus()
Window.location updateLocation()
Window.document.title updateTitle()
Window.open() createWindow()
Window.close() windowClose()
Window.blur() windowBlur()
Window.focus() windowFocus()
Window.moveBy() windowRect()
Window.moveTo() windowRect()
Window.resizeBy() windowRect()
Window.resizeTo() windowRect()

Here’s how it works:

1.  Create a custom class that extends HTMLHost.

2.  Override the methods as appropriate.

3.  Assign an instance of the custom class to the htmlHost property of the HTMLLoader object into which you’re loading the HTML content.

Next we’ll look at how you can create a subclass of HTMLHost. The HTMLHost constructor allows for an optional Boolean parameter indicating whether the default behaviors should be used. Normally we just implement the same thing for the subclass constructor, as in the following code:

package com.manning.airinaction.html {
   import flash.html.HTMLHost;

   public class CustomHTMLHost extends HTMLHost {
      public function CustomHTMLHost(defaultBehavior:Boolean = true) {
         super(defaultBehavior);
      }
   }
}

Next we need to override any of the methods for which we want to define custom behavior. The updateStatus(), updateLocation(), and updateTitle() methods all accept one parameter: a string with the new value. We’ll implement updateTitle() just to show one of them. There are many ways you could implement the methods of an HTMLHost subclass. In this case, we’re just going to set the title of the main window:

package com.manning.airinaction.html {
   import flash.html.HTMLHost;
   import flash.desktop.NativeApplication;

   public class CustomHTMLHost extends HTMLHost {
      public function CustomHTMLHost(defaultBehavior:Boolean = true) {
         super(defaultBehavior);
      }

      override public function updateTitle(title:String):void {
         NativeApplication.nativeApplication.openedWindows[0].title = title;
      }
   }
}

Next we’ll implement createWindow(). The createWindow() method gets passed a parameter of type flash.html.HTMLWindowCreateOptions. This object corresponds to the options passed to the window.open() method as the third parameter. Table 7.2 shows the attributes in the window.open() method parameter and the corresponding properties in an HTMLWindowCreateOptions object.

Table 7.2. Properties of HTMLWindowCreateOptions

Window.open() attribute

HTMLWindowCreateOptions property

Width width
Height height
screenX, left x
screenY, top y
location locationBarVisible
Menu menuBarVisible
scrollbars scrollBarsVisible
Status statusBarVisible
toolbar toolBarVisible
resizable resizable
fullscreen fullscreen

The createWindow() method must return an HTMLLoader object. The create-Window() method doesn’t receive information about what data to load into the HTMLLoader object, nor does it need that information. The method merely needs to create an HTMLLoader object and return it. AIR takes care of loading the specified content into the HTMLLoader instance.

Next we’ll look at an example of how you might implement createWindow(). In this case, we use the HTMLLoader.createRootWindow() method to create a new window using the dimensions specified in the parameter. Listing 7.16 shows the code.

Listing 7.16. Creating a new scrollable window for HTML content
package com.manning.airinaction.html {
   import flash.html.HTMLHost;
   import flash.desktop.NativeApplication;
   import flash.html.HTMLWindowCreateOptions;
   import flash.html.HTMLLoader;
   import flash.geom.Rectangle;

   public class CustomHTMLHost extends HTMLHost {
      public function CustomHTMLHost(defaultBehavior:Boolean = true) {
         super(defaultBehavior);
      }

      override public function updateTitle(title:String):void {
         NativeApplication.nativeApplication.openedWindows[0].title = title;
      }

      override public function createWindow(options:HTMLWindowCreateOptions):HTMLLoader {
         var bounds:Rectangle = new Rectangle(options.x, options.y, options.width, options.height);
         var loader:HTMLLoader = HTMLLoader.createRootWindow(true, null, true, bounds);
         return loader;
      }
   }
}

The windowClose(), windowFocus(), and windowBlur() methods all return no value and accept no parameters. In listing 7.17, we’ll look at a simple implementation of windowClose() that closes the main application window.

Listing 7.17. Closing a window by implementing the windowClose() method
package com.manning.airinaction.html {
   import flash.html.HTMLHost;
   import flash.desktop.NativeApplication;
   import flash.html.HTMLWindowCreateOptions;
   import flash.html.HTMLLoader;
   import flash.geom.Rectangle;

   public class CustomHTMLHost extends HTMLHost {
      public function CustomHTMLHost(defaultBehavior:Boolean = true) {
         super(defaultBehavior);
      }

      override public function updateTitle(title:String):void {
         NativeApplication.nativeApplication.openedWindows[0].title = title;
      }

      override public function createWindow(options:HTMLWindowCreateOptions):HTMLLoader {
         var bounds:Rectangle = new Rectangle(options.x, options.y, options.width, options.height);
         var loader:HTMLLoader = HTMLLoader.createRootWindow(true, null, true, bounds);
         return loader;
      }

      override public function windowClose():void {
         NativeApplication.nativeApplication.openedWindows[0].close();
      }
   }
}

You can override the windowRect property by overriding a setter method by that name. The windowRect property is of type Rectangle. Listing 7.18 illustrates how you can use the windowRect property to move or resize the main application window.

Listing 7.18. Resizing a window by implementing a windowRect setter
package com.manning.airinaction.html {
   import flash.html.HTMLHost;
   import flash.desktop.NativeApplication;
   import flash.html.HTMLWindowCreateOptions;
   import flash.html.HTMLLoader;
   import flash.geom.Rectangle;

   public class CustomHTMLHost extends HTMLHost {

      override public function set windowRect(value:Rectangle):void {
         NativeApplication.nativeApplication.openedWindows[0].bounds = value;
      }

      public function CustomHTMLHost(defaultBehavior:Boolean = true) {
         super(defaultBehavior);
      }

      override public function updateTitle(title:String):void {
         NativeApplication.nativeApplication.openedWindows[0].title = title;
      }

      override public function createWindow(options:HTMLWindowCreateOptions):HTMLLoader {
         var bounds:Rectangle = new Rectangle(options.x, options.y, options.width, options.height);
         var loader:HTMLLoader = HTMLLoader.createRootWindow(true, null, true, bounds);
         return loader;
      }

      override public function windowClose():void {
         NativeApplication.nativeApplication.openedWindows[0].close();
      }
   }
}

All of this code can be tested by simply loading an HTML page into an HTMLLoader object that uses an instance of CustomHTMLHost. Listing 7.19 illustrates an example document class that does this.

Listing 7.19. Using CustomHTMLHost in a document class
package com.manning.airinaction.html {

   import flash.display.MovieClip;
   import flash.html.HTMLLoader;
   import flash.net.URLRequest;
   import com.manning.airinaction.html.CustomHTMLHost;

   public class Main extends MovieClip {

      private var _htmlLoader:HTMLLoader;

      public function Main() {
         _htmlLoader = new HTMLLoader();
         _htmlLoader.width = stage.stageWidth;
         _htmlLoader.height = stage.stageHeight;
         _htmlLoader.htmlHost = new CustomHTMLHost();
         _htmlLoader.load(new URLRequest("example.html"));
         addChild(_htmlLoader);
      }
   }
}

You can use an HTML file such as is shown in listing 7.20.

Listing 7.20. Using a custom HTMLHost implementation
<html>
   <body>
      <button onclick="window.document.title='New Title'">Title</button>
      <button onclick="window.open('http://www.manning.com/lott', null, 'screenX=0, screenY=10, width=500, height=400')">Window</button>
      <button onclick="window.close()">Close</button>
      <button onclick="window.moveTo(0, 0)" />Reset Location</button>
   </body>
</html>

Of course, you aren’t limited to implementing the HTMLHost methods as we’ve shown in this example. You can implement them in many different ways to achieve many different effects. But the general principles apply no matter how you implement the methods.

 

Note

An HTMLHost object (or an instance of a subclass) automatically has a reference to the HTMLLoader object that it’s assigned to. This reference is stored in a property called htmlLoader.

 

Using HTMLHost, you can indirectly call ActionScript methods from JavaScript. In the next section, we’ll see how you can reference ActionScript elements more directly from JavaScript.

7.5.5. Referencing ActionScript elements from JavaScript

Not only can you reference JavaScript and HTML elements from ActionScript, but you can also reference ActionScript elements from JavaScript. In JavaScript, all of the AIR APIs, including standard Flash Player APIs, are available when the HTML page has been loaded into an AIR application. You can access classes and functions of the runtime from window.runtime. For example, if you want to call the global trace() method, you can do that using window.runtime.trace(). If classes are in packages, you can reference them using the fully qualified class name following the window.runtime reference. For example, listing 7.21 shows how to create a new Shape object and add it to the stage of the main window.

Listing 7.21. Creating a Shape object and adding it to the stage
<html>
   <script>

      function loadHandler() {
         var shape = new window.runtime.flash.display.Shape();
         shape.graphics.lineStyle(0, 0, 0);
         shape.graphics.beginFill(0, 1);
         shape.graphics.drawRect(0, 0, 100, 50);
         shape.graphics.endFill();
         var mainWindow = window.runtime.flash.desktop. NativeApplication.nativeApplication.openedWindows[0];
         mainWindow.stage.addChild(shape);
      }

   </script>
   <body onload="loadHandler()">
   </body>
</html>

You’ll notice that, in the first line of the function, we create a new Shape object using window.runtime.flash.display.Shape to reference the Shape class. Once we’ve created an object, we can reference all the properties and methods of that instance as we would in ActionScript. In this example, we reference the graphics property of the Shape object and call the methods of that property. We can also get a reference to the main window using window.runtime.flash.desktop.NativeApplication.native-Application.openedWindows[0].

Not only can you reference standard classes, but you can also reference custom classes. In order to make custom classes available to JavaScript, you must load the HTML page into the same ApplicationDomain in which the ActionScript classes are defined. If you’re not familiar with the concept of an ApplicationDomain, don’t be concerned. Most developers haven’t had reason to use ApplicationDomain before. Basically, an ApplicationDomain is a partition within which code is stored. Normally when you load an .swf or HTML content into an AIR application, it gets loaded into a new ApplicationDomain, distinct from the ApplicationDomain used by the main AIR application. That means that the loaded content doesn’t have access to the code contained within the main ApplicationDomain. If you want to make the custom Action-Script classes available to JavaScript, you can load the HTML content into the main ApplicationDomain by setting the runtimeApplicationDomain property of the HTMLLoader object to flash.system.ApplicationDomain.currentDomain.

We next look at an example of referencing custom classes from JavaScript. We build off the questionnaire example we produced in the previous section. This time we create a custom Response type that we use to store the questionnaire response items. Listing 7.22 shows what this class looks like.

Listing 7.22. The custom Response class to model questionnaire response items
package com.manning.airinaction.questionnaire {

   public class Response {

      private var _question:String;
      private var _answer:String;

      public function get question():String {
         return _question;
      }

      public function get answer():String {
         return _answer;
      }

      public function Response(question:String, answer:String) {
         _question = question;
         _answer = answer;
      }
   }
}

The Response class has two getter methods: question and answer. We next modify the questionnaire.html file to use this custom type to store the responses that are returned by the getSurveyResponses() function. Listing 7.23 shows what the getSurveyResponses() function looks like, with changes in bold.

Listing 7.23. Using the Response class in JavaScript
function getSurveyResponses() {
   var response;
   var options = document.questionnaire.skyColor;
   for(var i = 0; i < options.length; i++) {
      if(options[i].checked) {
         response = options[i].value;
      }
   }
   var response1 = new window.runtime.com.manning. airinaction.questionnaire.Response(document. getElementById("question1").innerText, response);

   options = document.questionnaire.skySize;
   for(i = 0; i < options.length; i++) {
      if(options[i].checked) {
         response = options[i].value;
      }
   }
   var response2 = new window.runtime.com.manning. airinaction.questionnaire.Response(document. getElementById("question2").innerText, response);

   return [response1, response2];
}

At this point, if we try to run the application, we’ll get runtime JavaScript errors that will cause the application to fail silently because the Response class isn’t defined at runtime. We next need to modify the document class to add the Response class and load the HTML page in the main ApplicationDomain. Listing 7.24 shows what this class looks like, with changes in bold.

Listing 7.24. Including the new Response class
package com.manning.airinaction.questionnaire {

   import flash.display.MovieClip;
   import flash.events.MouseEvent;
   import flash.html.HTMLLoader;
   import flash.net.URLRequest;
   import flash.events.Event;
   import fl.controls.TextArea;
   import flash.system.ApplicationDomain;
   import com.manning.airinaction.questionnaire.Response;

   public class Main extends MovieClip {

      private var _htmlLoader:HTMLLoader;
      private var _textArea:TextArea;

      public function Main() {
         _htmlLoader = new HTMLLoader();
         _htmlLoader.width = stage.stageWidth;
         _htmlLoader.height = stage.stageHeight;
         _htmlLoader.addEventListener(Event.COMPLETE, completeHandler);
         _htmlLoader.runtimeApplicationDomain = ApplicationDomain.currentDomain;
         _htmlLoader.load(new URLRequest("questionnaire.html"));
         addChild(_htmlLoader);

         _textArea = new TextArea();
         _textArea.width = stage.stageWidth;
         _textArea.height = stage.stageHeight;
      }

      private function completeHandler(event:Event):void {
         _htmlLoader.window.document.getElementById("submitButton"). addEventListener(MouseEvent.CLICK, clickHandler);
      }

      private function clickHandler(event):void {
         removeChild(_htmlLoader);
         var responses:Array = _htmlLoader.window.getSurveyResponses();
         _textArea.text = "";
         var response:Object;
         for(var i:Number = 0; i < responses.length; i++) {
            response = responses[i] as Response;
            _textArea.text += response.question;
            _textArea.text += "
	" + response.answer + "

";
         }
         addChild(_textArea);
      }
   }
}

Now that the Response class is compiled into the AIR application, it’s available at runtime. Because the HTML page is getting loaded into the main ApplicationDomain, it can access the Response class, and everything in the application will work.

 

Note

Because of security issues that you can read about in the next section, it isn’t possible to allow access to custom classes to a file that’s loaded outside the application domain. For example, the preceding application won’t work if questionnaire.html is loaded from a remote server, because AIR won’t allow the remote file access to custom ActionScript classes.

 

One thing that we haven’t yet talked about is the effect of loading HTML from different locations, something we’ll look at in the next section.

7.6. Managing security issues

Imagine for a moment that you’re an expert plumber. You just got hired by MegaCorp to be their chief plumber, which is quite a prestigious job. However, on your first day at work you discover that not only are you responsible for standard plumbing duties, which are all within your realm of expertise, but you’re also responsible for security for the entirety of MegaCorp, which has offices in every major city in the world. Sound ridiculous? We think it does. But this scenario isn’t entirely dissimilar to the one we find ourselves in first as web developers and then as AIR application developers. While our primary expertise may be application development, we’ve discovered that we have a second and very serious duty: a responsibility for the security of those applications and their users. Although it may seem unfair (and we think it is), it looks as though it’s impossible to completely separate the responsibilities of an application developer from those of an application security engineer. Therefore, although security issues may not currently be part of your expertise, we strongly encourage you to follow along with us through the next few sections as we look at how these issues relate to AIR application development.

AIR opens up a lot of possibilities to HTML and JavaScript content that gets loaded into an AIR application. As you’ve seen, AIR makes it possible for JavaScript to reference the AIR runtime, accessing all sorts of behaviors that wouldn’t normally be available to JavaScript. Therefore, imagine what might happen if you built an AIR application that allowed a user to load any HTML from any location, and one of those pages had malicious JavaScript designed to use the AIR runtime to install a virus on the user’s computer. If all HTML pages could get that level of unrestricted access to AIR, you could unwittingly open up the possibility of all sorts of problems for users. For exactly that reason, AIR puts in place a security model to mitigate those sorts of problems.

7.6.1. Sandboxes

All HTML content in AIR gets loaded into one of two security sandboxes, each of which has its own rules dictating what the HTML/JavaScript content has access to. These two sandboxes are called the application sandbox and the nonapplication sandbox. All content loaded from the application directory (the same directory or a subdirectory of the directory in which the application is installed) is automatically placed in the application domain. All other content is placed in the nonapplication domain. That means that all content loaded from a web server is placed in the nonapplication domain.

Each of these sandboxes has a different set of rules for what’s permissible. In the application sandbox, restrictions are placed on what can be done dynamically at runtime. The following aren’t permitted in the application sandbox:

  • The eval() statement can’t be used for anything other than object literals and constants.
  • The setTimeout() and setInterval() functions can only be used to call function literals, and they won’t evaluate strings.
  • You can’t use innerHTML or outerHTML to parse script elements.
  • You can’t use the javascript URI scheme.
  • You can’t import JavaScript files from outside the application domain.

These restrictions are in place as a layer of insurance against loading and running malicious JavaScript code. While these restrictions don’t necessarily ensure that no malicious code will ever be loaded, they do create a reasonable level of insurance. This is important because, within the application domain, JavaScript has access to the full AIR API, meaning it can do things like read and write files on the local file system.

On the other hand, files loaded from outside the application domain have none of the restrictions applied to files loaded into the application domain. You may be thinking that it seems terribly unfair to restrict files in the application domain while allowing all the same behaviors to files outside the domain. Before you get too upset about this inequality, let us assure you that there’s a tradeoff. You see, files loaded from outside the application domain don’t have access to the AIR API. That means that, while a file outside the application domain can run eval() statements unhindered, it can’t access the local file system.

We’re about to tell you how you can work around the limitations of the sandbox restrictions. But before we disclose that information, we want to stress how important it is that you use this technique only when absolutely necessary, and even then you should exercise extreme caution. Err on the side of being too conservative in your use of bridging the sandbox security model. Remember that the sandboxes are in place for a reason, and you shouldn’t subvert them without good cause. With that said, in cases when it’s absolutely necessary, you can bridge the sandboxes using a technique called sandbox bridging.

7.6.2. Sandbox bridges

The principle of a sandbox bridge is that an HTML page loaded into an iframe of another HTML page has the ability to communicate with the parent, and the parent can communicate with the child. If the two pages are loaded into different sandboxes (because one is remote and one is in the application directory, for example), they can work together to get around their respective limitations. Typically, the way this works is that you create an HTML file with an iframe that resides in the application directory. This file will be loaded into the application sandbox. This local HTML file loads a nonapplication HTML file (such as a file from a remote server) into the iframe. The nonapplication HTML file is loaded into the nonapplication sandbox. Once the two pages are loaded, they can communicate by defining interfaces that they expose to each other.

You can define interfaces that each page can share with the other using variables called parentSandboxBridge and childSandboxBridge. You can create an object that has properties containing values or references to functions and assign it to a variable called parentSandboxBridge or childSandboxBridge within the HTML content loaded into the iframe. The following example illustrates how this can work. Listing 7.25 shows an HTML page containing an iframe. This HTML page can be saved in the application directory. When it’s loaded, it’ll have access to the AIR API.

Listing 7.25. Using a local HTML file with an iframe to load remote content

As you can see in this example, we create an object to serve as the bridge and we add a reference to a function. When the page loads, we assign the bridge to a variable called parentSandboxBridge within the content window of the iframe . Now the content in the iframe will be able to call the writeMemo() function using the bridge. Listing 7.26 shows what the code for the content looks like.

Listing 7.26. Calling a function using the bridge
<html>
   <body>
         <button onclick="parentSandboxBridge.writeMemo(subject.value, message.value)">Save Memo</button>
      <form>
         Subject <input type="text" id="subject" /><br />
         Message <textarea id="message" /><br />
      </form>
   </body>
</html>

In this case, we’re calling parentSandboxBridge.writeMemo() when the user clicks the button. This uses the bridge to allow the frame content, which is loaded into a nonapplication sandbox, to call a function within the parent HTML page, which is in the application sandbox. The result is that we can save a file locally even though the content for the file originates from an HTML page that’s outside the application domain.

You can make values and functions accessible to the parent from the child page by using a variable called childSandboxBridge. This works almost identically. From within the content page, you can define an object with properties that contain values or references to functions and then assign that object to a variable called childSandboxBridge. From within the parent page, you can reference the childSandboxBridge from the content window of the iframe once it loads.

7.7. Adding HTML to AirTube

Now that we’ve learned more than we ever imagined about working with HTML in AIR, we can add a new feature to our AirTube application. After what you’ve just learned, this is going to seem remarkably easy. All we’re going to do is allow the user to open the YouTube web page for a video in an AIR window. We’ll add a button to the video window that allows the user to launch the HTML page in a window.

The first thing we do is update the HTMLWindow component to display HTML content using an HTML component. To do this, simply open HTMLWindow.mxml and add the code shown in bold in listing 7.27.

Listing 7.27. Showing HTML content in the HTMLWindow component
<?xml version="1.0" encoding="utf-8"?>
<mx:Window xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
   width="800" height="800" closing="closingHandler(event);">
   <mx:Script>
      <![CDATA[

         [Bindable]
         private var _url:String;

         public function set url(value:String):void {
            _url = value;
         }

         private function closingHandler(event:Event):void {
            event.preventDefault();
            visible = false;
         }

      ]]>
   </mx:Script>
   <mx:HTML id="html" location="{_url}" width="100%" height="100%" />
</mx:Window>

As you can see, the new code is short. We merely add an HTML component and bind its location attribute to a _url property. We’ll next see how we set the URL for the HTML-Window component.

You’ll recall that way back in chapter 2 when we initially created the skeleton for HTMLWindow, we also added a method to AirTube.mxml that opens the HTMLWindow instance. That method is called launchHTMLWindow(), and it looks like the following:

public function launchHTMLWindow(url:String):void {
   if(_htmlWindow.nativeWindow == null) {
        _htmlWindow.open();
     }
     else {
        _htmlWindow.activate();
     }
}

We’re going to now update that method by adding one line to it. Listing 7.28 shows the new line of code in bold.

Listing 7.28. Setting the url property of the HTMLWindow instance
public function launchHTMLWindow(url:String):void {
   _htmlWindow.url = url;
   if(_htmlWindow.nativeWindow == null) {
        _htmlWindow.open();
     }
     else {
         _htmlWindow.activate();
     }
}

Now the only remaining task is to call the launchHTMLWindow() method when the user clicks a button in the VideoWindow. To do that, we need to make changes to the Video-Window component. Listing 7.29 shows what the updated code looks like with changes shown in bold.

Listing 7.29. Adding a button to launch HTML from the video window

This new code adds a button that calls viewOnYouTube() when clicked. When the user clicks the button, we first call the launchHTMLWindow() method of the application instance , passing it the URL to the YouTube page that’s stored in the AirTubeVideo object for the current video. And for good measure, we also pause the video .

With the new changes, the application might look something like what you see in figure 7.9.

Figure 7.9. Use the new button in the video window to launch the corresponding HTML page.

And with that, you now have a nearly complete AirTube application. All we have left to do with the application is enable the user to double-click on .atv files to launch them in AirTube, which we’ll see in the next chapter.

7.8. Summary

In this chapter, you’ve taken a whirlwind tour of all sorts of details for working with HTML in AIR. We started out by looking at the basics: how can you load and render HTML content within a Flash or Flex-based AIR application. We saw how to do that using the HTMLLoader class or the HTML component. Next we learned how to control how the AIR application loads and manages HTML content, including things such as caching content and handling authentication challenges from a server. We also looked at the broad topic of ActionScript-JavaScript cross-scripting, and you learned not only how to control JavaScript and HTML elements from ActionScript, but how to target ActionScript elements from JavaScript. Then, before closing the chapter with the additions to the AirTube application, we also looked at security issues related to HTML in AIR.

This chapter marks a real milestone in this book. This is the last chapter dedicated solely to adding new behavior to AIR applications. In the next chapter, we’ll focus on tying together everything you’ve learned thus far and bundling it into a deployable application. Then we’ll look at strategies for deploying the application and updating it when necessary.

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

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