HTML and JavaScript development skills are a crucial part of any mobile application developer's skill set, given the explosion of the mobile web and the introduction of Windows 8 and WinRT. Successful JavaScript-based mobile web development requires a client-centric architecture instead of a server-centric approach, to enable development of apps that can work across many types of devices and to maximize code reusability. In this article, I'll discuss how to use the jQuery JavaScript library -- specifically the jQuery Mobile framework that's based on jQuery -- to build client-centric mobile web applications. If you are new to jQuery Mobile, see "Get Started Using jQuery Mobile for Mobile Web Development," which provides an introduction to using the framework.

Thinking Client-Centric Development

Typically most modern web applications generate their HTML on the server through server-side binding to business/data layers, with pages served up to the browsers requesting them. Such applications may be doing this during initial load or even during partial postbacks. This method requires a request from the client and a full request/response lifecycle on the server. During the client/server interaction, the entire page is processed on the server with each request. First, the server decouples the request, then the server runs the request through the application's business logic. Next, the server uses the data-access layer to pull together data to build the HTML markup, which is then sent back to the client. Figure 1 illustrates this traditional server-centric web application architecture.

Figure 1: Traditional server-centric web application architecture

In contrast to the server-centric architecture, in client-centric development the user interface (UI) and data-access application layers exist on the client, as shown in Figure 2. Depending on how much control you have over your environment, you can add more application logic on the client side in JavaScript without having to worry about exposing proprietary information.

Figure 2: Client-centric architecture

The benefits of client-centric development are many. First and foremost, a client-centric development model frees up large amounts of server resources and minimizes the server/client chattiness. This practice also helps you build applications that are more scalable and interactive and use the same techniques to target different form factors.

Building a Client-Centric Application

The example in this article demonstrates how to build a web application that brings UI and data-access layer logic onto the client. The sample app that we'll build uses the Netflix OData feed to create a simple Netflix client that lets you browse movie catalogs from the Netflix database. The Netflix OData feed has read-only access, is known in the OData community, and is the perfect fit for our example. No lines of server-side code are required for this example; the application uses the code running on the Netflix servers. Note that this article focuses on data-access and UI-generation techniques of web application views and does not delve into application security, authorization, or authentication.

After completing this walk-through, you should be able to take the approach I've described and use whatever server-side stack you choose to build client-centric applications. You can tie this development architecture with OData, Windows Communication Foundation (WCF), REST, and other services. The techniques discussed here are appropriate for building both mobile and desktop interfaces.

The best "getting started" approach is to develop for touch and mobile contexts first, then move to bigger form factors by enabling the UI for keyboard and mouse interaction. The mobile interface relies on the jQuery Mobile framework for the visual and application navigation schemes. jQuery Mobile provides the touch interaction needed on the mobile device. After following the steps discussed in this article, you should be able to apply the knowledge you've gained to build browser-based applications that can target different devices using jQuery data-access and view-generation techniques.

Application Overview

This web-based application serves as the interface to the public OData feed from Netflix. The application provides three different ways to search Netflix movies, as shown in Figure 3.

  • Search by name: Users can enter text keywords to search for the movie title.
  • Search by release year: The user can search for movies within a series of predefined year ranges.
  • Search by actor/actress name: Users can choose from a list of actors and actresses in the Netflix database.

Figure 3: Sample Netflix app home screen

Once the user initiates a search using any of the available methods, the application goes to the Netflix database passing in the required search parameter. The user is taken to a Movies List screen, shown in Figure 4, displaying all movie titles that match the search criteria.

Figure 4: Movies List screen

The user can then click any of the list items to display a detail screen, which presents a description of the movie, as shown in Figure 5.

Figure 5: Movie Details screen

Designing the Home Screen

The jQuery Mobile framework includes building blocks that enable quick mobile screen design and development, including a collection of UI widgets to display toolbars, lists, and buttons with theme support. As long as you choose the correct attributes, the framework takes care of making your application "touch aware" and navigable within the context of a mobile device.

The page can be designed as header, content, and footer. To set up the markup for the first page, create the header section to display the Netflix logo. To build the basic page structure, we use the following markup:

  1. <div data-theme="b">
  2. <h1><img src="netflix_logo.gif" alt="Netflix Logo" /></h1>
  3. <p>Welcome to Netflix Web Data Browser</p>
  4. </div>

