10 June 2010
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.
Additional Requirements
Intermediate
When you turn off system chrome for a window, you gain tremendous creative possibilities, but you also lose the automatic management of window size, position, and display state that the operating system provides for standard windows. The Custom Chrome example application illustrates how to take advantage of some of the creative advantages of providing your own chrome and how to replace the window management services that are no longer provided by the operating system.
In addition, the application demonstrates how to map a portion of the application to a non-application sandbox and how to set up a bridge between a parent document in the application sandbox and a child document in a non-application sandbox.
Note: This is a sample application provided, as is, for instructional purposes.
This sample application includes the following files:
The Custom Chrome example uses four general types of window chrome:
Figure 2 shows an exploded view of the Custom Chrome application, showing the individual chrome elements.
To code a window in AIR that does not use system chrome, you add <systemChrome>false</systemChrome> to the <initialWindow> element of the application descriptor file. To allow the user to move, resize, or change the display state of the window, You must then add your own chrome to the window. This example demonstrates how to add your own chrome using standard HTML, CSS, and JavaScript, as well as how to use the AIR NativeWindow functions for controlling the window from your custom chrome.
The Custom Chrome application is structured in two parts. The first part defines the window chrome and contains the code that calls AIR APIs to move, resize, close, and change the display state of the window. The second part, is essentially the “client area” of the window and defines the application proper. In Custom Chrome, this application is a simple animation drawn using the Canvas API.
The first part, defined in html/index.html, loads the second part, sandbox/html/child.html, using an iFrame:
<iframe id="application" src="html/child.html"
sandboxRoot="http://localhost/"
documentRoot="app:/sandbox/"
frameBorder="0" width="100%" height="100%"
onDOMInitialize="setBridge()">
</iframe>
This two-part structure allows the main part of the application to be easily mapped to the appropriate sandbox without disturbing the operation of the chrome elements. For example, if the application displayed a map from a remote server, and the page included its own JavaScript functions to control the map, then you could change the sandboxRoot attribute to the domain of the remote server. On the other hand, if the application called AIR APIs directly, you could remove the sandboxRoot property altogether so that the content remained in the application sandbox. In neither case, does the code for the chrome have to be changed because it always runs in the application sandbox.
The example content used in Custom Chrome could run in any sandbox, including the application sandbox. For demonstration purposes, it is mapped to the remote, http://localhost/, sandbox.
When setting the sandboxRoot attribute, be careful not to forget the URL scheme. You can use the http: or https: schemes to map content to a remote sandbox, and the file: scheme to map content to the local-with-filesystem sandbox. If you leave out the scheme, however, the content remains in the application sandbox, and you may be scratching your head as to why certain JavaScript functions only allowed outside the application sandbox unexpectedly fail.
When you map content out of the application sandbox, it can no longer call AIR APIs. The Custom Chrome application needs to access AIR APIs from outside the application sandbox for two features. The first is the Source Viewer feature, triggered by a DHTML context menu; the second, useful for debugging, is the AIR trace() function. To allow content in the application sandbox to interact with content in a non-application sandbox, and vice-versa, you can set up a parent or child sandbox bridge.
Each bridge goes in a single direction. The parent bridge allows the child content to call functions and access properties in the parent, application-sandboxed document. This is the type of bridge used in Custom Chrome. The child bridge allows the parent document to call functions and access properties in the child document. In both cases, code accessing the bridge can only access the properties and methods of a single object, which is assigned to the parentSandboxBridge or childSandboxBridge property of the JavaScript Window object. Custom Chrome creates an object called Bridge and creates two member functions, viewSource() and trace():
var Bridge = {};
Bridge.viewSource = function(){
SourceViewer.getDefault().viewSource();
}
Bridge.trace = function(string){
air.trace(string);
}
Custom Chrome sets the parentSandboxBridge property of the child.html document in response to the dominitialize event of the iframe with setBridge() function defined in WindowControl.js:
function setBridge(){
var childApp = document.getElementById("application").contentWindow;
childApp.parentSandboxBridge = Bridge;
}
The bridge can be created by the parent document at anytime, but in order for the scripts in the child document to access the bridge before the load event is handled, you must use the dominitialize event.
Each chrome element has two parts, an HTML element, which places the element into the DOM and assigns an id, and one or more CSS styles, which specify the position and images to use for the chrome element. In addition, event listeners are added to the button and drag handles either by using the onClick attribute or by looking up the element id in JavaScript.
The CSS position:fixed style makes it easy to position the chrome elements relative to the window borders. You can use the top, bottom, left, and right properties to “glue” an edge of a chrome element a specified distance from the edge of the window. The chrome element then automatically repositions itself, resizing if necessary, when the window is resized. For example, the following styles are used for the top, right slice of the background:
.background {
position:fixed;
background-image:url("../images/background.png");
}
#backgroundRight {left:99; right:25; top:44; height:75;}
Because both the left and right properties are set, the slice resizes horizontally. Because the top property is set to 44 and the height is set to 75, the slice always remains 44 pixels from the top of the window and 75 pixels high.
Note: AIR does not support the CSS opacity property. If you set a background color on an element, the background will be completely opaque. To simulate tinted transparency, you can use a PNG graphic of the desired color and opacity as the background image for the element.
The window buttons are defined in the main html file using anchor tags:
<a href="#" id="close" class="button" onClick="onClose()"></a>
The onClick event attribute attaches the button to the relevant event handler function. The anchor element is positioned and supplied with images using the corresponding CSS styles:
.button {
position:fixed;
}
a#close {
top:7px; right:40px; width:51px; height:41px;
background:url("../images/buttons/close_over.png")
}
a#close:link {background:url("../images/buttons/close_up.png")}
a#close:visited {background:url("../images/buttons/close_up.png")}
a#close:hover {background:url("../images/buttons/close_over.png")}
a#close:active {background:url("../images/buttons/close_down.png")}
The move handles and resize gripper are defined with div elements in the main html file:
<div id="topDragBar"></div>
<div id="bottomGroup">
<div id="bottomDragBar"></div>
<div id="resizeGripper"></div>
</div>
They are positioned using CSS styles. For example, the top bar used to move the window is styled with the following selector:
#topDragBar {
position:fixed; right:0; top:0; width:211; height:157;
background:url(../images/handles/topHandle.png);
}
A mouseDown event handler is added to each element in the initialize() function:
var topBar = document.getElementById("topDragBar");
topBar.addEventListener("mousedown",onMove,true);
When a window uses system chrome, user interaction with the window can be canceled by listening for and canceling the default behavior of the relevant event. For example, when a user clicks the system chrome close button, the closing event is dispatched. If any registered listener calls the preventDefault() method of the event, perhaps because it contains unsaved data, then the window will not close.
When a window does not use system chrome, notification events for intended changes are not automatically dispatched before the change is made. Hence, if you call the methods for closing a window, changing the window state, or set any of the window bounds properties, the change cannot be canceled.
In AIR, most objects are capable of dispatching events, including the NativeWindow object. To notify components in your application before a window change is actually made, your application logic can dispatch the appropriate notification event using the dispatchEvent() method of the native window. For example, the following logic implements a cancelable event handler for a window close button:
function onClose(){
var closing = new air.Event(air.Event.CLOSING, true, true);
window.nativeWindow.dispatchEvent(closing);
if(!closing.isDefaultPrevented()){
nativeWindow.close();
}
}
Note: Although the dispatchEvent() method returns false if the event preventDefault() method is called by a listener, it can also return false for other reasons. It is better to explicitly use the isDefaultPrevented() method to test whether the window change should be canceled.
To initialize the window, Custom Chrome calls the initialize() function in response to the load event dispatched by the body element:
function initialize(){
var topBar = document.getElementById("topDragBar");
topBar.addEventListener("mousedown",onMove,true);
var bottomBar = document.getElementById("bottomDragBar");
bottomBar.addEventListener("mousedown",onMove,true);
var resizeGripper = document.getElementById("resizeGripper");
resizeGripper.addEventListener("mousedown",onResize,true);
maxButton = document.getElementById("maximize");
restoreButton = document.getElementById("restore");
nativeWindow.addEventListener(air.NativeWindowDisplayStateEvent.DISPLAY_STATE_CHANGE, onDisplayStateChange);
}
The function finds the chrome elements by id so that they can be referenced in the control code and adds event listeners. Even though the system chrome title bar and buttons are not displayed, users can still change minimize, maximize, and restore the window through the operating system by other means (such as the Alt+Space menu on Windows). Therefore, Custom Chrome listens for changes in the window display state by adding an event listener to the window using the nativeWindow property of the JavaScript Window object. This property, which AIR automatically adds to all pages running within the application sandbox, is essentially an interface to the desktop window object.
Calling the startResize() or startMove() methods of a window starts a system-mediated window change. The user's mouse or keyboard actions are used to change the window bounds. In this case, each incremental change is preceded by an automatically generated moving or resizing event. If any listeners cancel these events, the move or resize sequence is terminated and no further changes are made to the window.
Custom Chrome uses the following functions to start a move and a resize:
var onMove = function(event){
nativeWindow.startMove();
}
var onResize = function(event){
nativeWindow.startResize(air.NativeWindowResize.BOTTOM_RIGHT);
}
The parameter passed to the startResize() method determines how the resize operation proceeds and should match the location of the chrome element which triggered the resize operation. Since the resize gripper is located in the bottom, right corner of the window, the function is called using the bottomRight resize type. The constants for the permitted values for the parameter are defined by the NativeWindowResize class and correspond to each of the corners and edges of the window.
The window display state can be controlled using the maximize(), minimize(), and restore() methods of the NativeWindow object. When you call one of these methods directly, no displayStateChanging event is dispatched, so to allow other components to prepare for the change, Custom Chrome dispatches the event itself. For example the function for maximizing the window is defined as follows:
function onMaximize(){
var maximizing = new air.NativeWindowDisplayStateEvent(
air.NativeWindowDisplayStateEvent.DISPLAY_STATE_CHANGING,
true, true,
window.nativeWindow.displayState,
air.NativeWindowDisplayState.MAXIMIZED);
window.nativeWindow.dispatchEvent(maximizing);
if(!maximizing.isDefaultPrevented()){
nativeWindow.maximize();
}
}
Custom Chrome closes the window using the close() method of the NativeWindow object. Calling the close method directly doesn't generate a cancelable closing event automatically, so it is a good idea to dispatch the closing event before calling close(). This allows other components of the application to prepare for or even veto the close operation if, for example, there is still data to save:
function onClose(){
var closing = new air.Event(air.Event.CLOSING, true, true);
window.nativeWindow.dispatchEvent(closing);
if(!closing.isDefaultPrevented()){
window.close();
}
}
There is overlap between the window control functions provided by the JavaScript Window object and those provided by the AIR NativeWindow object. You can, for example, close a window with either window.close() or window.nativeWindow.close(). When such overlap exists, use whichever method you find most convenient.