Adobe AIR for JavaScript Developers Pocketguide
English, 1.0
Chapter 4 : Adobe AIR Mini-Cookbook
This chapter provides solutions to common tasks when developing Adobe AIR applications. The solutions in this chapter illustrate many concepts used in AIR application development, and provide working HTML and JavaScript code that you can leverage within your application.
All examples in this chapter assume that you are using the AIRAliases.js file.
Application Deployment
Deploy from a Web Page
Problem
You have finished your application, have signed and packaged it, and want to distribute it via a web page.
Solution
Adobe AIR applications can be easily distributed from a web page using the badge installer included with the SDK.
Discussion
Adobe AIR application files are largely self-contained entities, and are
ready for distribution once they are signed and packaged. The resultant file
will have an .air extension. That application file can be distributed via
email, CDROM, or other traditional forms; however, installing an air file
requires that Adobe AIR is already present on the target machine.
Alternatively, a webpage based "badge installer" can streamline installation by detecting the runtime and installing it if necessary before installing your application.
Though you can customize it in a number of different ways, a sample badge installer is included with the Adobe AIR SDK. The badge takes the form of a small 217x180 area, which is ideal for a blog sidebar or other constrained spaces. The default badge installer runs as a Flash 9.0.115 (Flash Update 3) component in the browser. The Flash source file (FLA) is also included with the SDK for additional customization.
You can find the files for the sample badge installer in the samples/badge directory of the SDK.
Deploying with the badge installer requires four files: badge.swf, default_badge.html, AC_RunActiveContent.js, and your AIR application.
Even though the badge installer does appear as Flash content on a web page, you do not need to have any Flash knowledge or software such as Adobe Flash CS3. The badge installer was prebuilt with a number of configurable options that you can set from within the containing HTML page. On line 59 of the default_badge.html file, you will see the flashvars parameter, which is assigned the various initialization properties that are specific to your application. This parameter takes the form of a query string, and has the options outlined in Table 41.
Table 4-1 Badge Installer flashvars options
| Parameter | Description |
| appname | The name of the application, displayed by the badge installer. |
| appurl | Required. The URL of the Adobe AIR file to be downloaded. You must use an absolute, not a relative, URL. |
| airversion | Required. For the 1.0 version of the runtime, set this to 1.0. |
| imageurl | Optional. The URL of the image to display in the badge. |
| buttoncolor | Optional. The color of the download button (specified as a hex value, such as FFCC00). |
| message color | Optional. The color of the text message displayed below the button (specified as a hex value, such as FFCC00). |
Here is an HTML page that displays the badge installer to install an AIR application, as well as the AIR runtime if necessary:
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>Adobe AIR Application Installer Page</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<style type="text/css">
<!-
#AIRDownloadMessageTable
{
width: 217px;
height: 180px;
border: 1px solid #999;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 14px;
}
#AIRDownloadMessageRuntime
{
font-size: 12px;
color: #333;
} -->
</style>
<script language="JavaScript" type="text/javascript">
<!-
var requiredMajorVersion = 9;
var requiredMinorVersion = 0;
var requiredRevision = 115;
</script>
</head>
<body bgcolor="#ffffff">
<script src="AC_RunActiveContent.js" type="text/javascript">
</script>
<script language="JavaScript" type="text/javascript">
<!-// Version check based upon the values entered above in "Globals"
var hasRequestedVersion = DetectFlashVer( requiredMajorVersion,
requiredMinorVersion, requiredRevision);
// Check to see if the version meets the requirements
// for playback
if(hasReqestedVersion )
{
AC_FL_RunContent(
'codebase',
'http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab',
'width','217',
'height','180',
'id','badge',
'align','middle',
'src','badge',
'quality','high',
'bgcolor','#FFFFFF',
'name','badge',
'allowscriptaccess','all',
'pluginspage','http://www.macromedia.com/go/getflashplayer',
'flashvars','appname=My%20Application&appurl=myapp.air&airversion= 1.0&imageurl=test.jpg',
'movie','badge'
); //end AC code
} else {
document.write('<table id="AIRDownloadMessageTable"><tr> <td>Download <a href="myapp.air">My Application</a> now.<br /><br /><span id="AIRDownloadMessageRuntime">This application requires the <a href="');
var platform = 'unknown';
if( typeof( window.navigator.platform ) != undefined )
{
platform = window.navigator.platform.toLowerCase();
if( platform.indexOf('win' ) != −1 )
{
platform = 'win';
}
else if( platform.indexOf( 'mac' ) != −1 )
{
platform = 'mac';
}
}
if( platform == 'win' )
{
document.write('http://airdownload.adobe.com/air/win/ download/1.0/AdobeAIRInstaller.exe' );
}
else if( platform == 'mac' )
{
document.write('http://airdownload.adobe.com/air/ mac/download/1.0/AdobeAIR.dmg' );
}
else
{
document.write( 'http://www.adobe.com/go/getair/' );
}
document.write('">Adobe® AIR™ runtime</a>.</span></td></tr></table>');
}
// -->
</script>
<noscript>
<table id="AIRDownloadMessageTable">
<tr>
<td>Download <a href="myapp.air">My Application</a> now.<br /> <br />
<span id="AIRDownloadMessageRuntime">This application requires Adobe® AIR™ to be installed for <a href="http://airdownload.adobe.com/air/mac/download/ 1.0/AdobeAIR.dmg">Mac OS</a> or <a href="http:// airdownload.adobe.com/air/win/download/1.0/ AdobeAIRInstaller.exe">Windows</a>.
</span>
</td>
</tr>
</table>
</noscript>
</body>
</html>
Application Chrome
Add Custom Controls
Problem
You want to create custom window chrome for your application and you need the user to be able to close and minimize the application.
Solution
Use the NativeWindow class within Adobe AIR to integrate, minimize, and close button functionality.
Discussion
Although Adobe AIR allows developers to completely define and customize the application's window chrome, it is important to remember that when doing so, the application is responsible for every type of windowing event that might normally occur. This means the application must connect the various visual elements with their respective operating system events.
When deploying an application on Adobe AIR, the window object gets additional
properties. Among those properties is nativeWindow, which is a reference to
the native window that houses the current HTML document. Using the native
window reference, the appropriate methods can be called to trigger the
operating-system-specific event (or vice versa). In the case of being able to
minimize the window, the application can use NativeWindow.minimize(); it can use NativeWindow.close() in the case of closing it:
window.nativeWindow.minimize();
window.nativeWindow.close();
The NativeWindow.close() method does not necessarily terminate the
application. If only one application window is available, the application will
terminate. If multiple windows are available, they will close until only one
window remains. Closing the last window terminates the application.
application.xml
<?xml version="1.0" encoding="utf-8" ?>
<application xmlns="http://ns.adobe.com/air/application/1.0">
<id>com.adobe.demo.CustomControls</id>
<name>Custom Controls</name>
<version>1.0</version>
<filename>Custom Controls</filename>
<description>Adding Custom Window Controls</description>
<initialWindow>
<title>Custom Controls</title>
<content>index.html</content>
<systemChrome>none</systemChrome>
<transparent>true</transparent>
<visible>true</visible>
<width>206</width>
<height>206</height>
</initialWindow>
</application>
index.html
<html>
<head>
<title>Custom Window Controls</title>
<style>
body
{
background-image: url( 'custom-chrome.gif' );
font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
font-size: 12px;
}
#closer
{
position: absolute;
width: 70px;
left: 68px;
top: 105px;
}
#minimize
{
position: absolute;
width: 70px;
left: 68px;
top: 75px;
}
textarea
{
position: absolute;
left: 8px;
right: 8px;
bottom: 8px;
top: 36px;
border-color: #B3B3B3;
}
#title
{
position: absolute;
font-weight: bold;
color: #FFFFFF;
}
</style>
<script type="text/javascript" src="AIRAliases.js"></script>
<script>
function doClose()
{
window.nativeWindow.close();
}
function doLoad()
{
document.getElementById("minimize").addEventListener(
"click",
doMinimize);
document.getElementById("closer").addEventListener(
"click",
doClose );
}
function doMinimize()
{
window.nativeWindow.minimize();
}
</script>
</head>
<body onload="doLoad()">
<input id="minimize" type="button" value="Minimize" />
<input id="closer" type="button" value="Close" />
</body>
</html>
Windowing
Create a New Window
Problem
You need to display an additional widow into which additional content can be loaded.
Solution
Basic windows can be generated and maintained in a similar fashion as traditional HTML content using the window.open() method.
Discussion
The JavaScript window.open() method invokes a new window similar to the way
it would when used in the browser. Content that gets loaded into the new
window can come from a local file, or URL endpoint. Similar to windows created
using JavaScript in the browser, there is finite control over the window
itself. The window properties that can be controlled are width, height,
scrollbars, and resizable.
var login = window.open( 'login.html', 'login', 'width = 300, height = 200' );
A native window is a better choice when additional control over the new window
is required. Native windows expose virtually the entire functionality of the
operating system, such as control over minimize and maximize functionality,
always in front, full screen, and even removal of system chrome altogether.
The choice between using window.open() and Native Window depends largely on
the requirements for the window and the overall portability of the JavaScript
source code.
You also can use the window.opener property, which is commonly used in JavaScript to refer from a new window to the parent (creating) window.
<html>
<head>
<title>Creating a New Window</title>
<style type="text/css">
body
{
font-family: Verdana, Helvetica, Arial, sans-serif;
font-size: 11px;
color: #0B333C;
}
</style>
<script type="text/javascript">
function doLoad()
{
document.getElementById( 'window' ).addEventListener( 'click', doWindow ); }
function doWindow()
{
var login = window.open( 'login.html', null,'width=325, height = 145' );
}
function doLogin( email, pass )
{
alert( 'Welcome: ' + email );
}
</script>
</head>
<body onLoad="doLoad();">
<input id="window" type="button" value="Login" />
</body>
</html>
Login.html
<html>
<head>
<title>Login</title>
<style type="text/css">
body
{
font-family: Verdana, Helvetica, Arial, sans-serif;
font-size: 11px;
color: #0B333C;
}
</style>
<script>
function doLoad()
{
document.getElementById('signIn').addEventListener( 'click', doSignIn );
}
function doSignIn()
{
var email = document.getElementById( 'email' ).value;
var password = document.getElementById( 'password' ) .value;
window.opener.doLogin( email, password );
}
</script>
</head>
<body onLoad="doLoad();">
<table>
<tr>
<td>Email:</td>
<td><input id="email" name="email" /></td>
</tr>
<tr>
<td>Password:</td>
<td><input id="password" name="password" type="password" /></td>
</tr>
<tr>
<td colspan="2" align="right">
<input id="signIn" type="button" value="Sign In" />
</td>
</tr>
</table>
</body>
</html>
Create a New Native Window
Problem
You need to display an additional window into which content can be loaded, and you need to be able to finetune how the new window appears.
Solution
The HTMLLoader class represents the root content of an HTML based Adobe AIR
application, and has various methods for creating and loading new native
windows that require a high degree of customization and control.
Discussion
Creating and managing native windows with Adobe AIR is highly customizable.
As an example, depending on the application requirements, you may want to hide
the minimize and maximize buttons. You may also want to control window
z-ordering, or force a particular window to always stay on top. The
NativeWindow and NativeWindowInitOptions classes offer control over virtually
all of these aspects of a native window. Although you can access the native
window directly through the window.nativeWindow property, the HTMLLoader class
provides much of the functionality for creating new native windows.
Calling HTMLLoader.createRootWindow() returns a reference to the HTMLLoader
instance of the newly created window (not the native window itself). The
HTMLLoader.createRootWindow() method can take up to four arguments controlling
initial visibility, initialization options, scroll bars, and window bounds
(i.e., the size and position on the screen). The initialization options are
passed through an instance of NativeWindowInitOptions, which must be created
and configured prior to creating the new native window. The
NativeWindowInitOptions object controls aspects of the window such as whether
it is resizable, or even whether it has any system chrome at all. The NativeWindowInitOptions constructor takes no arguments:
var options = new air.NativeWindowInitOptions();
var login = null;
var bounds = new air.Rectangle(
( air.Capabilities.screenResolutionX - 270 ) / 2,
( air.Capabilities.screenResolutionY - 150 ) / 2,
270,
150 );
options.minimizable = false;
options.maximizable = false;
options.resizable = false;
login = new air.HTMLLoader.createRootWindow( false, options, true, bounds );
All of the arguments for HTMLLoader.createRootWindow() have default values
which can be further explored in the Adobe AIR documentation. Not all of the
options an application may use appear as initialization options. Additional
options that may be controlled on an instance of NativeWindow itself include
the window title, and whether it is always in front.
In many cases, it is beneficial to start with an invisible window. This will allow the window to size and position itself, load the desired content, and then lay out and render the application without impacting what is displayed. This technique falls into a broader classification that is often referred to as perceived performance and is a very important aspect to consider during development.
Once a reference to the HTMLLoader instance of a new native window is
obtained, you can load content into it via the HTMLLoader.load() method. The
HTMLLoader.load() method takes a single argument which is a URLRequest
instance that points to the HTML content to be loaded into the new window:
<html>
<head>
<title>Creating a New Native Window</title>
<script src="AIRAliases.js" type="text/javascript"></script>
<style type="text/css">
body {
font-family: Verdana, Helvetica, Arial, sans-serif;
font-size: 11px;
color: #0B333C;
}
</style>
<script type="text/javascript"> function doLoad() {
function doLoad()
{
document.getElementById( 'window' ).addEventListener( 'click', doWindow ); }
function doWindow()
{
var init = new air.NativeWindowInitOptions();
var bounds = null;
var win = null;
var login = air.File.applicationDirectory.resolvePath ( 'login.html' );
bounds = new air.Rectangle(
( air.Capabilities. screenResolutionX - 325 ) / 2,
( air.Capabilities.screenResolutionY - 145 ) / 2,
325,
145 );
init.minimizable = false;
init.maximizable = false;
init.resizable = false;
win = air.HTMLLoader.createRootWindow( true, init, false, bounds );
win.load(new air.URLRequest( login.url ) );
}
</script>
</head>
<body onLoad="doLoad();">
<input id="window" type="button" value="Login" />
</body>
</html>
Login.html
<html>
<head>
<title>Login</title>
<style type="text/css">
body {
font-family: Verdana, Helvetica, Arial, sans-serif;
font-size: 11px;
color: #0B333C;
} </style>
<script>
function doLoad()
{
document.getElementById( 'signIn' ).addEventListener ( 'click', doSignIn );}
function doSignIn()
{
window.nativeWindow.close();
}
</script>
</head>
<body onLoad="doLoad();">
<table>
<tr>
<td>Email:</td>
<td><input id="email" name="email"></td>
</tr>
<tr>
<td>Password:</td>
<td><input id="password" name="password" type="password"></td>
</tr>
<tr>
<td colspan="2" align="right">
<input id="signIn" type="button" value="Sign In">
</td>
</tr>
</table>
</body>
</html>
Create Full-Screen Windows
Problem
For the purpose of enabling more viewing space or enabling additional interactions, your application needs to be able to run using the full screen.
Solution
The HTMLLoader class provides the functionality to create new native
windows, which, when used in conjunction with the
NativeWindowInitOptionsclass, can create transparent and fullscreen native
windows.
Discussion
The difference between using NativeWindowInitOptions for fullscreen display
and using NativeWindowInitOptions for custom windows is an additional
initialization option and setting the window to fill the screen. To remove any
OS-specific windowing chrome, use the NativeWindowInitOptions.system Chrome
property. The NativeWindowInitOptions.systemChrome property should be set to
one of the two string constants found in the NativeWindowSystemChrome class
(see Table 42).
Table 4-2 String constants in NativeWindowSystemChrome
| String constant | Description |
NativeWindowSystemChrome.STANDARD |
This is the default for NativeWindow and reflects the window chrome used on the specific operating system. |
NativeWindowSystemChrome.NONE |
This indicates that no chrome should be present, and requires that the application handle all traditional windowing tasks. |
To create a fullscreen window without any chrome, set the NativeWindowInitOptions.systemChrome property to NativeWindowSystemChrome.NONE. Window boundaries that match the fullscreen resolution can be passed when calling HTMLLoader.createRootWindow(). The boundaries for the window are passed to the HTMLLoader.createRootWindow() method as a Rectangle object which specifies horizontal and vertical origination, as well as width and height. Depending on the requirements for the application, an alternative approach would be to call NativeWindow.maximize() or to set NativeWindow.bounds directly when system chrome is set to NativeWindowSystemChrome.NONE.
If you find yourself confronted with an application that doesn't shut down, but whose visible windows are all closed, you're probably dealing with one of a few different challenges. The first is that you never set a size on a NativeWindow. The second is that you never set a NativeWindow to visible. The most common is that you used NativeWindowSystemChrome.NONE, but never added any content.
<html>
<head>
<title>Creating a Full Screen Window</title>
<script src="AIRAliases.js" type="text/javascript"></script>
<script type="text/javascript">
function doLoad()
{
var init = new air.NativeWindowInitOptions();
var win = null; var bounds = new air.Rectangle( 0, 0,
air.Capabilities. screenResolutionX,
air.Capabilities. screenResolutionY );
init.minimizable = false;
init.maximizable = false;
init.resizable = false;
init.systemChrome = air.NativeWindowSystemChrome.NONE;
win = air.HTMLLoader.createRootWindow( true, init, true, bounds );
win.load(new air.URLRequest('http://www.adobe.com/go/air'));
}
</script>
</head>
<body onLoad="doLoad();">
</body>
</html>
File API
Write Text to a File from a String
Problem
A user has made changes to textual content in the application, which need to be saved to the local disk for offline access.
Solution
You can write text through the File and FileStream classes that are part of Adobe AIR.
Discussion
Before any reading or writing to disk takes place, a reference to a file or
directory must exist in the application. You can establish a file reference in
a number of ways, including via programmatic manipulation and user selection.
You accomplish both of these by using the File class. The File class also
contains static properties that point to common locations on the operating
system. These locations include the desktop directory, user directory, and
documents directory:
var file = air.File.applicationStorageDirectory.resolvePath('myFile.txt');
The call to File.resolvePath() creates a reference to a file named
myFile.txt located in the application storage directory. Once a reference
has been established, it can be used in file I/O operations. Note that this
doesn't actually create the file at this point.
Physically reading and writing to disk is accomplished using the FileStream
class. The FileStream class does not take any arguments in its constructor:
var stream = new air.FileStream();
With the file reference available and a FileStream object instantiated, the
process of writing data to disk can take place. The type of data that can be
written may be anything from binary, to text, to value objects. You can access
all of these by using the respective FileStream method for that operation.
The first step in writing a file is to open it using the FileStream.open()
method. The FileStream.open() method takes two arguments. The first argument
is the file reference created earlier that will be the destination of the
output. The second argument is the type of file access the application will
need. In the case of writing data to a file, the FileMode.WRITE static
property will be most common. A second possibility is FileMode.APPEND,
depending on the application requirements:
stream.open(file, air.FileMode.WRITE);
When writing text, an application should use File Stream.writeMultiByte() to
ensure that data is written with the correct encoding. The
FileStream.writeMultiByte() method takes two arguments. The first argument is
the String object (text) that will be written to disk. The second argument is
the character set to be used. The most common character set is that which the
operating system is using, which is available on the File class as
File.systemCharset:
stream.writeMultiByte( document.getElementById ('txtNote' ).value,
xair.File.systemCharset );
Once the text has been written to the file, it is important to close the file
to avoid any corruption or blocking of access from other applications. You
close a file using the FileStream.close() method.
XML data is already in textual format and, as such, can be written to disk using this same series of steps. If the application uses the XMLHttpRequest object, using the myXml.responseText property alone may be adequate for most situations.
<html>
<head>
<title>Writing a Text File</title>
<style type="text/css">
body
{
font-family: Verdana, Helvetica, Arial, sans-serif;
font-size: 11px;
color: #0B333C;
}
#save
{
position: absolute;
right: 5px;
bottom: 5px;
}
textarea
{
position: absolute;
left: 5px;
right: 5px;
top: 5px;
bottom: 32px;
}
</style>
<script type="text/javascript" src="AIRAliases.js"></script>
<script type="text/javascript">
function doLoad()
{
document.getElementById('save').addEventListener('click', doSave);
}
function doSave()
{
var file = air.File.desktopDirectory.resolvePath('note.txt');
var note = document.getElementById('note').value;
var stream = new air.FileStream();
stream.open( file, air.FileMode.WRITE );
stream.writeMultiByte( note, air.File.systemCharset );
stream.close();
}
</script>
</head>
<body onLoad="doLoad();">
<textarea id="note"></textarea>
<input id="save" type="button" value="Save" />
</body>
</html>
Synchronously Read Text from a File
Problem
You want to read the contents of a small text file into your application.
Solution
Use the File and FileStream classes provided by Adobe AIR to locate, open, and operate on text files.
Discussion
You can read small files that contain text content using the
FileStream.open() method. This method opens a file synchronously for reading.
Synchronous access requires less code, but also blocks any additional user
input until the data has been read. When using asynchronous access, additional
user input is not blocked, but event handlers must be registered, which
results in more development time.
Although it is possible to access XML files as text, the result of this approach is a string of text that can't be readily manipulated. Accessing an XML file for use as a data source is often more easily handled using XMLHttpRequest or wrapper functionality offered by most JavaScript libraries.
The steps for synchronously reading a file are almost always the same:
1 Get a File reference.
2. Create a FileStream object.
3. Open the stream for synchronous access.
4. Call the appropriate FileStream read methods.
5. Close the file.
The first step to reading a text file is to get a reference to the resource on
disk. You can establish a reference by programmatically designating a path
using the appropriate property on the File object, such as
File.applicationStorageDirectory. You will also have to call
File.resolvePath() when using this approach, as the static File class
properties always return a directory:
var file = air.File.applicationStorageDirectory.resolvePath('myFile.txt');
The FileStream class has an empty constructor and can be instantiated anywhere
in your application. The file reference just established is used during the
physical process of opening the file. The mode for which the file is going to
be opened must also be specified.
The FileMode object serves no purpose other than to provide constants for the
types of file access that can be performed. These operations are
FileMode.READ, FileMode.WRITE, File Mode.UPDATE, and FileMode.APPEND:
var stream = new air.FileStream();
stream.open(file, air.FileMode.READ);
You can use three FileStream methods to read character data from a file. The
FileStream.readUTF() and FileStream.readUTFBytes() methods are specifically
tuned for UTF data.
If this is the target format of the data for the application, you should use
these methods directly. In the case of other character sets, you can use the
FileStream.readMultiByte() method to specify the target format. Additional
character sets are specified in the form of a string, such as us-ascii. There
is also a convenience property on the File object to use the default system
character set, File.systemCharset.
You also need to specify the number of bytes to be read in the case of
FileStream.readUTFBytes() and FileStream.readMultiByte(). This sizing will
depend largely on the requirements of the application. When reading the entire
file is required, you can find the number of bytes available to be read on the
FileStream.bytesAvailable property:
var data = stream.readMultiByte( stream.bytesAvailable,
air.File.systemCharset);
Once the contents of a file have been read, it is important to close the file. This operation will allow other applications to access the file:
stream.close();
Although a demonstrable amount of flexibility has been provided by Adobe AIR, the actual process in its entirety is considerably concise. This brevity is provided when performing synchronous data access operations. Synchronous file access should be reserved for smaller files regardless of reading or writing character or binary data:
<html>
<head>
<title>Synchronous File Access</title>
<style type="text/css">
body
{
font-family: Verdana, Helvetica, Arial, sans-serif;
font-size: 11px;
color:#0B333C;
}
textarea
{
position: absolute;
left: 5px;
right: 5px;
top: 5px;
bottom: 5px;
}
</style>
<script type="text/javascript" src="AIRAliases.js"></script>
<script>
function doLoad()
{
var data = null;
var file = new air.File();
var stream = null;
file = air.File.applicationDirectory.resolvePath( 'the-raven.txt' );
stream = new air.FileStream();
stream.open( file, air.FileMode.READ );
data = stream.readMultiByte( stream.bytesAvailable,
air.File.systemCharset );
stream.close();
document.getElementById( 'editor' ).value = data;
}
</script>
</head>
<body onLoad="doLoad();">
<textarea id="editor"></textarea>
</body>
</html>
Asynchronously Read Text from a File
Problem
You want to read a large amount of text into your application without impacting the user interface.
Solution
Use the File and FileStream classes to asynchronously operate on the data;
ensuring that the application execution is not blocked while the file is being
processed.
Discussion
Files containing a large amount of data should be read using the
FileStream.openAsync() method. This method opens a file asynchronously for
reading or writing and will not block additional user input. Asynchronous file
operations achieve this goal by raising events during processing. The result
is that event listeners must be created and registered on the FileStream
object.
The steps for asynchronously reading a file are almost always the same:
-
Get a
Filereference. -
Create a
FileStreamobject. - Create event handlers for processing data.
- Add event listeners for asynchronous operations.
- Open the stream for asynchronous access.
- Close the file.
The first step to reading a text file is to get a reference to the resource on
disk. You can establish a reference by programmatically designating a path
using the appropriate property on the File object, such as
File.applicationStorageDirectory:
var file = air.File.applicationStorageDirectory.resolvePath('myFile.txt');
A FileStream instance is necessary to read or write to the file:
stream = new air.FileStream();
Before registering event handlers on a
FileStream object, you must create those handlers. The events that are
triggered by file I/O operations using the FileStream class will always pass
an event object as an argument. The properties on the event object will depend
on the type of event being raised. This object can be helpful in determining
the target FileStream object, how much data is available for reading, how much
data is waiting to be written, and more:
function doProgress( event ) { // Read all the data that is currently available var data = stream.readMultiByte( stream.bytesAvailable, air.File.systemCharset );
// Append the most recent content
document.getElementById( "editor" ).value += data;
// Close the file after the entire contents
// have been read
if( event.bytesLoaded == event.bytesTotal )
{
stream.close();
}
}
Registering for events takes place using the addEventListener() method:
stream.addEventListener( air.ProgressEvent.PROGRESS, doProgress );
You can open a stream for asynchronous access using the FileStream.openAsync() method. The FileStream.openAsync() method takes two
arguments that specify the file being accessed and the type of access being
performed.
The FileMode object serves no purpose other than to provide constants for the
types of file access that can be performed. These operations are
FileMode.READ, FileMode.WRITE, File Mode.UPDATE, and FileMode.APPEND:
stream.openAsync( file, air.FileMode.READ );
As soon as the file is opened and new data is available in the stream, the
ProgressEvent.PROGRESS event is triggered. Depending on the size of the file,
as well as machine and network characteristics, not all of the bytes may be
read in a single pass. In many cases, additional read operations take place,
raising a ProgressEvent.PROGRESS event for each iteration.
Once all of the data has been read from the file, an Event.COMPLETE event is
broadcast.
After the file has been read, it is important to close the file stream to ensure that other applications can access it:
stream.close();
This example provides a baseline for the various types of asynchronous access an application might choose to perform. In this case, the contents of the file are read and placed into an HTML text area each time more data is available. Asynchronous processing also provides the means for random file access (seek) without interrupting the user interface. An application should always use asynchronous access whenever the size of a file is in question.
<html>
<head>
<title>Asynchronous File Access</title>
<style type="text/css">
body
{
font-family: Verdana, Helvetica, Arial, sans-serif;
font-size: 11px;
color: #0B333C;
}
textarea
{
position: absolute;
left: 5px;
right: 5px;
top: 5px;
bottom: 5px;
}
</style>
<script type="text/javascript" src="AIRAliases.js"></script>
<script type="text/javascript">
var stream = null;
function doLoad()
{
var file = air.File.applicationDirectory.resolvePath('the-raven.txt');
stream = new air.FileStream();
stream.addEventListener(air.ProgressEvent.PROGRESS, doProgress );
stream.openAsync( file, air.FileMode.READ );
}
function doProgress( event )
{
var data = stream.readMultiByte( stream.bytesAvailable,
air.File.systemCharset );
document.getElementById( 'editor' ).value += data;
if( event.bytesLoaded == event.bytesTotal )
{
stream.close();
}
}
</script>
</head>
<body onLoad="doLoad();">
<textarea id="editor"></textarea>
</body>
</html>
Load Data from an XML File
Problem
You want to read XML data from a local file using common JavaScript techniques, and you want to manipulate the Document Object Model (DOM), not just the character data.
Solution
You can read a local XML document for its data using the XMLHttpRequest
object, and by using a File object reference as the URI endpoint as opposed to
a web address.
Discussion
Most JavaScript libraries, and virtually every data-oriented Ajax application,
makes use of the XMLHttpRequest object to load data. This is a common means to
accessing data from the client without refreshing the page, and it is core to
Ajax development techniques. Adobe AIR includes support for the XMLHttpRequest
object, which can be used for data access.
The XMLHttpRequest.open() method expects three arguments. The first argument
is the HTTP method to be used for the call, which is commonly GET or POST. The
third argument tells the object whether it should make the request
asynchronously. The challenge in an Adobe AIR application is the second
argument, which tells the object where to get its data:
var xml = new XMLHTTPRequest();
xml.open( 'GET', 'myData.xml', true );
This URI endpoint generally points to a remote server. This can still happen in an application that is online, but as Adobe AIR applications can also work offline, the endpoint needs to be pointed to a local resource. Rather than pass an endpoint to a remote server, a File reference can be provided:
var file = air.File.applicationStorageDirectory.resolve ('myData.xml' );
var xml = new XMLHttpRequest();
xml.onreadystatechange = function()
{
if( xml.readystate == 4 )
{
// Work with data
}
}
xml.open('GET', file.url, true );
xml.send( null );
The key distinction to make for this example is the use of the File.url
property, which the XMLHttpRequest object understands and uses to access the
appropriate data. Using this approach results in a traditional DOM that can be
used to traverse and manipulate the XML data in the file. Additionally, you
can use this approach with common JavaScript libraries.
Given
<rolodex>
<contact>
<first>Kevin</first>
<last>Hoyt</last>
</contact>
</rolodex>
Example
<html>
<head>
<title>Reading XML Data (using XMLHttpRequest)</title>
<style type="text/css">
body
{
font-family: Verdana, Helvetica, Arial, sans-serif;
font-size: 11px;
color: #0B333C;
}
</style>
<script type="text/javascript" src="AIRAliases.js"></script>
<script type="text/javascript">
var contacts = air.File.applicationDirectory.resolvePath('rolodex.xml');
function doLoad()
{
var xml = new XMLHttpRequest();
xml.onreadystatechange = function()
{
var elem = null;
var first = null;
var last = null;
var rolodex = null;
if( xml.readyState == 4 )
{
rolodex = xml.responseXML.documentElement.getElementsByTagName('contact');
for( var c = 0; c < rolodex.length; c++ )
{
first = rolodex[c].getElementsByTagName('first')[0].textContent;
last = rolodex[c].getElementsByTagName ('last')[0].textContent;
elem = document.createElement('div');
elem.innerText = first + " " + last;
document.body.appendChild( elem );
}
}
}
xml.open( 'GET', contacts.url, true );
xml.send( null );
}
</script>
</head>
<body onLoad="doLoad();">
</body>
</html>
Create a Temporary File
Problem
An application needs to store transient information during file processing, and cannot assume that adequate memory exists to store the data in memory.
Solution
Creating temporary files with File.createTempFile() is an ideal means to
store transient information while relieving the overhead of additional memory.
Discussion
The File class contains a static File.createTempFile() method that you can use
to establish a temporary file. The temporary file is created at a destination
determined by the operating system. Temporary files are also automatically
given a unique name to avoid collision with other files that may be present:
var temp = air.File.createTempFile();
Once a temporary file has been created, you can use the File and FileStream
APIs to interact with the file as you would any other file.:
var stream = new air.FileStream();
stream.open( temp, air.FileMode.WRITE );
stream.writeMultiByte('Hello', air.File.systemCharset );
stream.close();
You can use the File.moveTo() and File.moveToAsync() methods after the fact,
should you decide that it is necessary to keep the temporary file for later
reference. Both move methods take two arguments. The first argument is a File
reference to the destination location. The second argument is a Boolean value
that controls overwriting any existing file. If the second argument is set to
false, and a collision occurs, the application throws an error:
var move = air.File.desktopDirectory.resolve('temp.txt' );
try
{
temp.moveTo( move, false );
}
catch( ioe )
{
alert('Can\'t move file:\n' + ioe.message );
}
The JavaScript try/catch block will receive an error object of type IOError.
The IOError class has available numerous properties that you can use for
further evaluation. The exception in the previous code snippet raises the
error message that is generated by Adobe AIR:
<html>
<head>
<title>Creating a Temporary File</title>
<script type="text/javascript" src="AIRAliases.js"></script>
<script type="text/javascript">
function doLoad()
{
var stream = new air.FileStream();
var temp = air.File.createTempFile();
var move = air.File.desktopDirectory.resolvePath('temp.txt');
stream.open(temp, air.FileMode.WRITE);
stream.writeMultiByte( 'Hello World!', air.File.systemCharset );
stream.close();
try
{
temp.moveTo( move, false );
}
catch( ioe )
{
alert( 'Could not move temporary file:\n' + ioe.message );
}
}
</script>
</head>
<body onLoad="doLoad();">
</body>
</html>
Iterate the Contents of a Directory
Problem
The application is required to display information about a directory as part of the user interface.
Solution
Use the File.browseForDirectory() method to prompt the user to select a
directory, and then use the File.getDirectoryListing() method to iterate
through the contents of the directory.
Discussion
The File class provides numerous properties that you can use to get specific
information about files on disk. Also, various methods on the File class
pertain to getting a directory listing. Although an application can specify a
directory programmatically, you can use File.browseForDirectory() to prompt
the user to select a directory using the native dialog. Once a location on the
local disk has been specified, the File.getDirectoryListing() method returns
an Array of File objects for the currently referenced directory.
Before prompting the user to select a directory using the native dialog, the
application needs to establish and register an event handler for Event.SELECT.
The Event.target property on the raised event object will contain a reference
to the File object that invoked the browse operation.
The File.browseForDirectory() method takes one argument, a String representing
additional information that will be placed in the dialog box. This String is
not the title of the dialog, as is the case with File.browseForOpen(). There
is also no need to specify FileFilter objects, as the dialog box presented is
specific to directories, and no files will be displayed.
After the user has selected a directory, the registered event handler will be
called. The file reference, whether using a class or global reference, or
Event.target, will now contain the path to the selected directory. At this
point, File.getDirectoryListing() can be called, which returns an Array of
File objects for the selected directory (as represented by the file
reference). The File.getDirectoryListing() method takes no arguments:
var listing = directory.getDirectoryListing();
The File class can represent both files and directories on the local
filesystem. You can use the File.isDirectory property to determine whether a
specific File instance references a file or a directory.
See the API documentation for a complete list of data exposed by the File API.
<html>
<head>
<title>Selecting a Directory</title>
<style type="text/css">
body
{
font-family: Verdana, Helvetica, Arial, sans-serif;
font-size: 11px;
color: #0B333C;
}
</style>
<script type="text/javascript" src="AIRAliases.js"></script>
<script type="text/javascript">
var directory = null;
function doBrowse()
{
directory.browseForDirectory('Select a directory of files:');
}
function doLoad()
{
directory = air.File.documentsDirectory;
directory.addEventListener( air.Event.SELECT, doSelect );
document.getElementById('browse').addEventListener('click', doBrowse);
}
function doSelect( e )
{
var files = directory.getDirectoryListing();
var elem = null;
var name = null;
var mod = null;
var size = null;
for( var f = 0; f < files.length; f++ )
{
name = files[f].name;
mod = files[f].modificationDate;
mod = ( mod.month + 1 ) + '/' + mod.date + '/' + mod.fullYear;
size = Math.ceil( files[f].size / 1000 ) + ' KB';
elem = document.createElement( 'div' );
elem.innerText = name + ' is ' + size +
' and was last modified on ' + mod;
document.body.appendChild( elem );
}
}
</script>
</head>
<body onLoad="doLoad();">
<input id="browse" type="button" value="Browse" />
</body>
</html>
File Pickers
Browse for a File.
Problem
An application needs to prompt the user to select a file to open from the local system using a native dialog.
Solution
The File class allows an application to prompt the user to select one or
more files of a specific type from the local system.
Discussion
The File class provides numerous browse methods that present the native
dialog for the specified operation. In the case of browsing for a single file
to open, the appropriate method is File.browseForOpen(). This method takes a
required string argument for the title of the dialog box, and an optional
Array of FileFilter objects.
FileFilter objects allow an application to filter the viewable files in the
native dialog box. This argument is null by default, which allows the user to
select any file to which he would normally have access (i.e., not hidden
files). An application can provide as many filters as necessary, by placing
multiple FileFilter objects in an Array and passing that Array as the second
argument to File.browseForOpen().
None of the browse methods on the File class is static, and as such, an
existing reference to a valid File object must first be available. The
directory represented by that File object reference will be selected by
default when the dialog is displayed:
var file = air.File.documentsDirectory;
var filters = new Array();
filters.push( new FileFilter( "Image Files", "*.jpg" ) );
file.browseForOpen(file, filters);
When a file selection has been made, Adobe AIR will raise an event in the
issuing application. To catch that event, the application must have first
registered an event listener. The event that gets raised is Event.SELECT, and
an event object will be passed to the handler:
var file = air.File.documentsDirectory; var filters = new Array();
filters.push( new air.FileFilter( "Image Files", "*.jpg" ) );
file.addEventListener( air.Event.SELECT, doSelect );
file.browseForOpen( file, filters );
function doSelect( event )
{
alert( file.nativePath );
}
A useful property of the Event object that is sent to the handler is the
"target" which contains a reference to the originating File object. Nothing is
returned from the dialog operation to be assigned to a File object, as the
originating object will now hold a reference to the directory selected by the
user. For this purpose, it may be beneficial to have a class or global
reference to the File object, and even to reuse it:
<html>
<head>
<title>Selecting a File</title>
<style type="text/css">
body
{
font-family: Verdana, Helvetica, Arial, sans-serif;
font-size: 11px;
color: #0B333C;
}
</style>
<script type="text/javascript" src="AIRAliases.js"></script>
<script type="text/javascript">
var file = null;
function doBrowse()
{
var filters = new Array();
filters.push( new air.FileFilter( 'Image Files', '*.jpg' ) );
file.browseForOpen( 'Select Photo', filters );
}
function doLoad()
{
file = air.File.documentsDirectory;
file.addEventListener( air.Event.SELECT, doSelect );
document.getElementById( 'browse' ).addEventListener( 'click', doBrowse );
}
function doSelect( e )
{
var elem = document.createElement( 'div' );
elem.innerText = file.nativePath;
document.body.appendChild( elem );
}
</script>
</head>
<body onLoad="doLoad();">
<input id="browse" type="button" value="Browse" />
</body>
</html>
Browse for Multiple Files
Problem
An application needs to prompt the user to select multiple files from the local system using the native dialog.
Solution
Use the File.browseForOpenMultiple() method to prompt the user with a dialog
box that will allow for multiple file selection.
Discussion
Using the File class to open a single file is predominantly the same as
using the File class to open multiple files. In the case of allowing the user
to select multiple files, the appropriate method to use is
File.browseForOpenMultiple(). The File.browseForOpenMultiple() method takes
the same two arguments that the File.browseForOpen() method takes: a String to
be used in the title of the dialog, and an Array of FileFilter objects.
Once the user has selected the files from the dialog, FileListEvent.SELECT_MULTIPLE will be broadcast. The event object that is sent to the
handler will be of type FileListEvent. The FileListEvent class contains a
files property, which will be an Array of File objects representing the files
that the user selected:
var file = air.File.documentsDirectory;
file.addEventListener( air.FileListEvent.SELECT_MULTIPLE, doSelect );
function doSelect( event )
{
for( var f = 0; f < event.files.length; f++ )
{
...
}
}
Here is the complete code:
<html>
<head>
<title>Selecting Multiple Files</title>
<style type="text/css">
body
{
font-family: Verdana, Helvetica, Arial, sans-serif;
font-size: 11px;
color:#0B333C;
}
</style>
<script type="text/javascript" src="AIRAliases.js"></script>
<script type="text/javascript">
var file = null;
function doBrowse()
{
var filters = new Array();
filters.push( new air.FileFilter( 'Image Files', '*.jpg' ) );
file.browseForOpenMultiple( 'Select Photos', filters );
}
function doLoad()
{
file = air.File.documentsDirectory;
file.addEventListener(air.FileListEvent.SELECT_MULTIPLE, doSelect );
document.getElementById( 'browse' ).addEventListener('click', doBrowse ); }
function doSelect( e )
{
var elem = null;
var name = null;
var size = null;
for( var f = 0; f < e.files.length; f++ )
{
name = e.files[f].name;
size = Math.ceil( e.files[f].size / 1000 );
elem = document.createElement( 'div' );
elem.innerText = name + ' (' + size +' KB)';
document.body.appendChild( elem );
}
}
</script>
</head>
<body onLoad="doLoad();">
<input id="browse" type="button" value="Browse" />
</body>
</html>
Browse for a Directory
Problem
Application requirements dictate that you allow users to select a directory in which they will store data.
Solution
Use the File.browseForDirectory() method to prompt the user to select a
directory.
Discussion
The File.browseForDirectory() method creates a native dialog box that allows
users to select a directory. The method takes a required String argument,
which will be used to provide additional information to the user about the
purpose of the selected directory.
When a directory selection has been made, Adobe AIR will raise an event in the
issuing application. To catch that event, the application must have first
registered an event listener. The event that gets raised is Event.SELECT, and
an event object will be passed to the handler:
var file = air.File.applicationStorageDirectory;
file.addEventListener( air.Event.SELECT, doSelect );
file.browseForDirectory("Where do you want to store your photos?" );
function doSelect( event )
{
alert( file.nativePath );
}
Nothing is returned from the dialog operation to be assigned to a File object,
as the originating object will now hold a reference to the directory selected
by the user. For this purpose, it may be beneficial to have a class or global
reference to the File object, and even to reuse it:
<html>
<head>
<title>Selecting a Directory</title>
<style type="text/css">
body
{
font-family: Verdana, Helvetica, Arial, sans-serif;
font-size: 11px;
color: #0B333C;
}
</style>
<script type="text/javascript" src="AIRAliases.js"></script>
<script type="text/javascript">
var directory = null;
function doBrowse()
{
directory.browseForDirectory('Select a directory of files:');
}
function doLoad()
{
directory = air.File.documentsDirectory;
directory.addEventListener( air.Event.SELECT, doSelect );
document.getElementById('browse').addEventListener('click', doBrowse);
}
function doSelect( e )
{
var files = directory.getDirectoryListing();
var elem = null;
var name = null;
var mod = null;
var size = null;
for( var f = 0; f < files.length; f++ )
{
name = files[f].name;
mod = files[f].modificationDate;
mod = ( mod.month + 1 ) + '/' + mod.date + '/' + mod.fullYear;
size = Math.ceil( files[f].size / 1000 ) + ' KB';
elem = document.createElement('div');
elem.innerText = name + ' is ' + size +
' and was last modified on ' + mod;
document.body.appendChild( elem );
}
}
</script>
</head>
<body onLoad="doLoad();">
<input id="browse" type="button" value="Browse" />
</body>
</html>
Service and Server Monitoring
Monitor Connectivity to an HTTP Server
Problem
Your application needs to monitor and determine whether a specific HTTP server can be reached.
Solution
Use the URLMonitor class to detect network state changes in HTTPIS
endpoints.
Discussion
Service monitor classes work through event notification and subsequent polling of the designated endpoint. Service monitoring is not an integrated function of Adobe AIR directly, and needs to be added before it can be used.
The classes for service monitoring are contained in the servicemonitor.swf file, which you can find in the frameworks directory of the Adobe AIR SDK.
You should copy this file into the application project folder; you can include
it through the use of the HTML SCRIPT tag. You also need to include the
servicemonitor.swf file in the packaged Adobe AIR application. The SCRIPT
tag used to include service monitoring functionality must come before the
AIRAliases.js file is declared. You also must specify the content type on
the SCRIPT tag as application/x-shockwave-flash:
<script src="servicemonitor.swf" type="application/x-shockwave-flash"></script>
<script src="AIRAliases.js" type="text/javascript"></script>
The URLMonitor class takes a single argument in the constructor, an instance
of the URLRequest class. The URLRequest constructor takes a String that
represents the URL service endpoint to query. The URLRequest class also
contains information about how to query the endpoint (i.e., GET, POST), and
any additional data that should be passed to the server:
var request = air.URLRequest('http://www.adobe.com') ;
var monitor = new air.URLMonitor( request );
The URLMonitor class will raise a StatusEvent.STATUS event when the network
status changes. Once the event handler has been registered, the URLMonitor
instance can be told to start watching for network start changes:
monitor.addEventListener(air.StatusEvent.STATUS, doStatus);
monitor.start();
After a network change has been propagated as an event, you can use the
URLMonitor.available property on the originating URLMonitor instance to check
for the presence of a connection. The URLMonitor.available property returns a
Boolean value that reflects the state of the network. As it is necessary to
query the originating URLMonitor instance for network availability, you should
declare the object in a scope that is accessible across the application:
<html>
<head>
<title>Connectivity to an HTTP Server</title>
<style type="text/css">
body
{
font-family: Verdana, Helvetica, Arial, sans-serif;
font-size: 11px;
color: #0B333C;
}
</style>
<script src="servicemonitor.swf" type="application/x-shockwave-flash"></script>
<script type="text/javascript" src="AIRAliases.js"></script>
<script type="text/javascript">
var monitor = null;
function doLoad()
{
var request = new air.URLRequest ( 'http://www.adobe.com' );
monitor = new air.URLMonitor( request );
monitor.addEventListener(air.StatusEvent.STATUS, doStatus );
monitor.start();
}
function doStatus( e )
{
var elem = document.createElement( 'div' );
elem.innerText = monitor.available;
document.body.appendChild( elem );
}
</script>
</head>
<body onLoad="doLoad();">
</body>
</html>
Monitor Connectivity to a Jabber Server
Problem
A Jabber chat client is required to reflect network presence in the user
interface, but the endpoint is a Jabber server on a specific port, and not
HTTPS.
Solution
Use the SocketMonitor class to detect network state changes against TCP/IP
socket endpoints.
Discussion
The service monitoring features are not built into Adobe AIR directly, and
need to be added before they can be used. The servicemonitor.swf file, which
is included in the Adobe AIR SDK, must be imported as an application resource
and included via an HTML SCRIPT tag. The content type on the SCRIPT tag must
be specified, and the SCRIPT tag for the service monitor classes must come
before the AIRAliases.js SCRIPT tag.
The SocketMonitor class takes two arguments in the constructor: a String that
represents the host endpoint, and a port on which the server is listening:
var host = 'im.mydomain.com';
var port = 5220;
var monitor = new air.SocketMonitor( host, port );
The SocketMonitor class will raise a StatusEvent.STATUS event when the network
status changes. Once the event handler has been registered, calling the
SocketMonitor.start() method will start watching the network for changes:
monitor.addEventListener( air.StatusEvent.STATUS, doStatus );
monitor.start();
After a network change has been propagated as an event, you can use the
SocketMonitor.available property on the originating SocketMonitor instance to
check for the presence of a connection. The SocketMonitor.available property
returns a Boolean value that reflects the state of the network. As a best
practice, you should declare the SocketMonitor object in a scope that is
accessible across the application and is referenced directly during event
handling:
<html>
<head>
<title>Connectivity to a Jabber Server</title>
<style type="text/css">
body
{
font-family: Verdana, Helvetica, Arial, sans-serif;
font-size: 11px;
color: #0B333C;
}
</style>
<script src="servicemonitor.swf" type="application/x-shockwave-flash"></script>
<script type="text/javascript" src="AIRAliases.js"></script>
<script type="text/javascript">
var monitor = null;
function doLoad()
{
monitor = new air.SocketMonitor ( 'im.mydomain.com', 1234 );
monitor.addEventListener ( air.StatusEvent.STATUS, doStatus );
monitor.start();
}
function doStatus( e )
{
var elem = document.createElement( 'div' );
elem.innerText = monitor.available;
document.body.appendChild( elem );
}
</script>
</head>
<body onLoad="doLoad();">
</body>