Next, you need the three regions for the different types of search capability. Using the collapsible panel for the different search areas and the data-collapsible attribute for the containing div, you can show the search panels on the page, using the code in Figure 6, Figure 7, and Figure 8.

  1. <div data-role="collapsible" data-theme="a" data-content-theme="c">
  2.        <h3>Search By Name</h3>
  3.        <br />
  4.        Enter Search Text : <input type="text" id="txtMovieTitle" maxlength="100" />
  5.         <input type="button" id="btnTxtSearch" value="Search" />
  6. </div>
  1. <div data-role="collapsible" data-theme="a" data-content-theme="c">
  2.        <h3>Search By Release Year</h3>
  3.        <br />
  4.        Select a date range:
  5.         <select id="dateOptions">
  6.           <option value="1970">1970 - 1979</option>
  7.           <option value="1980">1980 - 1989</option>
  8.           <option value="1990">1990 - 1999</option>
  9.           <option value="2000">2000 - 2009</option>
  10.           <option value="2010">2010 - 2011</option>
  11.         </select>
  12.         <input type="button" id="btnYrSearch" value="Search" />
  13. </div>
  1. <div data-role="collapsible" data-theme="a" data-content-theme="c">
  2.        <h3>Search By Actor/Actress Name</h3>
  3.        Select Actor/Actress Name :
  4.         <select id="actorOptions">
  5.           <option id="defaultOption">Select a Name</option>
  6.         </select>
  7.         <script id="actorList" type="text/x-jquery-tmpl">
  8.         '<option id="${Id}">${Name}</option>'
  9.         </script>
  10.         <input type="button" id="btnActSearch" value="Search" />
  11. </div>

The jQuery Mobile conventions that perform many tasks "automagically" are based on the HTML5 "data-" attributes. I am relying on these conventions to build my HTML pages in our sample app. These attributes have special meaning and context within the application's UI generation. I simply define the template for my HTML construct, and at runtime jQuery Mobile turns it into the appropriate HTML element. On the home screen, for example, the data-role defines a page, content, header, collapsible panel, and footer sections. The data-position="fixed" fixes the footer section on the page, and data-theme attributes can be used to set up the application theme. The remainder of the page is standard HTML.

The next step is to hook events to the search buttons and populate the detail screen with user data. The actor/actress select box uses a jQuery template instead of standard HTML markup. (For more information about jQuery templates, see the list of related articles at the end of this article.) In the jQuery ready event setup, click events for all three search buttons, as well as hydrate the actor/actress select box.

For the search by text button click, once a user enters text in the input box, the Movies List page pops up. Figure 9 shows the code that performs these actions. The search by year-range code, shown in Figure 10, automatically increments the selected year by 10 to create a selection range. For example, if the user selects 1970, the range displays as 1970-1980. We will pass this year range as a query string parameter to the Movies List page. If the user selects 2010, then only one year is added.

  1. $('#btnTxtSearch').click(function () {
  2. var movieName = $('#txtMovieTitle').val();
  3. if (!movieName || movieName.length == 0 || movieName == null) {
  4. alert('Please insert a title before clicking the button');
  5. return false;
  6. } //end of if
  8. window.location = "NetflixMoviesList.html?searchType=textSearch&searchText=" + $('#txtMovieTitle').val();
  10. });
  1. $('#btnYrSearch').click(function () {
  2. var releaseYearSelected = $("#dateOptions").val();
  4. var selectedEndDecade = parseInt(releaseYearSelected) + 10;
  5. if (releaseYearSelected == 2010) {
  6. selectedEndDecade = parseInt(releaseYearSelected) + 1;
  7. }
  9. window.location = "NetflixMoviesList.html?searchType=yearSearch&releaseYear=" + releaseYearSelected + "&endDecade=" + selectedEndDecade;
  11. });

For the last search panel, we first need to fill in the select box with the actor/actress name. To do so, we'll run an OData query to grab the first 200 names from the Netflix database to populate the select box, as shown in Figure 11.

  1. var actorListURL = "$top=200&$callback=myActorsCallback&$format=json";
  3. $.ajax({
  4. dataType: 'jsonp',
  5. url: actorListURL,
  6. jsonpCallback: 'myActorsCallback',
  7. success: myActorsCallback,
  8. error: function () { alert('Error occurred'); },
  9. timeout: 6000
  10. }); //end of ajax
  12. function myActorsCallback(result) {
  13. $("#actorList").tmpl(result.d).appendTo("#actorOptions");
  14. }

In addition, we'll need to hook the search button of this panel, to get the actor ID selected by the user and pass it to the Movies List page so that the movies list is populated accordingly. Figure 12 shows the code that does this.

  1. $("#btnActSearch").click(function () {
  3. var selectedActorId = $('#actorOptions option:selected').not('#defaultOption').attr('id');
  5. if (selectedActorId == null) {
  6. alert('Please, make a selection');
  7. return false;
  8. } //end of if
  9. else
  10. window.location = "NetflixMoviesList.html?searchType=actorSearch&actorID=" + selectedActorId;
  11. });

Designing the List Screen

Once the user has initiated a search via one of the methods provided on the home screen, the application takes the user to a Movies List screen containing items that match the searched criteria. Using the query string to pass parameters from the home screen to the list screen initiates the appropriate search query to the Netflix OData feed. In the list screen, a header shows a back button, the main content area for the actual movie list, and a fixed footer area. Figure 13 shows the list screen markup.

  1. <div id="MovieList" data-role="page" class="type-home">
  2.         <div data-role="header" data-position="fixed" data-theme="b">
  3.         <a class="ui-btn-left jqm-home ui-btn ui-btn-icon-notext ui-btn-corner-all ui-shadow ui-btn-up-b" data-rel="back" data-iconpos="notext" data-icon="home" title="Home" data-theme="b"><span class="ui-btn-inner ui-btn-corner-all" aria-hidden="true"><span class="ui-btn-text">Home<span class="ui-icon ui-icon-home ui-icon-shadow"></a>
  4.                 <h1>Movies List</h1>
  5.              </div>
  6.             <div data-role="content">
  7.                     <div class="content-primary">      
  8.                         <ul id="movieList" data-role="listview" class="ui-listview">
  9.                         </ul>
  10.                 <script id="movieListTmpl" type="text/x-jquery-tmpl">
  11. <li data-theme="c" class="ui-btn ui-btn-icon-right ui-li-has-arrow ui-li ui-li-has-thumb ui-btn-up-c"><div class="ui-btn-inner ui-li" aria-hidden="true"><div class="ui-btn-text"><a href="javascript: window.location = 'NetflixMovieDetail.html?MovieId=${Id}';" class="ui-link-inherit">
  12.                                         <img src="${BoxArt.LargeUrl}" class="ui-li-thumb">
  13.                                         <h3 class="ui-li-heading">${Name}</h3>
  14.                                         <p class="ui-li-desc">{{html Synopsis}}</p>
  15.                                 </a></div><span class="ui-icon ui-icon-arrow-r ui-icon-shadow"></div></li>
  16.                  </script>  
  17.            </div>
  18.        </div>
  19.        <div data-role="footer" data-theme="b" data-position="fixed">
  20.             <p> >>> Click on a movie to view details <<< </p>
  21. </div>
  22. </div>

A header element is created by defining data-role="header" and setting data-position="fixed", which ensures that the header is always visible, even when the user scrolls the list. The page title is "Movies List" with an h1 tag. To define the list, a jQuery template is set up and filled in by calling the script with data received from the Netflix OData service. Finally, the footer element is defined by data-role="footer" and is also fixed just like the header.

Once the markup is completed, the search type in the script section is filtered out by examining the query string "searchType" parameter, as shown in Figure 14.

  1. var searchType = getParameterByName("searchType");
  3. $(function () {
  5. var queryURL;
  7. switch (searchType) {
  8. case "textSearch":
  9.                      var movieName = getParameterByName("searchText");
  10.                      var movieTitle = escape(movieName);
  11. queryURL = "$callback=myCallback&$format=json&$filter=Type eq 'Movie' and substringof('" + movieTitle + "',Name)";
  12.                      break;
  13. case "yearSearch":
  14.                      var releaseYearSelected = getParameterByName("releaseYear");
  15.                      var selectedEndDecade = getParameterByName("endDecade");
  16. queryURL = "$top=20&$callback=myCallback&$format=json&$filter=ReleaseYear ge " + releaseYearSelected + "and ReleaseYear le " + selectedEndDecade;
  17.                      break;
  18. case "actorSearch":
  19. var selectedActorId = getParameterByName("actorID");
  20. queryURL = "" + selectedActorId + ")/TitlesActedIn?$callback=myCallback&$format=json";
  21. break;
  22. }
  24. queryService(queryURL);
  26. });
  28. function queryService(URL) {
  30. $.ajax({
  31. dataType: 'jsonp',
  32. url: URL,
  33. jsonpCallback: 'myCallback',
  34. success: myCallback,
  35. error: function () { alert('Error occurred'); },
  36. timeout: 60000
  37. });
  38. }
  40. function myCallback(data) {
  42. if (searchType != "yearSearch")
  43. $("#movieListTmpl").tmpl(data.d.results).appendTo("#movieList");
  44. else
  45. $("#movieListTmpl").tmpl(data.d).appendTo("#movieList");
  46. }
  48. function getParameterByName(name) {
  49. name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
  50. var regexS = "[\\?&]" + name + "=([^]*)";
  51. var regex = new RegExp(regexS);
  52. var results = regex.exec(window.location.href);
  53. if (results == null)
  54. return "";
  55. else
  56. return decodeURIComponent(results[1].replace(/\+/g, " "));
  57. }

Based on the search type, an OData URL is constructed with the appropriate query. When a response is returned to the client, a jQuery template injects JavaScript Object Notation (JSON) data into the page content template. The outcome of this code execution is a jQuery Mobile list control displaying movie information.

Designing the Detail Screen

In the list screen, each element in the list is an <li> tag with an anchor tag inside of it that points to a detail screen. If the user taps or clicks on any of the movies, the detail screen appears showing additional movie information. The same concept as described in the previous section is used here: A query string passes the movie ID on to the detail page. The detail page takes that movie ID, queries the Netflix OData service, and uses the markup shown in Figure 15 to display the information returned from the service. The text marked as curly braces is replaced by movie data when the page loads.

  1. <div data-role="page" class="type-home">
  2.         <div data-role="header" data-position="fixed" data-theme="b">
  3.         <a data-rel="back" data-icon="back" class="ui-btn-left">Back</a>
  4.                 <h1>Movie Details</h1>
  5.              </div>
  6.             <div data-role="content">
  7.                     <div class="content-primary">      
  8.                        <div><h1 id="nfMovieName"></b></h1></div>
  9.                                 <table border="0" cellpadding="10" cellspacing="10">
  10.                         <tr>
  11.                             <td valign="top">
  12.                                 <img id="nfMovieImage" src="" />
  13.                             </td>
  14.                                 <td valign="top">
  15.                                             <div id="nfMovieSynopsis">
  16.                                                             {Synopsis}  
  17.                                                     </div>
  18.                                 <br />
  19.                                             <div>                  
  20.                                             <b>Runtime: </b><span id="nfRuntime"><br>
  21.                                                     <b>Type: </b><span id="nfMovieType"><br>
  22.                                             <b>Average Rating: </b><span id="nfMovieAverageRating"><br>
  23.                                 <a id="nfMovieLink" href="#">See it on</a>
  24.                          </div>
  25.                    </td>
  26.                 </tr>
  27.            </table>
  28.         </div>
  29.     </div>
  30. </div>

Using the jQuery onready function, the search parameter is passed from the List page and queries the Netflix service to capture that movie's information. Upon receiving movie information, the HTML content is replaced with actual movie data, as shown in Figure 16. The application's movie detail page is now complete.

  1. $(function () {
  2.     var movieID = getParameterByName("MovieId");
  3.     var queryURL = "'" + movieID + "')?$callback=myCallback&$format=json";
  4.     queryService(queryURL);
  6. });
  8. function queryService(URL) {
  9.         $.ajax({
  10.             dataType: 'jsonp',
  11.             url: URL,
  12.             jsonpCallback: 'myCallback',
  13.             success: myCallback,
  14.             error: function () { alert('Error occurred'); },
  15.             timeout: 60000
  16.         }); //
  17.     }
  19. function myCallback(data) {
  21.         $("#nfMovieName").html(data.d.Name + "(" + data.d.ReleaseYear + ")");
  22.         $("#nfMovieImage").attr("src", data.d.BoxArt.LargeUrl);
  23.         $("#nfMovieSynopsis").html(data.d.Synopsis);
  24.         $("#nfRuntime").html(Math.round(data.d.Runtime / 60) + " Minutes");
  25.         $("#nfMovieType").html(data.d.Type);
  26.         $("#nfMovieAverageRating").html(data.d.AverageRating);
  27.         $("#nfMovieLink").attr("href", data.d.Url);
  28. }
  30. function getParameterByName(name) {
  31.         name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]");
  32.         var regexS = "[\\?&]" + name + "=([^]*)";
  33.         var regex = new RegExp(regexS);
  34.         var results = regex.exec(window.location.href);
  35.         if (results == null)
  36.             return "";
  37.         else
  38.             return decodeURIComponent(results[1].replace(/\+/g, " "));
  39. }

Client-Centric Benefits

Our example has demonstrated the key benefits that a client-centric web application development model offers. The client-centric approach decouples the client and server in a way that data-access layer (DAL) and view generation are now done on the client. This offloading of the DAL and view generation onto the client enables developers to build applications that are much more scalable on the server side. In this client-centric approach, performance is no longer a bottleneck; all modern browsers can handle this level of JavaScript code execution without hindering the application's user experience. Bandwidth is also optimized, because only JSON data is transmitted between client and server rather than full HTML streams.

In addition to seeing the benefits of a client-centric architecture, you've also seen how jQuery Mobile can help you build a mobile web application. By using the jQuery Mobile HTML constructs and conventions, you can easily define your HTML template and let the framework take over at runtime and generate the appropriate HTML elements. I hope this article encourages you to take your explorations of JavaScript, jQuery, and client-centric web development further in "Better Mobile Website Development Using the jQuery Mobile Library," where we will show you tips and tricks for optimizing your mobile web app with jQuery Mobile.