AJAX is power. It makes Internet applications look, feel and perform in the eyes of the user like desktop apps, all while run from the server and written in the platform-agnostic languages of HTML and JavaScript. But, it carries a heavy price: breaking the browser.
“The customer is always right.” This time-worn adage—attributed to either Harry Selfridge, founder of the famous British Selfridges department store, or Marshall Field, of the Chicago department store that bears his name—has been discussed and dissected to no end. Undoubtedly, every one of us can come up with plenty of cases when customers aren't right, and it does not make sense to treat them that way. What is true, however, is that if you want to sell (or develop) something that's useful to customers, you must build it for the way they actually work, not the way you want them to work.
In the Web's early days, we were all entranced by the ability to access any application anywhere, without installing anything more than a browser. Developers loved the idea of writing in a single universal language. Even better, HTML is declarative—no interesting components and callbacks, no per-platform or per-OS-version oddities (more or less). Users loved the simple book paradigm. You could go back and forward (which, unsurprisingly, were the names of the buttons), and even click reload. The semantics were simple; writing for the platform was easy, and deployment, compared to managing each desktop, felt like the new Enlightenment.
The downsides, of course, were obvious, but a fair price to pay. If each page was statically generated with just HTML, every change, however small—say a change in text or adding a warning—required a complete page reload. Besides the headache for the user, it was unnatural and slow. Some pundits in the 1990s suggested that the Web would never be a dominant platform for this very reason. Dynamic HTML based on JavaScript, which allowed DOM manipulation, gave us some leeway, but anything that came from the server—real data—required a reload.
In the early to mid-2000s, developers began to explore how to communicate with the server without requiring page reload. Microsoft introduced the XMLHTTP ActiveX control in 1999, later adopted by every other browser. In 2005, Jesse Garrett, cofounder of Adaptive Path, coined the term Asynchronous JavaScript with XML, or AJAX. Although Jesse didn't invent it, he certainly popularized it, which once again underscores the importance of marketing that we engineers tend to overlook. As an interesting aside, one of the earliest known usages of AJAX occurred in...1596, by Sir John Harington, to describe his new invention: the flush toilet.
AJAX was wonderful. We could get what we wanted from the server without reloading the entire Web page. We could process it in the background. We could get as little or as much as we wanted. It seemed Web apps, now called Rich Internet Apps, finally were fully competitive with desktop apps in terms of ease of deployment and performance. It enabled such ubiquitous apps as Google Maps, which would have been impossible without AJAX.
The big problem with AJAX apps is that they broke Web semantics. The Refresh, Back and Forward buttons work entirely on the address in the URL bar of the browser. In the days of static pages, that mostly indicated where you were: http://example.com/store?product=12345 was definitely different from http://example.com/store?product=99999.
In the modern RIA AJAX world, however, the URL was http://example.com/store. With the product rendered using AJAX, the URL unchanged, reloading was highly unlikely to bring you back to where you were.
The first responses were to add complex state to the server. JavaEE, PHP frameworks and others all added session variables in which you could store oodles of information about what the user's last request was, and so you could roughly attempt to reconstruct it for the next request. The entire JavaServer Faces (JSF) framework is built around such complex state semantics. These did the job, more or less, but they were very complex and required lots of effort with which to work.
The next attempts essentially said, “we don't support browser buttons!” Put in other terms, “we and the technology are right, and the user is wrong.” As anyone who ever has been in business knows, this strategy is doomed to failure. It may work, for a little while, if your customer has no alternative, but customers who are told they are wrong and “just don't get it” quickly will look for alternatives. Silicon Valley is littered with the corpses of startups that whined, “our customer just doesn't get it.” Of course, it was the startup (and the engineers) who just didn't get it.
What we needed, then, was a way to use AJAX apps and modify the URL bar in a way that it would not reload the page, yet still give fairly complete indication of where we were. Thus, Back and Forward, not to mention Refresh, would work just fine.
The magic is in one little character, the hash (#). In the HTML specification RFC 1866, you can give a name to an anchor, as follows:
<a name="myname"/>
If you do so, a browser should be able to go to the named section on the page by appending # and the anchor name to the URL. For example, if you have an HTML page named mypage.html:
<html> <head> <!-- lots of stuff --> </head> <body> <div>Lots of content</div> <a name="part2"/> <div>Even more content</div> </body> </html>
To get to the above page, you would go to http://example.com/mypage.html. But, if you wanted to go to that page and directly to part2, you would go to http://example.com/mypage.html#part2.
The most interesting part is that if the browser is already on mypage.html and you go to mypage.html#part2, the browser should, and will, go directly to part2 without reloading the Web page. Even further, if the browser cannot find an anchor named part2, it will fail silently and graciously. Last, but not least, JavaScript events can capture this change and process it.
With the above, we have the making of a system that uses AJAX for Rich Internet Application dynamism, yet can change the URL to indicate where we are, and thus work with, rather than against, the user. As a matter of fact, if you use Gmail and look closely, you will see that this is exactly how it works.
Of course, remembering to manage the URLs can be difficult and changes the way you work. Wouldn't someone have developed a framework to manage all of this?
Sammy is an amazing Web framework developed by Aaron Quint. Not only does it provide the framework for managing the URLs, as well as lots of additional functionality to boot, but it also actually dramatically improves how it writes your client-side apps. You move from programmatically driven to declarative. You return to the ease of use of the early Web 1.0 days, when the URL defined exactly where you were, but without giving up the dynamism of AJAX. Once again, the URL becomes the declarer of location in your app, and you can leverage its full power.
Let's explore a basic Sammy app. For our purposes, let's use a contact application. To keep things simple, let's not do any data updating in this article, although Sammy's semantics fully support it. Let's stick with simple GETs. In the contact application, we have ten contacts, each with the ID of 1 through 10 (complicated!), and each with properties of First Name, Last Name and Email. Our application view has a left pane, wherein contacts are listed, and a right pane, wherein contact details are shown. Remember, we want this to be a Rich Internet Application, all running in a single page.
Word of warning: the code in this article may be incomplete. If you want to download and run it, get the sample app off the Web (see Resources).
First, let's define our single HTML page contacts.html:
<html> <head> <script type="text/javascript" charset="utf-8" ↪src="jquery.min.js"></script> <script type="text/javascript" charset="utf-8" ↪src="sammy.min.js"></script> <script type="text/javascript" charset="utf-8" ↪src="contactapp.js"></script> <style type="text/css"> #list {float: left; width: 48%;} #details {float: left; width: 48%;} </style> </head> <body> <h2>Contact Application</h2> <p>Click on a contact to view the details</p> <div id="list"> <table></table> </div> <div id="details"> <table> <tr><td>First Name:</td><td id="firstName"></td></tr> <tr><td>Last Name:</td><td id="lastName"></td></tr> <tr><td>Email:</td><td id="email"></td></tr> </table> </div> </body> </html>
Notice several elements:
Installation: we included jQuery, a prerequisite for Sammy (and a really great library to boot).
Installation: we included Sammy, after jQuery.
HTML: the page is really simple. There are two blank divs, one with the ID list, the other with the ID details. They are floated.
Next, we need to declare what all the states are in which the application can exist. These will determine what paths we want. In our contacts app, we really have only two states: 1) listing the contacts and 2) viewing one particular contact (while the main list remains open).
In keeping with RESTful style, let's declare our URLs as follows:
1) Listing the contacts: contacts.html#/contacts.
2) Viewing one particular contact: contacts.html#/contacts/:id (where :id is replaced by the ID of the viewed contact).
In addition, we want a default path. What happens if the user just opens contacts.html?
3) Default path: contacts.html, re-routed to contacts.html#/contacts.
Notice something interesting. We are defining various declarative paths. When each of these paths is encountered, we want to take a certain action. Essentially, these are routes. Most Ruby-based frameworks (Sinatra, Rails, Merb/Rails3 and so on) use this exact language, as does Sammy.
So, we have three routes and their actions:
contacts.html→redirect to contacts.html#/contacts.
contacts.html#/contacts→list contacts.
contacts.html#/contacts/:id→show details for contact :id.
In our included JavaScript file contactapp.js, we declare each of the routes:
var app = $.sammy(function(){ // for the verb GET with the path #/, go to #/contacts this.get("#/",function(context){ this.redirect("#/contacts"); }); // for the verb GET with the path #/contacts, render the contacts this.get("#/contacts",function(context){ // get our contact list from the server $.get("/contacts",function(res,status) { // render the results - should include // status-checking for safety // jQuery already parsed the response to JSON for us var list = res, tr, td, table = $("#list table"), a; // clear the existing list table.empty(); // use jQuery to go through each result $.each(list,function(i,elm) { tr = $("<tr></tr>").appendTo(table); td = $("<td></td>").appendTo(tr); // the key part: make it a URL a = $("<a></a>").attr("href","#/contacts/"+elm.id).text ↪(elm.lastName + " " + elm.firstName).appendTo(td); }); },"json"); // hide the details $("#details table").hide(); }); // for the verb GET with a specific path #/contacts/:id, // render that one contact this.get("#/contacts/:id",function(context){ // get our contact list from the server - access // param :id as this.params.id $.get("/contacts/"+this.params.id,function(res,status) { // render the results - should include // status-checking for safety // jQuery already parsed the response to JSON for us var contact = res, table = $("#details table"); // find the elements in the table, and fill them with the data $("#firstName",table).text(contact.firstName); $("#lastName",table).text(contact.lastName); $("#email",table).text(contact.email); // make sure the table is shown table.show(); },"json"); }); }); // set up a default route for contacts.html $(function(){ app.run("#/"); });
Notice several key elements:
There are no event handlers here at all. Although we might need some for things like edit buttons or key presses, navigation in the app really happens using URL <a> links. This makes it really easy to manage the app and understand what every change does. Clicking on a contact in the list is clicking on a URL. We just happen to use that URL to control our app.
We could have used a handler just as well. Instead of using an <a> link, we could have put on a handler with $("list td").click(function(e),{...});.
This application is incredibly short and easy to understand. That is the beauty of Sammy.
The browser URL changes, but the page does not reload. We remain in the Rich Internet App world, yet browser semantics simply work: Back, Forward, Reload. Try it!
The full sample, without minified JS, is available on-line (see Resources).
Sammy gives us the power to provide Rich Internet Applications simultaneously, work with the user's mindset rather than against it, and program our apps using routes declaratively, making it much simpler to build yet richer Internet applications.
The Sammy library is open source under the MIT license and available on-line (see Resources).