Christmas Is In The AIR
That’s right, Christmas is coming up fast and there’s plenty of things to do. Get the tree and lights up, get the turkey, buy presents and who know what else. And what about Santa? He’s got a list. I’m pretty sure he’s checking it twice.
Sure, we could use an existing list making web site or even a desktop widget. But we’re geeks! What’s the fun in that? Let’s build our own to-do list application and do it with Adobe AIR!
What’s Adobe AIR?
Adobe AIR, formerly codenamed Apollo, is a runtime environment that runs on both Windows and OSX (with Linux support to follow). This runtime environment lets you build desktop applications using Adobe technologies like Flash and Flex. Oh, and HTML. That’s right, you web standards lovin’ maniac. You can build desktop applications that can run cross-platform using the trio of technologies, HTML, CSS and JavaScript.
If you’ve tried developing with AIR before, you’ll need to get re-familiarized with the latest beta release as many things have changed since the last one (such as the API and restrictions within the sandbox.)
To get started
To get started in building an AIR application, you’ll need two basic things:
- The AIR runtime. The runtime is needed to run any AIR-based application.
- The SDK. The software development kit gives you all the pieces to test your application. Unzip the SDK into any folder you wish.
You’ll also want to get your hands on the JavaScript API documentation which you’ll no doubt find yourself getting into before too long. (You can download it, too.)
Also of interest, some development environments have support for AIR built right in. Aptana doesn’t have support for beta 3 yet but I suspect it’ll be available shortly.
Within the SDK, there are two main tools that we’ll use: one to test the application (ADL) and another to build a distributable package of our application (ADT). I’ll get into this some more when we get to that stage of development.
Building our To-do list application
The first step to building an application within AIR is to create an XML file that defines our default application settings. I call mine application.xml
, mostly because Aptana does that by default when creating a new AIR project. It makes sense though and I’ve stuck with it. Included in the templates folder of the SDK is an example XML file that you can use.
The first key part to this after specifying things like the application ID, version, and filename, is to specify what the default content should be within the content tags. Enter in the name of the HTML file you wish to load. Within this HTML file will be our application.
<content>ui.html</content>
Create a new HTML document and name it ui.html
and place it in the same directory as the application.xml
file. The first thing you’ll want to do is copy over the AIRAliases.js
file from the frameworks folder of the SDK and add a link to it within your HTML document.
<script type="text/javascript" src="AIRAliases.js"></script>
The aliases create shorthand links to all of the Flash-based APIs.
Now is probably a good time to explain how to debug your application.
Debugging our application
So, with our XML file created and HTML file started, let’s try testing our ‘application’. We’ll need the ADL application located in BIN folder of the SDK and tell it to run the application.xml
file.
/path/to/adl /path/to/application.xml
You can also just drag the XML file onto ADL and it’ll accomplish the same thing. If you just did that and noticed that your blank application didn’t load, you’d be correct. It’s running but isn’t visible. Which at this point means you’ll have to shut down the ADL process. Sorry about that!
Changing the visibility
You have two ways to make your application visible. You can do it automatically by setting the placing true
in the visible
tag within the application.xml
file.
<visible>true</visible>
The other way is to do it programmatically from within your application. You’d want to do it this way if you had other startup tasks to perform before showing the interface. To turn the UI on programmatically, simple set the visible
property of nativeWindow
to true
.
<script type="text/javascript">
nativeWindow.visible = true;
</script>
Sandbox Security
Now that we have an application that we can see when we start it, it’s time to build the to-do list application. In doing so, you’d probably think that using a JavaScript library is a really good idea — and it can be but there are some limitations within AIR that have to be considered.
An HTML document, by default, runs within the application sandbox. You have full access to the AIR APIs but once the onload
event of the window has fired, you’ll have a limited ability to make use of eval
and other dynamic script injection approaches. This limits the ability of external sources from gaining access to everything the AIR API offers, such as database and local file system access. You’ll still be able to make use of eval
for evaluating JSON responses, which is probably the most important if you wish to consume JSON-based services.
If you wish to create a greater wall of security between AIR and your HTML document loading in external resources, you can create a child sandbox. We won’t need to worry about it for our application so I won’t go any further into it but definitely keep this in mind.
Finally, our application
Getting tired of all this preamble? Let’s actually build our to-do list application. I’ll use jQuery because it’s small and should suit our needs nicely. Let’s begin with some structure:
<body>
<input type="text" id="text" value="">
<input type="button" id="add" value="Add">
<ul id="list"></ul>
</body>
Now we need to wire up that button to actually add a new item to our to-do list.
<script type="text/javascript">
$(document).ready(function(){
// make sure the application is visible
nativeWindow.visible = true;
$('#add').click(function(){
var t = $('#text').val();
if(t)
{
// use DOM methods to create the new list item
var li = document.createElement('li');
// the extra space at the end creates a buffer between the text
// and the delete link we're about to add
li.appendChild(document.createTextNode(t + ' '));
// create the delete link
var del = document.createElement('a');
// this makes it a true link. I feel dirty doing this.
del.setAttribute('href', '#');
del.addEventListener('click', function(evt){
this.parentNode.parentNode.removeChild(this.parentNode);
});
del.appendChild(document.createTextNode('[del]'));
li.appendChild(del);
// append everything to the list
$('#list').append(li);
//reset the text box
$('#text').val('');
}
})
});
</script>
And just like that, we’ve got a to-do list! That’s it! Just never close your application and you’ll remember everything. Okay, that’s not very practical. You need to have some way of storing your to-do items until the next time you open up the application.
Storing Data
You’ve essentially got 4 different ways that you can store data:
- Using the local database. AIR comes with SQLLite built in. That means you can create tables and insert, update and select data from that database just like on a web server.
- Using the file system. You can also create files on the local machine. You have access to a few folders on the local system such as the documents folder and the desktop.
- Using
EcryptedLocalStore
. I like using theEcryptedLocalStore
because it allows you to easily save key/value pairs and have that information encrypted. All this within just a couple lines of code. - Sending the data to a remote API. Our to-do list could sync up with Remember the Milk, for example.
To demonstrate some persistence, we’ll use the file system to store our files. In addition, we’ll let the user specify where the file should be saved. This way, we can create multiple to-do lists, keeping them separate and organized.
The application is now broken down into 4 basic tasks:
- Load data from the file system.
- Perform any interface bindings.
- Manage creating and deleting items from the list.
- Save any changes to the list back to the file system.
Loading in data from the file system
When the application starts up, we’ll prompt the user to select a file or specify a new to-do list. Within AIR, there are 3 main file objects: File
, FileMode
, and FileStream
. File
handles file and path names, FileMode
is used as a parameter for the FileStream
to specify whether the file should be read-only or for write access. The FileStream
object handles all the read/write activity.
The File
object has a number of shortcuts to default paths like the documents folder, the desktop, or even the application store. In this case, we’ll specify the documents folder as the default location and then use the browseForSave
method to prompt the user to specify a new or existing file. If the user specifies an existing file, they’ll be asked whether they want to overwrite it.
var store = air.File.documentsDirectory;
var fileStream = new air.FileStream();
store.browseForSave("Choose To-do List");
Then we add an event listener for when the user has selected a file. When the file is selected, we check to see if the file exists and if it does, read in the contents, splitting the file on new lines and creating our list items within the interface.
store.addEventListener(air.Event.SELECT, fileSelected);
function fileSelected()
{
air.trace(store.nativePath);
// load in any stored data
var byteData = new air.ByteArray();
if(store.exists)
{
fileStream.open(store, air.FileMode.READ);
fileStream.readBytes(byteData, 0, store.size);
fileStream.close();
if(byteData.length > 0) { var s = byteData.readUTFBytes(byteData.length); oldlist = s.split(“\r\n”);
// create todolist items
for(var i=0; i < oldlist.length; i++)
{
createItem(oldlist[i], (new Date()).getTime() + i );
}
}
}
}
Perform Interface Bindings
This is similar to before where we set the click event on the Add button but we’ve moved the code to save the list into a separate function.
$('#add').click(function(){
var t = $('#text').val();
if(t){
// create an ID using the time
createItem(t, (new Date()).getTime() );
}
})
Manage creating and deleting items from the list
The list management is now in its own function, similar to before but with some extra information to identify list items and with calls to save our list after each change.
function createItem(t, id)
{
if(t.length == 0) return;
// add it to the todo list
todolist[id] = t;
// use DOM methods to create the new list item
var li = document.createElement('li');
// the extra space at the end creates a buffer between the text
// and the delete link we're about to add
li.appendChild(document.createTextNode(t + ' '));
// create the delete link
var del = document.createElement('a');
// this makes it a true link. I feel dirty doing this.
del.setAttribute('href', '#');
del.addEventListener('click', function(evt){
var id = this.id.substr(1);
delete todolist[id]; // remove the item from the list
this.parentNode.parentNode.removeChild(this.parentNode);
saveList();
});
del.appendChild(document.createTextNode('[del]'));
del.id = 'd' + id;
li.appendChild(del);
// append everything to the list
$('#list').append(li);
//reset the text box
$('#text').val('');
saveList();
}
Save changes to the file system
Any time a change is made to the list, we update the file. The file will always reflect the current state of the list and we’ll never have to click a save button. It just iterates through the list, adding a new line to each one.
function saveList(){
if(store.isDirectory) return;
var packet = '';
for(var i in todolist)
{
packet += todolist[i] + '\r\n';
}
var bytes = new air.ByteArray();
bytes.writeUTFBytes(packet);
fileStream.open(store, air.FileMode.WRITE);
fileStream.writeBytes(bytes, 0, bytes.length);
fileStream.close();
}
One important thing to mention here is that we check if the store is a directory first. The reason we do this goes back to our browseForSave
call. If the user cancels the dialog without selecting a file first, then the store points to the documentsDirectory
that we set it to initially. Since we haven’t specified a file, there’s no place to save the list.
Hopefully by this point, you’ve been thinking of some cool ways to pimp out your list. Now we need to package this up so that we can let other people use it, too.
Creating a Package
Now that we’ve created our application, we need to package it up so that we can distribute it. This is a two step process. The first step is to create a code signing certificate (or you can pay for one from Thawte which will help authenticate you as an AIR application developer).
To create a self-signed certificate, run the following command. This will create a PFX file that you’ll use to sign your application.
adt -certificate -cn todo24ways 1024-RSA todo24ways.pfx mypassword
After you’ve done that, you’ll need to create the package with the certificate
adt -package -storetype pkcs12 -keystore todo24ways.pfx todo24ways.air application.xml .
The important part to mention here is the period at the end of the command. We’re telling it to package up all files in the current directory.
After that, just run the AIR file, which will install your application and run it.
Important things to remember about AIR
When developing an HTML application, the rendering engine is Webkit. You’ll thank your lucky stars that you aren’t struggling with cross-browser issues. (My personal favourites are multiple backgrounds and border radius!)
Be mindful of memory leaks. Things like Ajax calls and event binding can cause applications to slowly leak memory over time. Web pages are normally short lived but desktop applications are often open for hours, if not days, and you may find your little desktop application taking up more memory than anything else on your machine!
The WebKit runtime itself can also be a memory hog, usually taking about 15MB just for itself. If you create multiple HTML windows, it’ll add another 15MB to your memory footprint. Our little to-do list application shouldn’t be much of a concern, though.
The other important thing to remember is that you’re still essentially running within a Flash environment. While you probably won’t notice this working in small applications, the moment you need to move to multiple windows or need to accomplish stuff beyond what HTML and JavaScript can give you, the need to understand some of the Flash-based elements will become more important.
Lastly, the other thing to remember is that HTML links will load within the AIR application. If you want a link to open in the users web browser, you’ll need to capture that event and handle it on your own. The following code takes the HREF
from a clicked link and opens it in the default web browser.
air.navigateToURL(new air.URLRequest(this.href));
Only the beginning
Of course, this is only the beginning of what you can do with Adobe AIR. You don’t have the same level of control as building a native desktop application, such as being able to launch other applications, but you do have more control than what you could have within a web application. Check out the Adobe AIR Developer Center for HTML and Ajax for tutorials and other resources.
Now, go forth and create your desktop applications and hopefully you finish all your shopping before Christmas!
About the author
Jonathan Snook writes about tips, tricks, and bookmarks on his blog at Snook.ca. He has also written for A List Apart and .net magazine, and has co-authored two books, The Art and Science of CSS and Accelerated DOM Scripting. He has also authored and received world-wide acclaim for the self-published book, Scalable and Modular Architecture for CSS sharing his experience and best practices on CSS architecture.