Article
Learn Adobe AIR, Part III: Beyond the Browser
Running on AIR
Now that we’ve got our AIR project ready and our interface in place, let’s start adding functionality with the APIs.
Authentication
The front line of our application is the authentication dialog. Creating our client-side login system is quite similar to creating a server-side login system, with a couple of exceptions: the authentication API is accessed over HTTP, and we have to take an extra level of care regarding sensitive data.
Here’s the HTML for our login form as it stands:
<div class="form">
<div class="input">
<label for="username">Username</label>
<input type="text" id="username"/>
</div>
<div class="input">
<label for="password">Password</label>
<input type="password" id="password"/>
</div>
<div class="input">
<input type="submit" value="Login" id="loginbtn" />
</div>
</div>
Let’s open the behavior.js file and add some JavaScript. The file is currently empty, except for a $(document).ready() function with some basic interface management code. We’ll use this to bind to the form’s submit event, so that we can catch the user clicking that Login button. Inside the $(document).ready() function, add the following:
server = 'http://bitmeta.org/air/';
$("#loginbtn").click(function(){
params = 'username='+$("#username").val()+'&password='+$("#password").val();
request = new air.URLRequest(server + 'loginService.php');
request.data = params;
request.method = air.URLRequestMethod.POST;
loader = new air.URLLoader();
loader.addEventListener(air.Event.COMPLETE, loginComplete);
try {
loader.load(request);
} catch (error) {
alert("Error connecting to login server.");
}
});
We start by using jQuery to bind to the click event of our Login button (from the HTML snippet above). Here, we’re using two key AIR APIs: URLRequest, and URLLoader. URLRequest manages the request information—in our case, the URL to load, request method to use, parameters to pass and so on—while URLLoader handles the actual HTTP transfer and deals with the results. The request object has a data property that takes variables in name=value&name=value format, just like any POST request body. We really shouldn’t transmit the password in plaintext either, even though we do above—consider using HTTPS, client-side hashing, or both.
The loader object has a number of events, including request progress, identification of response status code, and IO error—see here for a full list. At present, we’re only interested in the completion of the request, and so we use the generic air.Event.COMPLETE to catch this, with the callback function: loginComplete.
We’ll receive an XML document from the web service, which we’ll handle with jQuery in a moment. For now, we’ll define our callback handler—insert this right after the previous block:
function loginComplete(event) {
handleLogin(event.target.data);
}
With this line, we pass the XML response to our handleLogin function, and away we go!
Reading Our Response Body
It’s time to start thinking about our server backend—our demonstration server runs at bitmeta.org/air/ with the username and password “test”.
We referenced our callback function loginComplete, which is passed a single argument—an Event object. This has a target property, referring to the “target” of the event—the URLLoader—which in turn will always place the response body in its data property. Now, loginService.php will give us an XML response like this:
<?xml version="1.0" encoding="utf-8"?>
<response xmlns="http://sitepoint.com/air/a3/login">
<login code="LOGIN_PASS">4d0vncb4j41mpu...</login>
<latestNews>
<item>
<heading>New Service: Data Migration</heading>
<content>Understanding that our clients...</content>
<guid>http://example.com/rss/article/1</guid>
</item>
<item>
<heading>Security Breach!</heading>
<content>On 1/1/1 we were made aware of a...</content>
<guid>http://example.com/rss/article/2</guid>
</item>
</latestNews>
</response>
Within the login element is our session token; this is unique for each successful login. A login failure response looks like this:
<?xml version="1.0" encoding="utf-8"?>
<response xmlns="http://sitepoint.com/air/a3/login">
<login code="LOGIN_FAIL"/>
</response>
All we need now is a handleLogin function to process this response. We’ll rely on jQuery to handle the tricky XML traversing and DOM manipulation here. Copy this into the very end of your behavior.js file:
function handleLogin(data) {
if ($("login", data).attr("code") == 'LOGIN_PASS') {
air.trace('Our session token is '+$("#login", data).text());
$("#latestnews").empty();
$("latestNews item", data).each(function(i) {
item = $("latestNews item", data).get(i);
story = $('<div class="rssitem"></div>')
.append('<span>'+$("heading",item).text()+'</span>'+
'<p>'+$("content",item).text()+'</p>'+
'<a href="'+$("guid",item).text()+'">Read More...</a>')
.appendTo('#latestnews' });
$(".rssitem a").click(function(){
air.navigateToURL(new air.URLRequest(this.href));
return false;
})
$("#login").hide();
$("#main").show();
} else {
alert('Login Failed: Please Try Again.');
}
}
We first check whether the code="LOGIN_PASS" attribute value is present in the login element; if so, we record the session token we received on the console. We then empty the <div id="latestnews"> element on our main page and start filling it with the faux RSS entries included in the response. (Parsing a real RSS feed is similarly trivial.) At this point, we could also use Javascript’s usual DOM traversal functions—the data variable holds a perfectly normal XML document. To launch a URL in the default web browser (and not inside our AIR application), we need to call air.navigateToURL(new air.URLRequest(url))—in this snippet, we use the hyperlink of the current object, as we’re binding to all the links in the news section.
We use a lot of CSS selectors here—remember that the jQuery selector syntax, $(“css selector”, domdocument) will try to select elements matching the selector within the provided DOM document, defaulting to the current document. We specify our XML response document to set the context of the operation.
NOTE: Maintaining a Session
In our login routine, we’re loading the authentication information as well as the latest news in one go. Often, however, you’ll need to make further HTTP requests after authentication. In our server script here, loginService.php includes a session ID of sorts. In our case it’s generated by PHP’s session extension, but it could just as well have been any login token.
To persist with this in an AIR application, it’s quite feasible to store it in a variable upon receipt, and if needed, a cookie as well. Any JavaScript code in your application can then make use of this token by including it in future requests. Of course, if you use a cookie-based session mechanism, your application could leave the entire process to PHP, thus avoiding the need to store a session token; on the client side, the URL request libraries will automatically handle cookies for you.