Developing Sites With AJAX
Developing Sites With AJAX
Almost every movie has a scene in which a character pull the protagonist aside and says, “There’s
something you should know about [insert another character's name here].” Most of the time, we find
out some dark secret about a supposed friend of the protagonist or that the main ally is actually an
evil overlord. This is that moment, and I am here to tell you a few things about our friend in the Web
2.0 world: AJAX.
We seem to have AJAX licked. The Web technology is ubiquitous, and libraries and frameworks
make it dead easy for us to create highly interactive Web applications and to spice up our static pages
and blogs.
1 <div id="target"></div>
2 <p><a href="#" class="ajaxtrigger">Let there be AJAX magic</a></p>
1 $('.ajaxtrigger').click(function(){
2 $('#target').load('ajaxcontent.html');
3 });
In a browser, if we clicked on the link labelled “Let there be AJAX magic,” the content of the HTML
documentajaxcontent.html would be loaded and written into the element with the ID target.
You can try this very simple AJAX example here. It’s simple and easy to use, but what’s really
happening there? What is AJAX?
[Offtopic: by the way, did you know that Smashing Magazine has one of the most influential and
popular Twitter accounts? Join our discussions and get updates about useful tools and resources —
follow us on Twitter!]
What Is AJAX?
After the main HTML document has loaded, AJAX loads content from the server and replaces parts of
the document with that content rather than reload the main document. It’s as simple as that. AJAX
stands for “Asynchronous JavaScript and XML” and was meant to load only XML documents, but we
soon used it to load everything under the sun, and so the XML part was quickly forgotten. The
asynchronous part is the killer feature; but what is it?
The traditional model for web applications (left) compared to the Ajax model (right).
1. The user enters a URI (like http://wait-till-i.com/index.php) into a user agent (usually a
browser).
2. The browser turns this URI into an IP and requests the file located at the URI specified
endpoint.
3. The browser loads the file and, if it recognizes the document type, tries to display it.
4. If the document is in HTML, we get an interface that we can interact with; for example, by
clicking a link or entering data into a form and submitting it.
5. In both cases, the whole document is replaced and the sequence restarts.
This has worked since the beginning of the Web and has become expected behaviour for Web surfers.
With AJAX, we disrupt this sequence of events. Instead of reloading the document or loading a new
one, we replace only a part of the interface, either when the user requests it or automatically every
few seconds to display new information.
We maintain a consistent interface, rather than discard it only to bring it up again with a few
slight changes after a long and annoying loading process.
We request only the data that we need, when we need it, saving us a lot of server traffic.
We are able to offer data without wrapping HTML around it to make it an interface.
We allow for simultaneous interaction; a user would be able, for example, to fill out a form
while an attachment uploads in the background.
However, with great power comes great responsibility, and with AJAX we have taken it upon ourselves
to simulate browser behavior for end users.
1 <div id="target"></div>
2 <p><a href="#" class="ajaxtrigger">Let there be AJAX magic</a></p>
This is not useful HTML. If JavaScript is not available or anything else goes wrong, you would be
offering the end user a link that goes nowhere. This is annoying; I’ve come to your website, took the
step of clicking a link, got excited by the prospect of awesome content but don’t get anything. Not
good. So, rather than keep the URI in the JavaScript part of the AJAX solution, leave it in the HTML:
1 <div id="target"></div>
2 <p><a href="ajaxtest-fullpage.html" class="ajaxtrigger">
3 Let there be AJAX magic
4 </a></p>
This would ensure that the link works; if there is a JavaScript error, the browser would simply move
on to load and display ajaxcontent.html. The jQuery code would change accordingly:
1 $('.ajaxtrigger').click(function(){
2 var url = $(this).attr('href');
3 $('#target').load(url);
4 return false;
5 });
Instead of hard-wiring a URI to load, we just read the href attribute of the link. The return
false is needed to stop the browser from following the link after jQuery has initiated the AJAX
request. This also means that any link with the class ajaxtrigger will load content via AJAX and
display it in the element with the ID target. You can try this reusable AJAX example here.
There is a problem, of course, because the document we load might be a full HTML document, with a
head and a body and so on. This works well in the browser, but the AJAX request would load and
inject this document it into another document, which is invalid HTML and would cause display issues.
Try this out by clicking the “Load a full document” link on the page referred to above.
jQuery is all about selectors, which is why the load() function allows you to cut down on the
returned HTML by defining a selector. This means that you can change the script to the following (you
can try the selector filtering example for yourself):
1 $('.ajaxtrigger').click(function(){
2 var url = $(this).attr('href');
3 $('#target').load(url+' #bd blockquote');
4 return false;
5 });
This loads only the blockquote into the other document, so you wouldn’t be creating invalid HTML
with the AJAX call. However, we lose the other benefit of AJAX, which is to load less content. If the
page is 100 KB and you want to show only the main text, which is 2 KB, why should your users have
to wait for 98 KB to load?
To work around this, you need to go server-side. In PHP, you can get information about the request
that was sent to load the page. One bit of information is the request method; JavaScript libraries such
as jQuery send a specific header across when they load a document with AJAX. You can use this in
PHP to set up conditional content:
1 <?php if($_SERVER['HTTP_X_REQUESTED_WITH']=='XMLHttpRequest'){?>
2 This is content requested by AJAX.
3 <?php }?>
4
5 <?php if($_SERVER['HTTP_X_REQUESTED_WITH']==''){?>
6 This is the normal content requested in a browser
7 <?php }?>
Try this header, switching out example for yourself: click the “Load a document with AJAX” link, and
then right-click (or Command-click) the same link to open it in a new tab (or hit the “Load the same
document without AJAX” link). The results should be “This is content requested by AJAX” and “This is
the normal content requested in a browser” respectively.
This way, you can keep all of the header and footer information in includes and load them only when
the request could not be done with AJAX. Try the header includes example to see it in action:
01 <?php if($_SERVER['HTTP_X_REQUESTED_WITH']==''){?>
02 include('header.php');
03 <?php }?>
04
05 <blockquote
06 cite="http://ia341030.us.archive[...]-h.htm">
<p>Alice was beginning to get very tired of sitting by her sister on the
bank, and of having nothing to do: once or twice she had peeped into the
book her sister was reading, but it had no pictures or conversations in
it, and where is the use of a book, thought Alice, without
07 pictures or conversations? So she was considering in her own mind,
(as well as she could, forthe hot day made her feel very sleepy and stupid,)
whether the pleasure of making a daisy-chain was worth the trouble of
getting up and picking the daisies, when a white rabbit with pink eyes ran
close by her.</p>
08 </blockquote>
09 <p>Excerpt taken from
10 <a href="http://ia341030.us.archive[...]-h.htm">archive.org</a>.
11 </p>
12
13 <?php if($_SERVER['HTTP_X_REQUESTED_WITH']==''){?>
14 include('footer.php');
15 <?php }?>
“Unobtrusive JavaScript” is the term for this method of developing highly interactive websites. It was
coined byStuart Langridge in 2002, and I wrote a self-training course for it in 2004. Incidentally,
Stuart was also the first author to cover AJAX in a JavaScript book, the unfortunately named DHTML
Utopia. My own not-quite-so-succinctly-titled book Beginning JavaScript with DOM Scripting and
AJAX was, I think, the second. Both books follow the approach shown here and create AJAX solutions
that fall back to non-JavaScript versions.
Jeremy Keith tried to further popularize this idea of “safer AJAX” in 2006 by calling it “Hijax”, and he
wrote a book titled Bulletproof AJAX in 2007. Sadly, though, I have encountered people who use this
as an excuse, saying, “We’re building an AJAX solution now, and we’ll move it to Hijax later.” This will
not work! Do it right the first time and you’ll have a stable solution. There is no “We’ll fix it in the next
iteration” when it comes to essential functionality in Web development: 12 years of professional
development have taught me that much.
AJAX Design Challenges
In dealing with AJAX as designers, we have to reconsider the ways in which we define interfaces.
Rather than concentrate on the look and feel of the page and subsequent pages, we need to drill down
to an atomic level. Each part of an AJAX interaction needs to be defined. Also, think about non-
JavaScript versions of widgets.
With AJAX interfaces, we move into a world of applications that have states and views and out of a
world in which our document or page model was based on ideas carried over from print. This for me is
a good thing. The Web is a rich medium, not a sequence of linked designs.
When we click a link, an indicator alerts us to a loading process (whether an animated icon,
progress bar, etc.).
For large files, the progress bar gives us an idea of how far we’ve reached in the loading
process.
If we get tired of waiting, we can hit the “Stop” button or try again by reloading the page.
If a page cannot be found, we are shown an error page.
If a page takes too long to load, we are shown an error page.
Other errors we encounter (for example, a page that needs authentication, or a document that
has been moved) are also displayed on a special page.
We can right-click a link to open it in a new tab or window, instead of replacing the current
document.
We can bookmark a page and come back to it at any time in the future.
When we need to undo something that’s gone wrong, a “Back” button takes us back one step
in our journey.
All of this needs to be accounted for in a full-fledged AJAX application, because AJAX should improve
the end user’s experience rather than make it harder. Let’s now enhance our AJAX script until we can
say that we’ve covered the basics.
Bookmarking and the Back Button
One thing I won’t go into in detail is the “Back” button and bookmarking functionality. To make this
work, you need to update the URI of the current page with a fragment and reload a hidden frame in
the page. There are all kinds of annoying differences between browsers, too, and you can use
something like the history plug-in for jQuery to get this to work.
A simple way to provide the user with feedback is to show a loading message. To do this in jQuery, we
need to get away from the load() method and instead use ajax(), which gives us information
about what happens to the request, such as:
The beforeSend event that is fired before the AJAX request is initiated, and
The success event that is fired when the AJAX request is successful.
Putting them together, we can add a loading message to the target element when the AJAX request
starts, which is replaced when the data has successfully loaded:
01 $(document).ready(function(){
02 var container = $('#target');
03 $('.ajaxtrigger').click(function(){
04 doAjax($(this).attr('href'));
05 return false;
06 });
07 function doAjax(url){
08 $.ajax({
09 url: url,
10 success: function(data){
11 container.html(data);
12 },
13 beforeSend: function(data){
14 container.html('<p>Loading...</p>');
15 }
16 });
17 }
18 });
Error Handling
As you may have guessed, the next logical step is to handle error cases. This is something far too
many AJAX solutions haven’t gotten right, and seeing a great application become useless just because
one call has timed out is very frustrating.
The user tries to load an external file that is not available because of AJAX security settings;
There is some server error (for example, “Page not found”);
The resource takes too long to load.
The following script takes care of all this, and you can see it in action on the error handling demo
page.
01 $(document).ready(function(){
02 var container = $('#target');
03 $('.ajaxtrigger').click(function(){
04 doAjax($(this).attr('href'));
05 return false;
06 });
07 function doAjax(url){
08 if(url.match('^http')){
09 var errormsg = 'AJAX cannot load external content';
10 container.html(errormsg);
11 } else {
12 $.ajax({
13 url: url,
14 timeout:5000,
15 success: function(data){
16 container.html(data);
17 },
18 error: function(req,error){
19 if(error === 'error'){error = req.statusText;}
20 var errormsg = 'There was a communication error: '+error;
21 container.html(errormsg);
22 },
23 beforeSend: function(data){
24 container.html('<p>Loading...</p>');
25 }
26 });
27 }
28 }
29 });
We test whether the link URI starts with http and then report an error that loading it with
AJAX is not possible.
If the link doesn’t begin with http, we start a new AJAX request. This one has a few new
features:
We define a timeout of 5 seconds (i.e. 5000 milliseconds);
We add an error handler.
The error handler either sends us what happened on the server asreq.statustext or
gives us the error message timeout when the 5 seconds are up. So, we need to check what
we got before we write out the error message.
Highlighting Changes
We’re almost done enhancing the usability of our AJAX solution. One last touch is to make it very
obvious that something on the page has changed. The standard way of doing this is called the yellow
fade and was introduced in 2004 by 37signals in its Basecamp application.
With this technique, you change the background colour of the element to yellow and then fade it
smoothly back to white. This grabs the user’s attention without overloading them (unlike zooming in
on the content in or popping it up, PowerPoint style, which would overwhelm), and it is pretty easy to
implement.
jQuery has a plug-in in the effects package called Highlight that does exactly that. Using it, we
can highlight the AJAX returns, making it very obvious that something has changed:
01 $(document).ready(function(){
02 var container = $('#target');
03 $('.ajaxtrigger').click(function(){
04 doAjax($(this).attr('href'));
05 return false;
06 });
07 function doAjax(url){
08 if(url.match('^http')){
09 var errormsg = 'AJAX cannot load external content';
10 container.html(errormsg).
11 effect('highlight',{color:'#c00'},1000);
12 } else {
13 $.ajax({
14 url: url,
15 timeout:5000,
16 success: function(data){
17 container.html(data).
18 effect("highlight",{},1000);
19 },
20 error: function(req,error){
21 if(error === 'error'){error = req.statusText;}
22 var errormsg = 'There was a communication error: '+error;
23 container.html(errormsg).
24 effect('highlight',{color:'#c00'},1000);
25 },
26 beforeSend: function(data){
27 container.html('<p>Loading...</p>');
28 }
29 });
30 }
31 }
32 });
Notice the different colors for the error case and success case.
This is about all we need to do to make AJAX more usable. But to make it accessible to everyone out
there, we have to do a bit more.
Screen readers are tools that read out to visually impaired users what is on the screen (or in
the HTML and hidden by CSS).
Screen readers work on top of the normal browser and enhance its functionality. Specifically,
they allow for quicker keyboard navigation (for example, jumping from headline to headline
with a shortcut).
They take a copy of a document after it has loaded and apply changes to it.
This means that screen readers understand JavaScript, but they only execute a request when
the page has loaded. If you change a document with JavaScript and AJAX after it has loaded,
you need to notify the screen reader somehow that something has changed and refresh the
copy of the page. This can be done by refreshing a form field as a hack.
The real problem with screen readers, and any assistive technology, is that they add yet another level
of complexity to our Web interaction.
We have HTML interfaces such as links and forms that need to work with all kinds of input devices:
keyboard, mouse, voice recognition software, to name a few. Then the browser needs to somehow tell
the assistive software (whether a screen reader or software that zooms the screen or a voice
recognition tool) that something has changed, and that other tool has to translate it into an
understandable format. All of this can, and frequently does, fail.
Much like how HTML 5 is being pushed to replace HTML 4 because the latter is not rich enough to
support the interfaces we want to build, WAI-ARIA is a standard that works around the problem of
assistive technology and browsers not talking to each other.
With WAI-ARIA, you can tell a screen reader, for example, that a particular element on the page
changes frequently and will be refreshed with AJAX. Again, this topic is too big to cover here,
but some good articles are out there in case you are interested.
This is where you as a designer can do a lot to make your AJAX interface more accessible. Patrick
Lauke haswritten a wonderful article on keyboard-access styling to get you on your way.
Important Feature #2: Notify at the Source
The second, very important, part is to notify users in the element that they activated that something
is happening. You’ll often see interfaces where the activation button or link is in one spot but the
content gets loaded somewhere else on the screen. One example of this is the contact form on Get
Satisfaction:
Large view
When we can see the screen in full, everything is pretty obvious. But consider an end user who has to
magnify the screen to 1600% to be able to read it. Or someone who gets easily confused and uses a
tool to focus on the part of the screen they are interacting with and blur out the rest. Their experience
is different:
By clicking this, the user expects to be able to submit feedback. Instead, all they get is a darker
screen, which could be a hardware problem (running out of battery?) or something else entirely. They
have no information on which to base their next move.
You don’t even have to go as far as considering people with disabilities: just use a netbook whose
viewport is a mere 300-pixels high (like my first-generation Eee PC) or a mobile interface that zooms
into a certain part of the page (like my Blackberry with Opera Mini).
In any of these cases, your AJAX solution will be neither usable nor accessible if the section that is
replaced is far removed from the button that fires the AJAX request.
You have two workarounds. The most obvious one is to keep the elements close together. If that is
not possible, the other workaround is to change the content of the element that fires the AJAX request
once the user clicks on it. This indicates to the end user what is going on.
As an added assistance, you can shift the keyboard focus to the target element when the AJAX
request has been processed. Be aware, though, that this could confuse some users; being jumped
around the screen without meaning to can be scary. Pretty, smooth-transitioning solutions look good
to the rest of us, but they can be a total nightmare for users with learning disabilities.
Putting all of this together, take a look at this more accessible example. It adds a span to the link to
show the state of the AJAX request, it highlights the content when it has finished loading, and then it
shifts the focus to the new element. Here is the final code. Check the comments (// example) to see
what is going on.
001 $(document).ready(function(){
002
003 // this is the container we'll load content into
004 var container = $('#target');
005
006 // adding a tabIndex of -1 makes it keyboard accessible,
007 // and we can set the focus to it
008 container.attr('tabIndex','-1');
009
010 // if a user clicks on an element with the class ajaxtrigger...
011 $('.ajaxtrigger').click(function(){
012
013 // define trigger as the link
014 var trigger = $(this);
015
016 // read its href attribute (which is the URI we'll load with AJAX)
017 var url = trigger.attr('href');
018
019 // if the element does not have a class called "loaded"
020 if(!trigger.hasClass('loaded')){
021
022 // add a new span to the element.
023 trigger.append('<span></span>');
024
025 // add a class called 'loaded' to the element
026 trigger.addClass('loaded');
027
028 // and define msg as the last span in the element
029 var msg = trigger.find('span::last');
030
031 // otherwise, simply define msg as the last span in the element
032 } else {
033 var msg = trigger.find('span::last');
034 }
035 // ^ this condition means we only add the span once and not
036 // every time users click the element.
037
038 // call the doAjax function with the URI to load,
039 // the span inside the link to change and the
040 // target element to replace.
041 doAjax(url,msg,container);
042
043 // tell the browser to not follow the link
044 return false;
045 });
046
047 // here's where the AJAX magic happens
048 function doAjax(url,msg,container){
049
050 // if the URI starts with http...
051 if(url.match('^http')){
052 // show an error and set the class of the span to 'error'
053 msg.html(' (error!)').addClass('error');
054
055 // tell the end user in the target element what the error is
056 var errormsg = 'AJAX cannot load external content';
057
058 // update the container with the error
059 updateContainer(errormsg,'#c00');
060
061 // if the URI does not start with http
062 } else {
063
064 // start an AJAX request using the url
065 $.ajax({
066 url: url,
067
068 // give the request five seconds time, otherwise call it
069 // a timeout error
070 timeout:5000,
071
072 // if all went well
073 success: function(data){
074
075 // set the span content to ready
076 msg.html(' (ready.)');
077
078 // update the container with the right data
079 updateContainer(data,'#ff9');
080 },
081
082 // if there was an error
083 error: function(req,error){
084
085 // say in the link that there was an error and set the
086 // class of the span to 'error'
087 msg.html(' (error!)').addClass('error');
088
089 // if the error just says error, get the real status
090 // text from the browser (jQuery doesn't do this right)
091 if(error === 'error'){error = req.statusText;}
092
093 // tell the user that there is a communication error
094 var errormsg = 'There was a communication error: '+error;
095
096 // update the container with the error
097 updateContainer(errormsg,'#c00');
098 },
099
100 // if the link was clicked but the AJAX request was not fired...
101 beforeSend: function(data){
102
// remove any "error" classes and set the span message to
103
loading
104 msg.removeClass('error').html(' (loading...)');
105 }
106 });
107 }
108 };
109
110 // update the container
111 function updateContainer(content,colour){
112 container.
113 // set the content
114 html(content).
115 // shift the browser focus
116 focus().
117 // flash the container for a second in the
118 // specified colour
119 effect('highlight',{color:colour},1000);
120 }
121
122 });
The code is a bit longer than in that our other examples, but the payoff makes it very much
worthwhile.
Important Feature #3: De-Clutter Your Interface
With a library such as jQuery, you can make anything on the page interactive and make it initiate
AJAX requests. You could use roll-overs or drag-and-drop interfaces, and these are great to look at,
but ask yourself: are they really intuitive? Browsers do not yet have any drag-and-drop functionality
or even roll-overs. Roll over your menu bar; you have to click to initiate any action.
But by using JavaScript tricks, you can make any element keyboard accessible. And if you build
widgets, go even further by following the rules of keyboard navigation. You could even create a screen
reader-compatible drag-and-drop interface. But again, ask yourself a few questions:
Making the interface as simple as possible does not mean neutering your creativity. On the contrary,
the easiest and simplest interfaces are the ones that have gone through a lot of research and design
iterations. Great usability means not recognizing that something has been done to make the interface
easy.
Nothing in your JavaScript or HTML is secure. I can change it on the fly and work around your
protection mechanisms.
If you are not building a Web application but are merely offering articles for people to read or a
catalogue to flip through, you probably shouldn’t go the AJAX route anyway.
The other thing to consider is search engines. If you load all of your content with AJAX, you aren’t
offering much in your documents for search engines to index. Static HTML content is still best for
search engine indexing—as well as performance, because pages can be packed and cached nicely on
your server, if you do it right. Loading via AJAX brings up the content much faster for users and saves
on bandwidth, but you will see less traffic from search engines. Something to consider.
You may sometimes need, though, to retrieve third-party content; i.e. load external content in your
document as data (because you can always use iFrames to embed other documents). This is where we
have to get clever with the technologies at our disposal.
The most common workaround for AJAX not being able to load something
like http://icant.co.uk/index.php is to write a server-side script that loads the page and
then prints it out. This is called a proxy, and you can see an example of the solution here.
Of utmost importance when using a proxy is to whitelist the URIs that you want to load. Do not
simply load any URI off the Web, or else attackers would be able to read files from your server and
use your server to send out spam and attack other servers, making it look as though you were the
perpetrator.
Other ways to retrieve external content is by getting data in a special format called JSON-P or
by using a hosted proxy service such as YQL. I’ll keep this brief because there are several solutions to
this problem. If you are interested in learning more, check out this blog post on the subject.
I hope you’ve gained a few more insights into what AJAX is and how you can use it to improve the
user experience in a way that is safe and doesn’t leave certain segments of users out in the cold. AJAX
makes stuff smoother, but nothing is more annoying than a supposed enhancement spoiling the whole
experience.
(al)