Our First AIR App

With Adobe AIR now up to 100 million downloads, being utilised by big name players such as the BBC and increasingly invading our desktop Headscape decided to give it a go.

This is a guest post by two of the Headscape developers – Craig Rowe and Dave McDermid

Setting up

Adobe Air is Flash + WebKit + SQL Lite on the desktop. As a Flash developer you can dive right in and use the Air extension for Flash to publish your beautifully crafted swfs and AS code into an installable cross platform desktop app. However, the flash projector has been around for a while doing similar things and we wanted to put our hard earned web development skills to productive use. So we went the HTML/Javascript route.

The SDK is free for download but Aptana provides a rather neat eclipse based IDE in which to work. Handily the new project wizard allows you to import a multitude of Javascript libraries making it incredibly easy to get a new project up and running with your preferred initial setup (we went for jQuery).

The Problem

To make the exercise worth while we needed a real world problem to solve. Trivial examples usually do very little other than increase your ability to copy/paste example code and do your best fireworks night ‘ooo’ ‘ahh’ impression at it. Instead, we chose to add to our server admin experience…

As loving, caring web developers we actively monitor all our servers, and most of our live websites. For a while this was a DOS script, this was then migrated to a Bash script on a Cron job. It worked great, but required a computer science degree to maintain. So here was our problem: we needed a pretty, maintainable and reliable app to monitor websites. All it had to do was let us know when one disappears or throws a nasty error.

Download and try our site watcher AIR application

The Journey

Step 1: The wireframe

Paper Wireframe

A new Adobe AIR project in Aptana comes with an html file named after your application which acts as your main program window. Creating a basic layout with a few buttons, titles etc can be done in a snap. The html, css and javascript are dealt with in exactly the same way as if intended to be deployed as a website (with no browser compatibility worries as we are only targeting the Adobe AIR WebKit rendering engine).

Step 2: The magic

The wireframe identified the main viewer as comprising of an unordered list of sites, each with their current status and edit/check/remove links. This list needed to be persisted, but editable by the user.

If this was a website we may be looking to server side scripting and a database of some nature. However, we had only jQuery and the air libraries. Although SQL Lite was an option we decided it was an over complication for what is a relatively simple, first AIR app. So, knowing that we could use jQuery to manipulate a DOM and the air libraries to save and open local files we opted for XML as a data source.

<?xml version="1.0" encoding="utf-8"?>
<sites>
<site address="http://www.headscape.co.uk/" status="200" frequency="30">
Headscape&#146;s website
</site>
...
</sites>

Looking back into our application html file we can see that we are given a readLocalfile() function that returns the string contents. This can then be passed into a jQuery object and manipulated in the usual way.

[The canny among you will notice that this readLocalfile() function is merely a few calls to the flash filesystem classes (using the AIR aliases). In fact at some points I directly call the flash library rather than using the AIR alias. There’s no functional difference, I’m just used to the flash namespaces]

With this ability added to the jquery ajax capabilities the application flow could be easily envisaged as follows:

  • On DOM Ready read the local xml file
  • For each site element in the xml create an LI element with the appropriate display and action elements
  • Fire off a jquery ajax call for each site
  • Use the response code to formulate a class for the LI to change its display
  • Fire off any notifications if the response code has changed i.e. e-mail, notification window, twitter post
  • Set a timeout before checking again
  • On window close parse the unordered list back into xml and write it to the persistent file

The Stumbling Blocks

Viewing the source of an installed AIR app can be done by nipping into program files (or Applications for the MACs amongst us) and looking in the application name directory. Here you will see the html, css and javascript files that make up the app (so we can continue to learn from others deployments just as we would with a website).

A brief look at the sitewatcher source and the flow described above becomes immediately clear. ‘Sitewatcher.html’ is our main form and it includes script.js as the main driver of the application with the #sites ul as the main containing element. The rest is GUI. ‘PopulateUIfromXML()’ directly completes steps 1-2 and fires off the 3-6 process via ‘CheckSite()’. However dispersed within this are the unusual non-front end website development bits, so we’ll look at those now:

Acting as a System Tray App

Many AIR apps, particularly those to do with notification (twitter, yammer etc) seem to want to run as system tray applications, we were no different.

The process of doing so is relatively easy, and encapsulated in the appropriately named SetUpSysTray() function of script.js. Essentially what we need to achieve is an override of the minimise behaviour, the setting of an appropriate icon and the associated window toggling behaviour.

Window.nativeWindow gives us access to the OS window holding our html window, and we can listen to events on it in much the same way. ‘nwMinimized’ is set to fire on display state changing and, if being minimized, instead docks (hides) the window and prevents the default behaviour.

if(air.NativeApplication.supportsSystemTrayIcon)
window.nativeWindow.addEventListener(runtime.flash.events.NativeWindowDisplayStateEvent.DISPLAY_STATE_CHANGING, nwMinimized);
function nwMinimized(nativeWindowDisplayStateEvent) {
if(nativeWindowDisplayStateEvent.afterDisplayState == runtime.flash.display.NativeWindowDisplayState.MINIMIZED) {
nativeWindowDisplayStateEvent.preventDefault();
Dock();
}
}
function Dock() {
window.nativeWindow.visible = !window.nativeWindow.visible;
}

This works fine but on its own will actually just hide the window from view/the taskbar leading the user to have to use task manager to close it. To ensure that an icon for your application sits in the system tray we need to set an icon for the nativeApplication (the mere presence of which will cause windows to display the app in the systray).

Back to SetUpSysTray() and we’re using a content loader to load the icon graphic into memory, with an iconLoadComplete handler waiting to do the work:

var iconLoader = new runtime.flash.display.Loader();
iconLoader.contentLoaderInfo.addEventListener(air.Event.COMPLETE, iconLoadComplete);
iconLoader.load(new air.URLRequest("../icons/AIRApp_16.png"));
function iconLoadComplete(event){
if(air.NativeApplication.supportsSystemTrayIcon){
air.NativeApplication.nativeApplication.icon.bitmaps = new Array(event.target.content.bitmapData);
air.NativeApplication.nativeApplication.icon.tooltip = "SiteWatcher";
air.NativeApplication.nativeApplication.icon.menu = new air.NativeMenu();
// Create Menu Items
var openCommand = new air.NativeMenuItem("Toggle");
openCommand.addEventListener(air.Event.SELECT,function(event){
Dock();
});
var sep = new air.NativeMenuItem("", true);
var exitCommand = new air.NativeMenuItem("Exit");
exitCommand.addEventListener(air.Event.SELECT,function(event){
air.NativeApplication.nativeApplication.exit();
});
// Add Items to menu
air.NativeApplication.nativeApplication.icon.menu.addItem(openCommand);
air.NativeApplication.nativeApplication.icon.menu.addItem(sep);
air.NativeApplication.nativeApplication.icon.menu.addItem(exitCommand);
}
}

We simply set the native application icon to be the bitmap data before finalising the sys tray setup by creating a menu to appear on click.

Note for MACs: The minimised event listener is not applied if the system does not support system tray icons. This is to avoid confusion on MACs where a minimise goes to the MAC dock anyway.

Notification Windows

So the app can now run in the system tray and use jQuery to check the sites listed in an XML file at regular intervals. However we need a process of notification. The App could be running on an actively used machine in which case we want messenger style pop-ups. Or it could be running on a separate machine/server from where we want it to send e-mail/other notifications.

In the case of window notifications we took a short cut and used some example code from Adobe Developer Center. This is encapsulated within the DisplayManager.js and Message.js files. Display Manager acts as a queue, dequeuing and displaying on a timed basis if the user is present. This is an important requirement as you do not want a user missing a notification prompt merely because they were away from their desk for a while. It can be easily achieved via the USER_IDLE and USER_PRESENT air events – in this case stopping and starting the poller.

‘CheckSite()’ simply queues message HTML when the response code received is different from the previously stored code. When the queue poller is running (the user is present) the message is dequeued and displayed via Message.js.

At this point it is worth remembering that notification windows are no different to any other native window. It’s just a name we’re giving to the way we are using native windows. They therefore have their own events, contents, position etc and this can be seen by the Message.js code where a new chromeless, transparent native window is created and its contents loaded from the message.html template.

The display process is then as follows:

  • Message.js stores the message content in a variable on the new window
  • MessageScripts.js, then running once the message.html dom is ready, sets a listener on the HTML_BOUNDS_CHANGE event before setting the message variable content as the body – ultimately firing the bounds change event
  • This event is handled by setting the native window height to match before firing the layoutComplete event
  • On hearing this Message.js makes the window visible, finds an appropriate resting position (in relation to any other messages) and animates it in.
E-mailing

A key feature of any site watcher is to let us know when something bad happens. The combination of emails and an email-to-text service allows us to be notified the minute we spot trouble. This was easy enough in the bash script, using sendmail on the Mac. Not so straight forward for an AIR app. We can’t run sys commands and there is no built-in SMTP server. The solution is to use sockets in AIR. A little hardcore, but it keeps the solution nice and tight.

Anyone who’s sent an email with telnet will know that the principle of SMTP is, as the acronym suggests, simple. Adobe gives us plenty of clues for opening sockets and listening for messages. All we had to do was make sure we sent the right info. There are some restrictions in opening sockets from scripts outside the application sandbox, but for our purposes it worked a treat. With a little trial and error we were firing off emails left right and centre.

The icing on the cake was adding twitter support. With a one-line AJAX call in JQuery and a little config it was a no-brainer. This allows us to keep an online audit in the form of a private tweet stream. For people who check twitter more frequently than their email, it’s handy for notifications. If Twitter let us UK folk receive updates via SMS again then we can ditch email-to-text in favour of Twitter.

Step 3: The makeover

One of the nice benefits to working in the web-kit world is being able to use some CSS3 styling such as rounded corners. So we went to town. The more design that is CSS based and the less that is image based the better.

JQuery UI allowed us to make the entire list sortable in a sweep of the mouse, and the prefs popup tabbed in a blink (suddenly there was heaps to customise!).

The End

Hopefully this post has given you an understanding of how quick and easy it really is to make a useful AIR application. We’ve shown how you can implement a system tray application that utilises notification popup windows and sends e-mails as well as uses local files as a data store. This is not intended as a best practice discussion. It was our first AIR app and developed in a very small amount of time as a proof of concept and so that we could share our experiences with you. We welcome any feedback.

Download and try our site watcher AIR application

See also:

More on Development

Boagworks

Boagworld