Data driven AIR Tutorial Part 2
May 4, 2009
Data driven AIR Tutorial Part 2: ORM, ActiveRecord-style
Adobe AIR provides a complete SQLite database engine, but no high level API to manage data out of the box. Enter ActiveRecord.js – a Javascript library that brings Rails’ ActiveRecord-style data interfaces to your AIR applications. No more writing SQL queries, worrying about database connections or dealing with persistence – building serious database applications in AIR is a cinch with ActiveRecord.js. In this tutorial, we’re going to build a simple client management system with AIR,
Introducing ActiveJS
ActiveJS is an open source project from Aptana that provides a series of Javascript libraries, notably ActiveRecord.js. ActiveRecord.js supports a number of database backends, including Adobe AIR, Google Gears, and Aptana’s own Jaxer platform.
We’re about to build a simple client management system, to demonstrate using ActiveRecord to handle database connectivity, persistence, model construction, relationships and basic data retrieval.
Getting started
To build our AIR application, we’ll be using Aptana Studio, the official free IDE for Adobe AIR development. If you’ve used Eclipse before, head over to aptana.com and download the release for your platform, then install the Adobe AIR plugin from inside Aptana. If you’re new to Aptana/Eclipse, run through the installation guide in the “A Powerful Tool for Building AIR Apps” section on another AIR article.
Creating the project
Fire up Aptana, make sure you’ve installed the AIR plugin, then open File > New > Project. Select “Adobe AIR Project” from the “Aptana Projects” folder, click Next, and give your project a name – I’ve called mine “ClientProjects”. Click Next, and you’ll be offered two screens to change application properties – the default values should be fine. Click Next twice till you come to “Import AIR Frameworks” – here, make sure you check AIR HTML Introspector and AIR HTML Menu Builder, then click Next. We’ll be using jQuery for some DOM work, so check “jQuery 1.3″ (or similar – 1.1+ will be fine) in the “Import Javascript Library” screen, then click Finish to create your project.

Your “Project” or “Navigator” view will now have a new entry with the AIR logo for your project name. Expanding it should display the above file tree.
Creating our application
We now have a basic AIR application template to work with. Aptana has included some sample code to demonstrate a handful of AIR features – we can safely clear this out to build our application. You can safely delete LocalFile.txt now. Replace the contents of ClientProjects.html with the following:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Client Database</title>
<link rel="stylesheet" href="sample.css" />
<script type="text/javascript" src="lib/air/AIRAliases.js"></script>
<script type="text/javascript" src="lib/air/AIRMenuBuilder.js"></script>
<script type="text/javascript" src="lib/air/AIRIntrospector.js"></script>
<script type="text/javascript" src="lib/activerecord/active_record.air.js"></script>
<script type="text/javascript" src="lib/jquery/jquery-1.3.2.min.js"></script>
</head>
<body>
<h1>Clients</h1>
<table id="target_clients" border="1">
<thead>
<tr>
<th>Client</th>
<th>Contact</th>
<th>Phone No.</th>
<th>Projects</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<script type="text/javascript" src="application.js"></script>
</body>
</html>
Note that we include a laundry list of Javascript libraries in our <head> – let’s make sure these are in place.
You’ll need to change the “lib/jquery/jquery-1.3.2.min.js” line based on the version of jQuery that shipped with your Aptana. Alternatively, replace the src value with http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js – a copy of jQuery hosted on Google’s servers, and the same version I’ve used for this article.
If you aren’t using Aptana, you need to copy AIRAliases.js, AIRMenuBuilder.js and optionally AIRIntrospector.js from the frameworks/ folder inside the AIR SDK – this can be downloaded from the Adobe AIR tools page on Adobe.com. Make sure you reconstruct the file structure we have here, or modify the <head> section of the HTML to suit.
ActiveRecord.js can be downloaded from the Github repository – create a folder called activerecord under your project’s lib folder and save active_record.air.js there. I’m using the latest as of 2009-04-25.
Building the application framework
We’re going to keep our code in a Javascript file called application.js inside the main project folder, so create that now. The easiest way to create files in Aptana is to right click on our project – ClientProjects (or similar) – in the Project pane on the left, then select New > (file type). Aptana will take care of storing the file on the filesystem and keeping track of it within our project. Let’s start off our new application.js some basic structure – copy the following in:
application = {
models: {},
_init_database: function() {
},
_init_menus: function() {
},
events: {},
init: function() {
application._init_database();
application._init_menus();
application.draw_clients();
}
}
This will offer us some basic structure for our application. One global application object will prevent us from filling the mainspace, and we can easily see where our application logic is. In particular, note the models and events objects that are currently empty – we’ll use models to store our ActiveRecord models so that we have a centralised location for any application component to access them; events will simply store functions that respond to events.
Let’s start by configuring ActiveRecord for our database.
Up and running with ActiveRecord
Copy this over the _init_database function we created in application.js a moment ago:
_init_database: function() {
ActiveRecord.connect(ActiveRecord.Adapters.AIR, 'application_db');
var Client = ActiveRecord.define('clients', {
name: '',
contact: '',
contact_phone: '',
}, {
valid: function() {
if (application.models.Client.findByName(this.name)) {
this.addError('Client name already in use.');
}
}
});
Client.hasMany('projects');
var Project = ActiveRecord.define('projects', {
project_name: '',
client_id: 0,
});
Project.belongsTo('client');
application.models.Client = Client;
application.models.Project = Project;
application.models.Client.afterCreate(function (row) { application.draw_clients(); });
application.models.Project.afterCreate(function (row) { application.draw_clients(); });
application.models.Client.afterDestroy(function () { application.draw_clients(); });
},
We first start a connection via ActiveRecord to an AIR database by the name of ‘application_db’. This will be automatically dealt with on the fly – if the database does not exist, it will be created. Next, we define two models, Client and Project. Notice we don’t actually ask for these to be created – if the database tables need to be created, they will be, but ActiveRecord will handle persistence transparently and check to see if they already exist. ActiveRecord allows us to define validation on models, so for now we’ll just enforce unique names on any Client we add to the system. We also relate both these models to each other, using hasMany and belongsTo – we can now use getProjectList() on any client row, and access the client property on any project row. We add a few events to our models – we’ll look at these in just a moment.
We’ll keep these models in application.models for future reference.
Creating our menus
We’ll use the AIR Menu Builder framework, similar to our previous Data Driven AIR article. Create a new file in your main project folder called application_menus.xml, and copy over the following:
<?xml version="1.0" encoding="utf-8"?>
<root>
<menuitem label="Actions">
<menuitem label="New Client" onSelect="newClient" />
</menuitem>
</root>
We can now define our _init_menus() function. Head back to application.js and copy this over _init_menus:
_init_menus: function() {
var windowMenu = air.ui.Menu.createFromXML('application_menus.xml');
air.ui.Menu.setAsMenu(windowMenu);
},
Also, copy this into the very end of application.js:
function newClient() {
application.events.newClient();
}
This allows our AIR Menu Builder to find our application.events.newClient() function, which we’ll define in just a minute.
When we run our application, we’ll have a simple “Actions” menu – either a standard native application menu (OS X), a window menu (Windows). We don’t actually need an exit menu item in our application (system chrome / application menus provide the same functionality), but we can easily add one using the “Menu_exit” function from our last Data Driven AIR article.
Handling events
To create some “New Client/Project” buttons, we’re going to need to handle the events. We’ll point all our button handlers at functions inside the events object we added to application.js‘ application. Copy this code over the events: {}, line:
events: {
newProject: function(client_id) {
var params = {};
params.client_id = client_id;
params.project_name = prompt('Project Name');
application.models.Project.create(params);
},
newClient: function() {
var params = {};
params.name = prompt('Client Name');
params.contact = prompt('Client Contact');
params.contact_phone = prompt('Client Contact Phone');
application.models.Client.create(params);
}
},
Notice we don’t have to worry about any SQL – ActiveRecord will take care of actually validating and creating the database entry. We simply prompt the user for all field values, construct an object with properties matching column names, and hand over to the create method of our model. Models come with a number of methods like this – see this API documentation page for more details.
Constructing our table
We’re nearly finished – our backend is in place, now we just need to pull data out of our database and display it on the screen. This is where jQuery comes in.
In the HTML we started with, we had a table with a thead and tbody explicitly defined. In particular, the tbody was empty – we need to use jQuery to fill it with rows retrieved from our database. We explicitly define a tbody so that we can clear out the table before retrieving a new recordset from the database, without clearing the table header.
Create a new draw_clients function inside the application object definition in application.js – e.g. between events and init:
draw_clients: function() {
$("#target_clients tbody").empty();
var clients = application.models.Client.find({all: true, order: "id ASC"});
for (i=0;i<clients.length;i++) {
var client = clients[i];
var projects = client.getProjectList();
var project_cell = $('<td>');
for (i=0;i<projects.length;i++) {
$(project_cell).append(projects[i].project_name + '<br/>');
}
$(project_cell).append($('<a>').attr('href', '#').data('id', client.id).text('Add').click(function() {
application.events.newProject($(this).data('id'));
}));
$("<tr>").append($('<td>').text(client.name))
.append($('<td>').text(client.contact))
.append($('<td>').text(client.contact_phone))
.append(project_cell)
.appendTo("#target_clients tbody");
}
},
This is quite an intricate block of code, so let’s have a closer look.
var clients = application.models.Client.find({all: true, order: "id ASC"});
This line will simply fetch all clients in our database, and store them in clients, which is now an array of objects. Notice we use an “order” parameter – there are a number of options available for this method, but are not presently documented. A “limit” options is available, and takes an integer number of rows to retrieve, which is generally passed onto the database adapter as an SQL LIMIT clause.
var projects = client.getProjectList();
Given a single client in client, we can call the get(RelatedModelNameInCamelCase)List() method to retrieve related rows. This is one of the methods created by declaring the hasMany relationship earlier.
$(project_cell).append($('<a>').attr('href', '#').data('id', client.id).text('Add').click(function() {
application.events.newProject($(this).data('id'));
}));
Here, we create a link for our users to add projects using the events.newProject function we defined earlier. This function needs to know what client to add a project to, so we take advantage of jQuery’s data system to bind the correct client ID to the row without messy HTML attributes or other markup-based data storage.
The final cog
There’s just one line left – we need to set the wheels in motion for our application, by handing over to the init method that we defined when we first created application.js. Add this to the very end of application.js:
application.init();
And we’re done! Fire up the application using the “Run” (green play icon) button in Aptana:

