Monday, April 18, 2011

Intro to Building Dynamic Apps With jQuery Mobile

The first task in building the mobile focused web application was to select a javascript framework. There are a good dozen frameworks or so on the market and many write-ups about them so I will just list a few links.
To make a long story short, I have started working with jQuery Mobile because it is open source, looks quite slick with very little additional styling work, has a large number of supported platforms, is built with graceful degradation in mind, and appears to have a lot of momentum behind it. It has also been documented to work well with tools that wrap web content into native apps like PhoneGap. In addition, I find jQuery to make JavaScript development tolerable, if not fun from time to time.
JQuery Mobile is currently in Alpha 4.1 of its initial release. Given this, the documentation to get you up and running with a dynamic app is somewhat fragmented or missing. I hope my next few posts will help alleviate this problem.

The first issue is how to populate a page with dynamic content. My example will show server code specific to Spring but any server side technology will do.
First we will setup two pages; foo and bar.

<body>
  <div id="foo" data-role="page">
    <div data-role="header">
      <h1>Foo</h1>
    </div>
    <div data-role="content">    
      <h2>Foo</h2>
      <a href="#bar">To bar</a>
    </div>
  </div>
  <div id="bar" data-role="page">
    <div data-role="header">
      <h1>Bar</h1>
    </div>
    <div data-role="content">    
      <h2>Bar</h2>
      <a href="#foo">To foo</a>
    </div>
  </div>
</body>

These pages have links to each other that will be hijacked by jQuery Mobile. If the page was not currently available in the DOM, jQuery would fetch it via an ajax call. In this case both pages are contained within a single html page so jQuery will simple create the transition between the two. Now let’s say that we wanted to provide some dynamic content on page bar. Here we have added a span to hold the content.

<div data-role="content"> 
  <h2>Bar</h2>
  <a href="#foo">To foo</a>
  <span id="dynamicContentHolder"></span>
</div>

There are several options for how to pull content down from the server (which I will go into in a later post), but for now we will simple treat it as a string. Next I created a Spring controller method to provide the content. If you want to know more about serving ajax content with spring read ajax-simplifications-in-spring-3-0. Additionally if you will have both traditional pages and ajax powered pages displaying the same content check out Spring’s content negotiation capabilities in rest-with-spring-contentnegotiatingview. With content negotiation you can reuse the same controller methods to serve multiple content types. The method below simply replies to a request to /dynamicData with the string “data string”.

@RequestMapping(value="/dynamicData", method=RequestMethod.GET)
public @ResponseBody String dynamicData(Model model){
  return "data string";
}

During the page transition lifecycle, jQuery Mobile provides 4 events; pagebeforeshow, pagebeforehide, pageshow, & pagehide.  As we do not want the partially assembled page to be shown to the user, we will bind our logic to the pagebeforeshow event. The first thing we do is call pageLoading to block the ui and notify the user that their content is loading. jQuery Mobile’s display of this box does not include the time spent executing our code in the 'pagebeforeshow' function. Next we conduct the jQuery ajax call. See http://api.jquery.com/jQuery.ajax/ for more information on the api. As we do not want jQuery Mobile to render and transition to the next page until our dynamic content is in place, we set async to false to stop further execution until the content is loaded. In general you should be careful with synchronous calls as it blocks all other actions from occurring in the browser. Once the data is received we can easily populate the dynamicContentHolder with jQuery. Additionally we can examine the http status code of the response to handle errors retrieving data. In the case of an error changePage is used to divert the page flow to the error page (There might be a better way of doing this and if so please let me know). The only non-default option added to changePage is to not include the transition in the history. This way the jQuery Mobile back button will take the user back to the referring page (foo).

$('#bar').live('pagebeforeshow',function(event, ui){
   $.mobile.pageLoading();    
   var dynamicDataResp = $.ajax({
    url: "/dynamicData.json",
    async: false,
    cache: false
  });
  if(dynamicDataResp.status == 200){
    $('#dynamicContentHolder').text(dynamicDataResp.responseText);
  }
  else{
    $.mobile.changePage('#error', 'none', false, false);
  }
});

<div id="error" data-role="page">
  <div data-role="header">
    <h1>Error</h1>
  </div>
  <div data-role="content">    
    Click back to return to the previous page.
  </div>
</div>

The last thing to note is that page events that are bound to the first page must be put in place before jQuery Mobile loads. The recommended approach is to place code inside a function bound to mobileint.

Putting everything together here is our final front end code.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, minimum-scale=1, maximum-scale=1">
    <link rel="stylesheet" href="http://code.jquery.com/mobile/1.0a4.1/jquery.mobile-1.0a4.1.min.css" />
    <script type="text/javascript" src="http://code.jquery.com/jquery-1.5.2.min.js"></script>
    <script type="text/javascript">
      $('#bar').live('pagebeforeshow',function(event, ui){
        $.mobile.pageLoading();    
        var dynamicDataResp = $.ajax({
          url: "/dynamicData.json",
          async: false,
          cache: false
        });
        if(dynamicDataResp.status == 200){
          $('#dynamicContentHolder').text(dynamicDataResp.responseText);
        }
        else{
          $.mobile.changePage('#error', 'none', false, false);
        }
      });
    </script>
    <script type="text/javascript" src="http://code.jquery.com/mobile/1.0a4.1/jquery.mobile-1.0a4.1.min.js"></script> 
  </head>
  <body>
    <div id="foo" data-role="page">
      <div data-role="header">
        <h1>Foo</h1>
      </div>
      <div data-role="content">    
        <h2>Foo</h2>
        <a href="#bar">To bar</a>
      </div>
    </div>
    <div id="bar" data-role="page">
      <div data-role="header">
        <h1>Bar</h1>
      </div>
      <div data-role="content">    
        <h2>Bar</h2>
        <a href="#foo">To foo</a>
        <span id="dynamicContentHolder"></span>
      </div>
    </div>
    <div id="error" data-role="page">
      <div data-role="header">
        <h1>Error</h1>
      </div>
      <div data-role="content">    
        Click back to return to the previous page.
      </div>
    </div>
  </body>
</html>

In my next few posts we will look at supporting rich dynamic content and integrating with spring security. If you know of a cleaner way of building this example out please let me know.

No comments: