Create Ajax applications for the mobile Web

Build cross-browser smartphone Web applications

Developing for mobile devices has been a high cost, low return proposition for many years, despite the hype around it. The latest generation of smartphones powered by the iPhone OS and Google's Android provide a much simplified solution: just build Web applications. This gives you a one build for all devices approach, which can lower the cost. Even better, these high-end devices all offer ultra-modern browsers supporting advanced HTML, JavaScript, and CSS. In this article, learn how to build Asynchronous JavaScript and XML (Ajax)-heavy applications that take full advantage of the capabilities of modern smartphones. You will learn not only how to get the most out of these devices, but also how to deal with the subtle differences between them.

Michael Galpin, Software architect, eBay

Michael_GalpinMichael Galpin is an architect at eBay and is a frequent contributor to developerWorks. He has spoken at various technical conferences, including JavaOne, EclipseCon and AjaxWorld. To get a preview of what he is working on, follow @michaelg on Twitter.



02 March 2010

Also available in Chinese Japanese Portuguese

Getting started

This article explains how to develop a mobile Web application targeted at the Apple iPhone as well as Android-based smartphones. To develop mobile Web applications against such devices, you can't just use your normal desktop browsers, at least not completely. You will need either emulators or actual devices. For the iPhone, you need the iPhone emulator. This is part of the iPhone SDK. For this article, iPhone SDK 3.1 was used. Similarly, you will need the Android SDK. This includes the Android Virtual Device manager, which can be used to create Android emulators running various versions of Android. For this article, Android SDK 2.1 was used. Most of the code in this article is JavaScript, with some HTML and CSS thrown in. There is a server-side aspect to this application that was written in Java™ code. This is arbitrary, and just like with any other Web application, you can use whatever server-side technology you want. To run the application in this article, you will need Java 1.6. You will also need Jersey, the reference implementation of JAX-RS, and all of the associated Java Archive (JAR) files (see Resources for links).


Mobile browsers and WebKit

Mobile devices have had Web browsers for years. However, developing for them has always been painful because Web developers have had to deal with cross-browser issues. A lot of long days and late nights can be attributed to getting HTML, JavaScript, and CSS to work identically on various versions of Internet Explorer, Mozilla Firefox, Safari, and so on. Well, the desktop browser world is completely tame compared to the mobile browser world. The number of different mobile browsers has, historically, been staggering. Every device maker had their own browser, and even devices from the same manufacturer had huge variations in operating systems, screen sizes, and so on. Some browsers only supported WAP, others supported subsets of HTML, some had full HTML, but no JavaScript.

Fortunately, things have really changed. As of January 2010, more than 80% of U.S. mobile Internet traffic is done either on an iPhone or an Android device. Not only do both of these operating systems use WebKit for HTML/CSS rendering, they both have JavaScript engines that have aggressively adopted the HTML 5 standards. You read that right. The browsers with the dominant position in the mobile space are also pushing open standards. You really could not ask for a better situation as a Web developer.

There are still variations between browsers, even between different versions of the iPhone and Android browsers. This is especially true for Android. On versions of Android prior to 2.0, the Android browser used the proprietary Google Gears technology. Gears can be credited with pushing many innovations that have now become part of the HTML 5 standard. However, that meant that for a while, some HTML 5 standards were not supported in the Android browser, but you could use Gears to get the same functions. All of the code in this article is based on the HTML 5 standards and will work on Android 2.0+ or iPhone 3.0+. Now that the stage is set for exploring these modern WebKit-based browsers, take a look at Ajax on these devices.

Ajax on mobile browsers

Just like on desktop Web applications, the key to creating a compelling user experience on mobile Web applications usually involves Ajax. Of course, user experience is not the only reason to use Ajax; it can also be faster and more efficient. These are even more significant reasons to use Ajax on a mobile Web application, where the network latency is so much greater, and the browser itself is much more constrained in terms of processor speed, memory, and cache sizes. Fortunately, Ajax turns out to be one of many things made easier by only having to worry about standards-based browsers. Before I get into that, let's take a quick look at the back-end server that the application you will create in this article will be calling.

Before you get started, you will need to download the necessary JAR files for Jersey, Xerces, Rome, and the Google App Engine SDK (see Resources for links) and install them in the following folder: WebKitBlog>war>WEB-INF>lib. You can download the rest of the source code for the application.

The WebKit blog

The mobile Web application in this article is a simple application for reading news on mobile Web development. For now, it will simply pull in an RSS feed from the official WebKit blog, but it could be easily modified to aggregate multiple RSS feeds. The application is a trivial Java Web application that you could deploy to any Java application server. All of the code is shown in Listing 1.