The application appears, but it’s a little bare – let’s enter some sample data:

Add some projects to clients, and we’re ready to roll.

The best bit is, this is all automatically persisted – close the application, open it up again using the Run button, and all your data is still perfectly intact!
Wrapping up
Here’s our final code for application.js:
application = {
models: {},
_init_database: function(){
ActiveRecord.connect(ActiveRecord.Adapters.AIR, 'application_db');
var Client = ActiveRecord.define('clients', {
name: '',
contact: '',
contact_phone: '',
}, {
valid: function(){
if (application.models.Client.findByName(this.name)) {
this.addError('Client name already in use.');
}
}
});
Client.hasMany('projects');
var Project = ActiveRecord.define('projects', {
project_name: '',
client_id: 0,
});
Project.belongsTo('client');
application.models.Client = Client;
application.models.Project = Project;
application.models.Client.afterCreate(function (row) { application.draw_clients(); });
application.models.Project.afterCreate(function (row) { application.draw_clients(); });
application.models.Client.afterDestroy(function () { application.draw_clients(); });
},
_init_menus: function(){
var windowMenu = air.ui.Menu.createFromXML('application_menus.xml');
air.ui.Menu.setAsMenu(windowMenu);
},
events: {
newProject: function(client_id) {
var params = {};
params.client_id = client_id;
params.project_name = prompt('Project Name');
application.models.Project.create(params);
},
newClient: function() {
var params = {};
params.name = prompt('Client Name');
params.contact = prompt('Client Contact');
params.contact_phone = prompt('Client Contact Phone');
application.models.Client.create(params);
}
},
draw_clients: function() {
var clients = application.models.Client.find({all: true, order: "id ASC"});
$("#target_clients tbody").empty();
for (i=0;i<clients.length;i++) {
var client = clients[i];
var projects = client.getProjectList();
var project_cell = $('<td>');
for (j=0;j<projects.length;j++) {
$(project_cell).append(projects[j].project_name + '<br/>');
}
$(project_cell).append($('<a>').attr('href', '#').data('id', client.id).text('Add').click(function() {
application.events.newProject($(this).data('id'));
}));
$("<tr>").append($('<td>').text(client.name))
.append($('<td>').text(client.contact))
.append($('<td>').text(client.contact_phone))
.append(project_cell)
.appendTo("#target_clients tbody");
}
},
init: function(){
application._init_database();
application._init_menus();
application.draw_clients();
}
}
function newClient() {
application.events.newClient();
}
application.init();
You can download a working copy of the Aptana project here (suitable for import – extract the archive, then see File > Import : General > Import Existing Projects…).
Further reading
You might find these resources useful in exploring AIR databases, ActiveRecord.js, and general data peristence.
- ActiveJS project page
- API documentation for ActiveRecord.js
- Usage tutorial/guide for ActiveRecord.js
- Adobe Team Blog on the ActiveJS project
Entry Filed under: Adobe Air. Tags: Adobe Air, Adobe AIR Tutorial, Air Applications, Data Driven Air.
Trackback this post | Subscribe to the comments via RSS Feed