There are two approaches we could take to loading dynamic data into our application. We
could load in the raw data (such as a list of JSON objects representing recently spotted
celebrities) and merge it into the application’s HTML—creating list elements and appending
them to the page. Or, we could load the entire HTML contents straight from the server and
dump it directly into our page. The latter approach sounds more straightforward—so let’s try
that first to become familiar with Ajax in the mobile environment. After that, we’ll take a
look at handling other data formats.1. Fetching HTML
The first thing we’ll need if we want to retrieve
data from a server is … a server! If you try and grab data from a file://
protocol URL (which will be the case if you’re testing pages by double-clicking an
index.html file), you’ll hit the dreaded “Access-Control-Allow-Origin” error.
Assuming we have a server—be it running on our machine locally, on a VM, or on
the Internet—we now need somewhere to dump the HTML chunks when they’re returned from the
server. For this we only require a skeleton of the page we’ve been working with, to act as
a container that we fill. Because we’ll be returning the same markup as before, our
original CSS will apply to the contents without needing to be modified. Here’s that
skeleton:listing 1. ch4/13-ajax.html (excerpt)
<div id="pages">
<div id="page-spots" class="page-spots"></div>
<div id="page-spot" class="page-spots"></div>
<div id="page-sightings" class="page-sightings"></div>
<div id="page-stars" class="page-stars"></div>
<div id="page-star" class="page-stars"></div>
</div>
|
All the content for each of those sections is back in the spots.html, new.html, and stars.html files—just like a regular website, except that now we’ll be using
Ajax to load that content into this empty skeleton.
With this basic HTML in
place, the next step is to stop the link from being followed when clicked, as we did with
our transition function earlier—with preventDefault(). Then we
can execute our Ajax. The jQuery load() function is perfect for our needs: it
loads HTML from a URL, and provides a mechanism for choosing which part of the document to
return. This is great, because we have no need for the whole page, with the <head> and <meta> tags—we only
want the contents of the <body>. Using
load() means we don’t need a special version of our HTML page
for Ajax, and any updates will only have to be made in one place.
To
accomplish this, we use the load() function with a string
parameter consisting of the URL we want, followed by a space, followed by a jQuery
selector string. The contents of the element matched by that selector will be inserted
into the element from which load() was called. The content we
want to insert is contained in the .wrapper
<div> of the target page. So, when the Spots link is
clicked, we want to call load() on our #spots
container and pass in the string "spots.html .wrapper":
listing 2. javascripts/ch4/13-ajax.js (excerpt)
$("#tab-spots a").click(function(e){
e.preventDefault();
$("#page-spots").load("spots.html .wrapper");
});
|
Note:
If you’re unfamiliar with jQuery, you might be wondering how it loads in a small
section of HTML via Ajax. There’s no magic here; it actually loads the entire page and
dumps it into a <div> element that exists outside the
DOM. The filtering is done on this element, and the results are inserted in the correct
position. Very handy, although of course it means transmitting more data over the
network than you actually end up using. For most real-world applications, you’ll
probably want to pull data in XML or JSON format and insert it into your HTML on the
client side. We’ll be looking at how this can be done shortly. For now, though, we’ll
stick with using load() to keep it simple, and focus on
demonstrating how the various Ajax methods work, as well as how they’re best used in the
context of a mobile app.
This will load the relevant HTML into the right container, but there are a few
more tasks that need attention—most notably, making the new content visible! Fortunately,
load() allows you to specify a callback function that will be
executed once the Ajax call has completed. Inside this callback, we’ll transition the page
into view:
listing 3. javascripts/ch4/13-ajax.js (excerpt)
$("#tab-spots a").click(function(e){
e.preventDefault();
$("#page-spots").load("spots.html .wrapper", function() {
transition('#page-spots', "fade", false);
});
});
|
2. Ajaxifying Links
Adding events to each navigation item is a tedious way to wire up our site. We made an
Ajax loader for the Spots page just now, but we’d have to duplicate this code multiple
times for it to work for all the links. Instead, we can take advantage of our site’s
consistent structure and concoct a system to do it programmatically, based on the contents
of the navigation elements. Doing this gives us a level of progressive enhancement: our
links are designed to work as normal, but our system intercepts them when clicked and
loads the content via Ajax. This method is sometimes known as Hijax.
We’ll then generalize the Ajax code we wrote so that it can be applied to any link we
pass it. There are two pieces of data needed for that: the URL to load, and the name of
the container to dump it in. This is where having conventions is important. As long as our
pages and classes are named in a consistent manner, we can easily create code that targets
all our links:
listing 4. javascripts/ch4/14-hijax.js (excerpt)
function loadPage(url, pageName) {
$("#" + pageName).load(url + " .wrapper", function(){
console.log(this);
transition("#" + pageName, "fade", false);
});
};
|
This function is almost identical to the code from before, except we’ve replaced the
page names and URLs with variables. Now we can load a page programmatically, for
example:
loadPage("spots.html", "spots");
If fact, we need to load a page by default when the application loads, so we can place
that line of code inside the document.ready handler. This will load up
the Spots page via Ajax as our home page.
Warning:
Data Caching
Because the load() method pulls in an entire HTML file, the
results can be cached by the browser, which means that changes you make in the page
being loaded may not be reflected right away. You can disable the cache globally (for
all Ajax requests) with $.ajaxSetup({ cache: false });, or, if it’s
just for one particular call, you can append a timestamp to the URL so that each request
will be seen by the browser as different. So, instead of loading url + "
#wrapper", you can load url + "?" + new Date().getTime() + "
#wrapper".
Finally, we want to call our function whenever any navigation items are clicked.
Remember, we have to extract two pieces of data to pass to our function: the URL and the
page name. The URL is simple—it’s the <href> value of
the link itself. For the page name, there are many approaches we could take: we could give
our containers the same names as the files (minus the .html), or we
could add some extra data into the link itself. The latter has become easier with the
addition of custom data attributes in HTML5, which let us annotate elements with key/value
pairs:
listing 5. ch4/14-hijax.html (excerpt)
<ul id="tab-bar">
<li>
<a data-load="spots" href="spots.html">Spots</a>
</li>
<li>
<a data-load="sightings" href="new.html">Add a sighting</a>
</li>
<li>
<a data-load="stars" href="stars.html">Stars</a>
</li>
</ul>
|
A data attribute starts with data- and is followed by your key
name. You can then provide it with whatever value you like. According to the spec, these
values should be retrieved using the element’s dataset property;
however, this is yet to be widely supported, so your best bet is to use the standard
getAttribute() function (as in, myElement.getAttribute("data-load")), or, if you’re using jQuery, the
attr() method:
listing 6. javascripts/ch4/14-hijax.js (excerpt)
$("#tab-bar a").click(function(e){
e.preventDefault();
var url = e.target.href;
var pageName = $(this).attr("data-load");
loadPage(url, pageName);
});
|
Et voilà! Any link inside the #tab-bar element will fire our loadPage() function,
and we’ll transition from the current page to the new page. You can easily extend this
system to also specify the transition type, if you like.
One current problem is that if the page takes a long time to load, the user has no
idea what’s going on (and they’ll probably become click-happy and try to load another
page).