General experience building HTML-based applications is suggested. For more details on getting started with this Quick Start, refer to Building the Quick Start sample applications with HTML.
Intermediate
When an application needs to display informational messages without disturbing the user's workflow, toast-style windows can be the right solution. The WordUp example application, shown in Figure 1, creates toast windows with the following attributes:
The WordUp example illustrates how to use the following Adobe AIR APIs:
HTMLLoader.createRootWindow() to create and display the toast windowsNativeApplication to detect user presence and to use the dock or system tray iconsScreen to place multiple toast windows on the desktop without overlappingEventDispatcher to dispatch events between JavaScript objects
Note: This is a sample application provided, as is, for instructional purposes.
The WordUp example application runs in the background with no visible main window. When a timer fires, the application displays a "toast" window containing a random message. After several seconds, the message expires and the toast window closes automatically.
To detect when the user is present, the application listens for userIdle and userPresent events from the NativeApplication object. When the userIdle event is dispatched, messages are queued until the next userPresent event. A userIdle event is dispatched when no keyboard or mouse input has been detected within an interval determined by the NativeApplication.idleThreshold property. The default interval is a reasonable 5 minutes, but WordUp resets the value to less than a minute so the change in application behavior can be more easily observed.
To test the application, launch the WordUp installer file (WordUpHTML.air) file. On Windows, a system tray icon is displayed in the notification area of the task bar. On Mac OS X, the normal triangle under the dock icon shows that the application is running. After about 5 seconds, the first message will pop up from the bottom of the screen. It will then disappear after a few moments.
To exit the application on Windows, right-click the system tray icon and select Exit WordUp from the menu. On Mac OS X, click and hold the dock icon to open the menu and select Quit WordUp.
WordUp is an HTML-based project and uses windows created with the HTMLLoader createRootWindow() method. You can also create windows with the JavaScript window.open() method, but you have less control over the appearance of the window chrome.
Note: For more information about the AIR functions and APIs used in this example, see the AIR API Reference for HTML Developers.
The first step to creating a windowless application is to make sure that the initial window that is created automatically by AIR never becomes visible. The visibility of this window is controlled in two places. The first place is within the <initialWindow> element of the application descriptor file. If <visible>true</visible> is placed within this element, then the window will be visible on application startup. WordUp sets the visible element to false (although false is the default value, so this is not strictly necessary). The second place window visibility is controlled is the visible property of the native window itself. The visible property can be set to directly true or false with an assignment statement and it will also be set to true when the native window activate() method is called.
It is often a good idea to give the user some indication that an application is running, even when it has no visible windows. On Mac OS X, the application dock icon serves this purpose. On Windows, the system tray icon can be used. The Mac dock icon automatically appears in the dock when the application is running and provides a default menu for exiting the application so WordUp doesn't need to change the dock icon properties. However, the Windows system tray icon is only shown if you assign an image to it.
WordUp detects whether it is running on an operating system that supports system tray icons using the static NativeApplication.supportsSystemTrayIcon property. If so, it adds a tool tip and a menu containing an exit command using the following code (defined in WordUpHTML.html):
if(air.NativeApplication.supportsSystemTrayIcon){
air.NativeApplication.nativeApplication.icon.tooltip = "WordUp";
air.NativeApplication.nativeApplication.icon.menu = new air.NativeMenu();
var exitCommand =
air.NativeApplication.nativeApplication.icon.menu.addItem(new air.NativeMenuItem("Exit WordUp"));
exitCommand.addEventListener(air.Event.SELECT,function(event){
air.NativeApplication.nativeApplication.exit();
});
}
To initialize the application logic, WordUp creates a DisplayManager object (defined in MessageCenter.js), which manages the pop-up toast windows, and a new MessageGenerator object (defined in MessageGenerator.js), which periodically generates an event containing a random message:
window.messageGenerator = new MessagGenerator();
messageGenerator.addEventListener("message", onMessage);
window.displayManager = new DisplayManager();
function onMessage(evt){
displayManager.queueMessage(evt.message);
}
The onMessage() event handler function puts the message text on the message queue of the DisplayManager object.
The DisplayManager object periodically checks its message queue to see if there are any messages to be displayed. If so, it creates a new Message object, which handles the creation and placement of the message window. In turn, the Message object (defined in Message.js) creates an HTML window to display the message. To create the display window, the Message code creates a NativeWindowInitOptions object and sets the appropriate options for the window. The important options for a toast-style window are type and the systemChrome:
var windowOptions = new air.NativeWindowInitOptions();
windowOptions.type = air.NativeWindowType.LIGHTWEIGHT;
windowOptions.systemChrome = air.NativeWindowSystemChrome.NONE;
WordUp uses the lightweight window type so that the Windows toolbar or Mac Windows menu does not display an entry for the window. When the lightweight type is used, the systemChrome option must also be set to none.
The new window is then created by calling the createRootWindow() method of the HTMLLoader object:
var htmlLoader = air.HTMLLoader.createRootWindow(false, windowOptions, false);
Next, the properties of the native window object are set and the message.html file, which defines the visual style for the message window, is loaded:
htmlLoader.window.nativeWindow.width = 250;
htmlLoader.window.nativeWindow.y = currentScreen.bounds.height;
htmlLoader.window.nativeWindow.alwaysInFront = true;
htmlLoader.addEventListener(air.Event.HTML_DOM_INITIALIZE, onInitialize);
htmlLoader.addEventListener("layoutComplete", onComplete);
htmlLoader.window.nativeWindow.addEventListener(air.Event.CLOSE, onClose);
//load the html file for the window
htmlLoader.load(new air.URLRequest("../html/message.html"));
Setting the size of the HTML window to fit its content is more involved than simply setting the width and height. Because the content size depends on both message length and the CSS rules of the display window, WordUp loads the html file into the message window, sets the message text, and then waits for the HTML to finish computing the layout. It then resizes the containing window to suit the final layout. (You could also choose a fixed window size and let the message scroll if it is too large.)
During the page loading sequence, the HTMLLoader object returned by the call to createRootWindow() dispatches an htmlDOMInitialize event to signal that the child window and document objects have been created. At this point, none of the window scripts have been parsed and no nodes have been added to the DOM, so you can initialize any variables in the window that must have valid values before the window load event occurs. WordUp uses the htmlDOMInitialize event to pass the message test to the new window:
var onInitialize = function (){
htmlLoader.removeEventListener(air.Event.HTML_DOM_INITIALIZE, onInitialize);
htmlLoader.window.message = message;
}
This message variable is accessed in the page load event handler, which inserts the text into a placeholder element in the document. The width of the window is set to 250 pixels at creation and this establishes one dimension of the layout. The other dimension, height, cannot be determined until the message text is actually inserted into the DOM and the HTML environment finishes laying out the content. THe HTMLLoader object dispatches an htmlBoundsChange event whenever the size of the HTML content changes. The resizeWindow() function (defined in message.html) responds to this event by changing the size of the native window object based on the size of the body element (plus the top and bottom margins). This allows window to display the HTML content without clipping or scrolling:
resizeWindow = function (){
window.htmlLoader.removeEventListener(air.Event.HTML_BOUNDS_CHANGE, resizeWindow);
window.nativeWindow.height = document.body.offsetHeight + 20;
event = new air.Event("layoutComplete", false, false);
htmlLoader.dispatchEvent(event);
}
The resizeWindow() function in turn dispatches a layoutComplete event to signal that the final window size has been set. (AIR does not define a layoutComplete event, but you can use any string as the event type as long as the same type is used in both the call to addEventListener() and the Event object constructor.) The Message object that created the window listens for the layoutComplete event and, when it is received, searches for an open spot on the desktop to place the window.
The Message object uses the AIR Screen API to find a spot to display the new message. The findSpotForMessage() function searches for an open area starting with the lower-right-hand corner of the first screen in the air.Screen.screens array. When it finds an open spot, it animates a window movement from the bottom of the screen to its appointed destination.
To check whether a message window is already displayed in a given spot, WordUp loops through the NativeApplication.nativeApplication.openedWindows array. It tests whether the rectangle defined by the area being considered intersects the rectangle defined by the bounds of an existing message window:
var isOccupied = function (testRect){
var occupied = false;
//i starts at one to skip the hidden initial window
for (var i = 1; i < air.NativeApplication.nativeApplication.openedWindows.length; i++){
occupied = air.NativeApplication.nativeApplication.openedWindows[i].bounds.intersects(testRect);
if(occupied) {break;}
}
return occupied;
}
The animation of the window location is achieved by moving the window one quarter of the distance remaining to its final destination in response to the enterFrame event dispatched by the native window object:
var animateY = function (endY){
var dY = 0;
Message.prototype.isAnimating = true;
var animate = function(event){
dY = Math.floor((endY - htmlLoader.window.nativeWindow.y)/4);
htmlLoader.window.nativeWindow.y = htmlLoader.window.nativeWindow.y + dY;
if( htmlLoader.window.nativeWindow.y <= endY){
htmlLoader.window.nativeWindow.y = endY;
Message.prototype.isAnimating = false;
htmlLoader.stage.removeEventListener(air.Event.ENTER_FRAME, animate);
}
}
htmlLoader.stage.addEventListener(air.Event.ENTER_FRAME, animate);
}
Once the window reaches its destination, the event listener is removed.
Note: The enterFrame event is dispatched at the underlying frame rate of the application. This event is a convenient way to control animation in an AIR application, but you could use the JavaScript setInterval() function if you prefer.
WordUp uses the NativeApplication userIdle and userPresent events to determine whether the user is actively using the computer. The idleThreshold property determines how long the computer must be inactive before the userIdle event is dispatched. By default, the threshold is five minutes, but WordUp lowers the threshold to 30 seconds so that the effect of the change is easier to see.
air.NativeApplication.nativeApplication.idleThreshold = 30;
air.NativeApplication.nativeApplication.addEventListener(air.Event.USER_IDLE, pause);
air.NativeApplication.nativeApplication.addEventListener(air.Event.USER_PRESENT, resume);
In response to these events, WordUp suspends or restarts the DisplayManager timer that checks the queue for messages to display:
//Stop the timer when the user is away
var pause = function (){
air.trace("idle");
poller.stop();
}
//Start the timer when the user returns
var resume = function (){
air.trace("present");
poller.start();
}
There are several ways to dispatch messages between two objects. One of the easiest ways is to take advantage of the EventDispatcher class available in AIR. Although you cannot extend an AIR class through class inheritance in JavaScript, you can include an EventDispatcher object as a member of a JavaScript object and forward calls to the addEventListener(), removeEventListener(), and dispatchEvent() to this object. An example of this technique is used by the MessageGenerator object to dispatch message events.
First, a new EventDispatcher variable is created (as a local variable in the object constructor):
var dispatcher = new air.EventDispatcher();
Next, public functions are added to the MessageGenerator object, which forward the event-related functions to the dispatcher object:
this.addEventListener = function (){
dispatcher.addEventListener.apply(this, Array.prototype.slice.apply(arguments));
}
this.removeEventListener = function (){
dispatcher.removeEventListener.apply(this, Array.prototype.slice.apply(arguments));
}
this.dispatchEvent = function (event){
dispatcher.dispatchEvent(event);
}
The event objects dispatched using this technique must inherit from the ActionScript Event object (or one of its subclasses). Because of the different inheritance schemes, you cannot extend the Event object to add your own data in JavaScript. However, you can use an existing Event subclass that already has members of the appropriate data type. For the MessageGenerator object, the TextEvent class is used because it has a text property, which holds a string. The following function creates and dispatches a TextEvent object containing the message text:
var createMessageEvent = function() {
var messageEvent =
new window.runtime.flash.events.TextEvent("message", false, false, generator.getMessage());
dispatcher.dispatchEvent(messageEvent);
}
The TextEvent isn't commonly used in a JavaScript-based AIR application because it is normally only dispatched by an ActionScript TextField object. Therefore, the class isn't included in the AIRAliases.js script and you must use the full reference name, including the window.runtime variable, the package name, and the class name.
Note: If none of the existing Event subclasses are appropriate for transmitting your custom event data, you can define (and compile) a new class in ActionScript and import the SWF file containing the class using a script tag. The custom class can then be used in your JavaScript application. For more information on using ActionScript libraries, see Using ActionScript Libraries within an HTML page in the Developing Adobe AIR Applications with HTML and Ajax guide (http://www.adobe.com/go/learn_air_html?content=ProgrammingHTMLAndJavaScript_07.html).