Listing 1. The Feed class
@Path("/feed")
public class Feed {
    String surfinSafari = "http://webkit.org/blog/feed/";
    
    @GET @Produces("application/json")
    public News getNews(@DefaultValue("0") @QueryParam("after") long after) 
    throws Exception{
      URL feedUrl = new URL(surfinSafari);
      SyndFeedInput input = new SyndFeedInput();
        SyndFeed feed = input.build(new XmlReader(feedUrl));
        List<Item> entries = new ArrayList<Item>(feed.getEntries().size());
        for (Object obj : feed.getEntries()){
            SyndEntry entry = (SyndEntry) obj;
            if (entry.getPublishedDate().getTime() > after){
                Item item = new Item(entry.getTitle(), entry.getLink(), 
                            entry.getDescription().getValue(), 
                            entry.getPublishedDate().getTime());
                entries.add(item);
            }
        }
        return new News(feed.getTitle(), entries);
    }
}

This code uses Java's JAX-RS to create a RESTful service. The @Path annotation denotes the endpoint of the service, that is, the relative URL to this service will be /feed. The @GET denotes that this service supports HTTP GET. The @Produces annotation declares that this service will produce data in a JSON format. This is an easy way to get your data serialized in JSON. The method getNews takes a single parameter called after, that is, get the entries after a certain date. This too uses the JAX-RS annotations to bind the after parameter to the query string parameter called after. If no value is given, then it gets a default value of 0.

So far, all I've talked about in Listing 1 are the JAX-RS annotations that set up the routing and data serialization for the service. The actual body of the method relies heavily on the Rome package for processing RSS. It simply downloads the latest RSS feed, and then transforms it to just the data that you need for the application, modeled by the Item and News classes. The only tricky part is that the published date of the article is turned into a long and used as an ID. This makes it an especially useful ID, because you can use it to sort by, as you will see later. The News class is shown in Listing 2.

Listing 2. The News class
@XmlRootElement
public class News {
    private String title;
    private List<Item> entries;
    // constructors, getters/setters omitted for brevity
}

Notice that the News class uses the JAXB annotation @XmlRootElement. There is no XML in this application, but JAX-RS leverages JAXB to do the automatic serialization/deserialization. All this class has is a title property and a list of Items. The Item class is shown in Listing 3.

Listing 3. Item class
@XmlType
public class Item {
    private String title;
    private String link;
    private String description;
    private Long id;
    // constructors, getters/setters omitted for brevity
}

This class shows you exactly what you will show in the Web application. Like the News class, it uses a JAXB annotation so that JAX-RS can serialize this into JSON for you. There is one last piece to the server-side code and that is configuring your Web application so that requests get routed to JAX-RS. For that you need to edit the web.xml file for the application, as shown in Listing 4.

Listing 4. The web.xml configuration file
<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee"
 xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    version="2.5">
    <servlet>
        <servlet-name>WebKit Blog Servlet</servlet-name>
        <servlet-class>com.sun.jersey.spi.container.servlet.
ServletContainer</servlet-class>
        <init-param>
            <param-name>com.sun.jersey.config.property.packages</param-name>
            <param-value>org.developerworks.mobile</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>WebKit Blog Servlet</servlet-name>
        <url-pattern>/resources/*</url-pattern>
    </servlet-mapping>
    <session-config>
        <session-timeout>30</session-timeout>
    </session-config>
    <!-- Default page to serve -->
    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
    <mime-mapping>
        <extension>mf</extension>
        <mime-type>text/cache-manifest</mime-type>
    </mime-mapping>
</web-app>

This is mostly boilerplate web.xml code. As you can see from the servlet declaration, the application is using Jersey, the reference implementation of JAX-RS. The initialization parameter to the servlet tells Jersey to scan the org.developerworks.mobile package for classes that have been annotated to handle service requests. Also, any requests that map to /resources/ will be mapped to the Jersey servlet. The last thing to note here is the mime-mapping section at the end of the file. This is the MIME type for manifest files, a key to offline Web applications that I will discuss later. Now that you've seen the back-end service that the Web application will use, it is time to look at the front end that uses it.


User interface powered by Ajax

You might have noticed in Listing 4 that the application has a standard index.html file. This is the entry point to the application, and it is shown in Listing 5.

Listing 5. The index.html file
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">

<html manifest="application.mf">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=UTF-8">
    <title>WebKit News</title>
    <meta name = "viewport" content = "width = device-width"/>
    <script type="text/javascript" src="index.js"></script>
  </head>
  <body onload="loadEntries()">
    <h1 id="title">Recent News About WebKit</h1><img 
id="loader" src="loading.gif"/>
  </body>
</html>

This is a pretty simple Web page, but there are a couple of very notable things. First, notice that in the head of the document, I set the viewport. This instructs the browser to zoom in on the content, so it looks good on a device. In terms of UI code, there is just a title and a loading image, that's it. Everything else is done by JavaScript. The loadEntries function in the index.js file makes an Ajax request to load the data. This function is shown in Listing 6.

Listing 6. The loadEntries function
function loadEntries(){
    if (!window.JSON){
        var head = document.getElementsByTagName("head")[0];
        var jsScript = document.createElement("script");
        jsScript.setAttribute("src", "json2.js");
        jsScript.setAttribute("type","text/javascript");
        head.appendChild(jsScript);
    }    
      var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(){
        if (this.readyState == 4 &&this.status == 200){
                  var theFeed = JSON.parse(this.responseText);
                  var i = 0;
                  if (theFeed.entries){
                         var len = theFeed.entries.length;
                         for (i=0;i<len;i++){
                                addEntry(theFeed.entries[len - 1 -i], true);
                         }
                  }
                  var body = document.getElementsByTagName("body")[0];
                  body.removeChild($("loader"));
              }
    };
    var urlStr = "/resources/feed";
    xhr.open("GET", urlStr);
    xhr.send();        
}

At the beginning of this function, I do a little bit of feature detection. Most of the browsers support a built-in function for parsing and serializing JSON. This is the same JSON library available from json.org. However, if it is not available, then all you need to do is include that file in your page, and you will have the same capabilities.

Otherwise, this is pretty simple Ajax code. You do not have to worry about Internet Explorer, so you can just create an XMLHttpRequest directly. You set its onreadystatechange property to a function, creating a closure in this case. When the response comes back from the server (readyState = 4) and there were no problems with the processing (status = 200 OK), then you parse the JSON response using the JSON.parse function. For each entry from the feed, you call the addEntry function. This is a function for creating the UI elements for each entry. It is shown in Listing 7.

Listing 7. The addEntry function
function addEntry(e, prepend){
    var eDiv = document.createElement("div");
    eDiv.setAttribute("class", "item");
    var link = document.createElement("a");
    link.setAttribute("href", e["link"]);
    var title = document.createTextNode(e["title"]);
    link.appendChild(title);
    eDiv.appendChild(link);
    var button = document.createElement("input");
    button.setAttribute("type", "button");
    button.setAttribute("value", "Show");
    button.setAttribute("id", "button" + e["id"]);
    button.setAttribute("onclick", "showDescription('" + e["id"] + "')");
    eDiv.appendChild(button);
    var dDiv = document.createElement("div");
    dDiv.setAttribute("id", e["id"]);
    dDiv.setAttribute("class", "description");
    dDiv.setAttribute("style", "display:none");
    dDiv.innerHTML = e["description"];
    eDiv.appendChild(dDiv);
    var body = document.getElementsByTagName("body")[0];
    if (!prepend && body.childNodes.length > 1){
            body.appendChild(eDiv);
    } else {
            body.insertBefore(eDiv, body.childNodes.item(2));
    }
}

This function is all pretty standard DOM manipulation. The only tricky aspect is that it can either append or prepend the entry, or add the entry either at the top or the bottom. Going back to Listing 6, the last thing the function does is remove the loading graphic. This concludes the basic Ajax functions, and depending on your use case, may be good enough. However, the application requires a lot of data to be downloaded from the server and then to be parsed. You can do better, and for a mobile Web application, you really must use local caching.


Caching with local storage

Local storage is part of the HTML 5 specification and is widely supported. It provides a simple name - value pair storage mechanism, where both the name and values are strings. So saving the entries to local storage is straightforward, and is shown in Listing 8.

Listing 8. Saving to local storage
function saveLocal(entry){
    if (!window.localStorage){
        return;
    }
    localStorage.setItem(entry["id"], JSON.stringify(entry));
}

Once again, you do some browser capability detection, by first checking if the window object has the localStorage property, per the HTML 5 specification. If it does, then you just use the id of the entry as the key to store. For the value, you must use a string, so you use the JSON.stringify function to serialize the object as a string. Now you just need a function for reading all of this data back from local storage. This is shown in Listing 9.

Listing 9. Loading from local storage
function loadLocal(){
    if (!window.localStorage){
        return [];
    }
    var i = 0;
    var e = {};
    var id = 0;
    var entries = [];
    for (i=0; i< localStorage.length; i++){
        id = localStorage.key(i);
        e = JSON.parse(localStorage.getItem(id));
        entries[entries.length] = e;
    }
    entries.sort(function(a,b) { 
        return b.id - a.id; 
    });
    return entries;
}

Again, this function starts by checking if local storage is available or not. Next, it iterates over all of the data in local storage. For each value in storage, you once again use the JSON.parse function to parse the string back into an object. Next, you sort the entries, because the order that they come out of from local storage is not guaranteed. This does a descending sort, putting the newest entries first. Finally, now that you have functions for storing and loading from local storage, you just need to integrate it back into the loadEntries function as shown in Listing 10.

Listing 10. Add caching to loadEntries
function loadEntries(){
    // check for JSON object
    var localEntries = loadLocal();
    var newest = 0;
    if (localEntries.length > 0){
        newest = localEntries[0].id;
    }
    var i = 0;
    for (i=0;i<localEntries.length;i++){
        addEntry(localEntries[i]);
    }
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(){
        if (this.readyState == 4 && this.status == 200){
                  var theFeed = JSON.parse(this.responseText);
                  var i = 0;
                  if (theFeed.entries){
                         var len = theFeed.entries.length;
                         for (i=0;i<len;i++){
                                addEntry(theFeed.entries[len - 1 -i], true);
                                saveLocal(theFeed.entries[len - 1 -i]);
                         }
                  }
                  var body = document.getElementsByTagName("body")[0];
                  body.removeChild($("loader"));
              }
    };
    var urlStr = "/resources/feed?after=" + newest;
    xhr.open("GET", urlStr);
    xhr.send();    
}

The major difference here is that you first load the local data. You determine the newest entry that you have, then when you call the server, use this parameter so that the server will only send entries that are not already available from local storage. Finally, on the callback, when you get the entries, you need to call saveLocal to save them to the local cache. With new entries stored locally, you are now in a good position to enable full offline access to your Web application.


Going offline

Enabling an application to work offline is often viewed as the holy grail of mobile Web application development. Although it can be tricky, many of the new features in HTML 5 have removed a lot of the hurdles. The first thing you need to do is identify what assets are needed offline, and just list them in the application's manifest, as shown in Listing 11.

Listing 11. Application manifest
CACHE MANIFEST
index.html
index.js
json2.js
loading.gif

For an application as simple as this one, the manifest is also quite simple. It is all of the static files: HTML, JavaScript, and images (also CSS, if you needed it). It is important that this file is served with a MIME type of text/cache-manifest—just as you set it in the web.xml file in Listing 4. Finally, if you look again at Listing 5, you will see that the root html element has an attribute of manifest that points to the manifest file in Listing 11. The last thing that you need to do is make one last modification to the loadEntries function as shown in Listing 12.

Listing 12. Check for offline
function loadEntries(){
    // load local
    // ...
    // check if online
    if (navigator.onLine){
        var xhr = new XMLHttpRequest();
        xhr.onreadystatechange = function(){
            if (this.readyState == 4 && this.status == 200){
                      var theFeed = JSON.parse(this.responseText);
                      var i = 0;
                      if (theFeed.entries){
                             var len = theFeed.entries.length;
                             for (i=0;i<len;i++){
                                    addEntry(theFeed.entries[len - 1 -i], true);
                                    saveLocal(theFeed.entries[len - 1 -i]);
                             }
                      }
                      var body = document.getElementsByTagName("body")[0];
                      body.removeChild($("loader"));
                  }
        };
        var urlStr = "/resources/feed?after=" + newest;
        xhr.open("GET", urlStr);
        xhr.send();    
    }
}

To see if you are online or offline, check the onLine property of the navigator object (window.navigator). If you are not online, then there is no point in calling the server to ask for new entries, just display all of the local ones. Now you have a complete mobile Web application that is offline-capable.


Summary

Mobile Web applications are currently a hot topic, for a good reason. They provide a way to reach users on many different kinds of devices. They take advantage of the highest level of standardization between browsers that Web developers have ever enjoyed. In this article, you have seen all of the essential techniques needed for creating a mobile Web application that relies on Ajax. And, by relying on Ajax, you are able to take advantage of cutting edge features in mobile browsers. Just look at the sample code in this article. You could have easily generated all of the HTML on the server when the page is requested. However, by putting all of the UI on the client and pulling data from the server using Ajax, you could easily cache data on the client and even enable the application to work offline.


Download

DescriptionNameSize
Article source codeWebKitBlog.zip32KB

Resources

Learn

Get products and technologies

Discuss

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Web development on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Web development, Open source, Industries, Mobile development
ArticleID=470431
ArticleTitle=Create Ajax applications for the mobile Web
publish-date=03022010