One objection that people always raise against Ajax applications concerns browser history. Because an Ajax application updates its content by performing sneaky Ajax postbacks, the browser backwards and forwards buttons don’t work as you would normally expect.

In a normal, non-Ajax application, when you click the browser back button, you return to a previous state of the application. For example, if you are paging through a set of movie records, you might return to the previous page of records.

In an Ajax application, on the other hand, the browser backwards and forwards buttons do not work as you would expect. If you navigate to the second page in a list of records and click the backwards button, you won’t return to the previous page. Most likely, you will end up navigating away from the application entirely (which is very unexpected and irritating).

Bookmarking presents a similar problem. You cannot bookmark a particular page of records in an Ajax application because the address bar does not reflect the state of the application.

The Ajax Solution

There is a solution to both of these problems. To solve both of these problems, you must take matters into your own hands and take responsibility for saving and restoring your application state yourself. Furthermore, you must ensure that the address bar gets updated to reflect the state of your application.

In this blog entry, I demonstrate how you can take advantage of a jQuery library named bbq that enables you to control browser history (and make your Ajax application bookmarkable) in a cross-browser compatible way.

The JavaScript Libraries

In this blog entry, I take advantage of the following four JavaScript files:

  1. jQuery-1.4.2.js – The jQuery library. Available from the Microsoft Ajax CDN at http://ajax.microsoft.com/ajax/jquery/jquery-1.4.2.js
  2. jquery.pager.js – Used to generate pager for navigating records. Available from http://plugins.jquery.com/project/Pager
  3. microtemplates.js – John Resig’s micro-templating library. Available from http://ejohn.org/blog/javascript-micro-templating/
  4. jquery.ba-bbq.js – The Back Button and Query (BBQ) Library. Available from http://benalman.com/projects/jquery-bbq-plugin/

All of these libraries, with the exception of the Micro-templating library, are available under the MIT open-source license.

The Ajax Application

Let’s start by building a simple Ajax application that enables you to page through a set of movie database records, 3 records at a time.

We’ll use my favorite database named MoviesDB. This database contains a Movies table that looks like this:

clip_image002

We’ll create a data model for this database by taking advantage of the ADO.NET Entity Framework. The data model looks like this:

clip_image004

Finally, we’ll expose the data to the universe with the help of a WCF Data Service named MovieService.svc. The code for the data service is contained in Listing 1.

Listing 1 – MovieService.svc

using System.Data.Services;
using System.Data.Services.Common;

namespace WebApplication1
{
    public class MovieService : DataService<MoviesDBEntities>
    {
        public static void InitializeService(DataServiceConfiguration config)
        {
            config.SetEntitySetAccessRule("Movies", EntitySetRights.AllRead);
            config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
        }
    }
}

The WCF Data Service in Listing 1 exposes the movies so that you can query the movie database table with URLs that looks like this:

The HTML page in Listing 2 enables you to page through the set of movies retrieved from the WCF Data Service.

Listing 2 – Original.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Movies with History</title>
    <link href="Design/Pager.css" rel="stylesheet" type="text/css" />
</head>
<body>

<h1>Page <span id="pageNumber"></span> of <span id="pageCount"></span></h1>

<div id="pager"></div>
  <br style="clear:both" /><br />
<div id="moviesContainer"></div>

<script src="http://ajax.microsoft.com/ajax/jquery/jquery-1.4.2.js" type="text/javascript"></script> 
<script src="App_Scripts/Microtemplates.js" type="text/javascript"></script>
<script src="App_Scripts/jquery.pager.js" type="text/javascript"></script>
<script type="text/javascript">
    var pageSize = 3, pageIndex = 0;

    // Show initial page of movies
    showMovies();

    function showMovies() {
        // Build OData query
        var query = "/MovieService.svc" // base URL
            + "/Movies" // top-level resource
            + "?$skip=" + pageIndex * pageSize // skip records
            + "&$top=" + pageSize  // take records
            + " &$inlinecount=allpages";  // include total count of movies

        // Make call to WCF Data Service
        $.ajax({
            dataType: "json",
            url: query,
            success: showMoviesComplete
        });
    }

    function showMoviesComplete(result) {
        // unwrap results
        var movies = result["d"]["results"];
        var movieCount = result["d"]["__count"]

        // Show movies using template
        var showMovie = tmpl("<li><%=Id%> - <%=Title %></li>");
        var html = "";
        for (var i = 0; i < movies.length; i++) {
            html += showMovie(movies[i]);
        }
        $("#moviesContainer").html(html);

        // show pager
        $("#pager").pager({
            pagenumber: (pageIndex + 1),
            pagecount: Math.ceil(movieCount / pageSize),
            buttonClickCallback: selectPage
        });

        // Update page number and page count
        $("#pageNumber").text(pageIndex + 1);
        $("#pageCount").text(movieCount);
    }

    function selectPage(pageNumber) {
        pageIndex = pageNumber - 1;
        showMovies();
    }

</script>
</body>
</html>

The page in Listing 3 has the following three functions:

  • showMovies() – Performs an Ajax call against the WCF Data Service to retrieve a page of movies.
  • showMoviesComplete() – When the Ajax call completes successfully, this function displays the movies by using a template. This function also renders the pager user interface.
  • selectPage() – When you select a particular page by clicking on a page number in the pager UI, this function updates the current page index and calls the showMovies() function.

Figure 1 illustrates what the page looks like when it is opened in a browser.

clip_image006

Figure 1

If you click the page numbers then the browser history is not updated. Clicking the browser forward and backwards buttons won’t move you back and forth in browser history.

Furthermore, the address displayed in the address bar does not change when you navigate to different pages. You cannot bookmark any page except for the first page.

Adding Browser History

The Back Button and Query (bbq) library enables you to add support for browser history and bookmarking to a jQuery application. The bbq library supports two important methods:

  1. jQuery.bbq.pushState(object) – Adds state to browser history.
  2. jQuery.bbq.getState(key) – Gets state from browser history.

The bbq library also supports one important event:

  1. hashchange – This event is raised when the part of an address after the hash # is changed.

The page in Listing 3 demonstrates how to use the bbq library to add support for browser navigation and bookmarking to an Ajax page.

Listing 3 – Default.html

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Movies with History</title>
    <link href="Design/Pager.css" rel="stylesheet" type="text/css" />
</head>
<body>

<h1>Page <span id="pageNumber"></span> of <span id="pageCount"></span></h1>

<div id="pager"></div>
  <br style="clear:both" /><br />
<div id="moviesContainer"></div>

<script src="http://ajax.microsoft.com/ajax/jquery/jquery-1.4.2.js" type="text/javascript"></script> 
<script src="App_Scripts/jquery.ba-bbq.js" type="text/javascript"></script>
<script src="App_Scripts/Microtemplates.js" type="text/javascript"></script>
<script src="App_Scripts/jquery.pager.js" type="text/javascript"></script>
<script type="text/javascript">
    var pageSize = 3, pageIndex = 0;

    $(window).bind('hashchange', function (e) {
        pageIndex = e.getState("pageIndex") || 0;
        pageIndex = parseInt(pageIndex);
        showMovies();
    });

    $(window).trigger('hashchange');

    function showMovies() {
        // Build OData query
        var query = "/MovieService.svc" // base URL
            + "/Movies" // top-level resource
            + "?$skip=" + pageIndex * pageSize // skip records
            + "&$top=" + pageSize  // take records
            +" &$inlinecount=allpages";  // include total count of movies

        // Make call to WCF Data Service
        $.ajax({
            dataType: "json",
            url: query,
            success: showMoviesComplete
        });
    }

    function showMoviesComplete(result) {
        // unwrap results
        var movies = result["d"]["results"];
        var movieCount = result["d"]["__count"]

        // Show movies using template
        var showMovie = tmpl("<li><%=Id%> - <%=Title %></li>");
        var html = "";
        for (var i = 0; i < movies.length; i++) {
            html += showMovie(movies[i]);
        }
        $("#moviesContainer").html(html);

        // show pager
        $("#pager").pager({
            pagenumber: (pageIndex + 1),
            pagecount: Math.ceil(movieCount / pageSize),
            buttonClickCallback: selectPage
        });

        // Update page number and page count
        $("#pageNumber").text(pageIndex + 1);
        $("#pageCount").text(movieCount);
    }

    function selectPage(pageNumber) {
        pageIndex = pageNumber - 1;
        $.bbq.pushState({ pageIndex: pageIndex });
    }

</script>
</body>
</html>

Notice the first chunk of JavaScript code in Listing 3:

$(window).bind('hashchange', function (e) {
    pageIndex = e.getState("pageIndex") || 0;
    pageIndex = parseInt(pageIndex);
    showMovies();
});

$(window).trigger('hashchange');

When the hashchange event occurs, the current pageIndex is retrieved by calling the e.getState() method. The value is returned as a string and the value is cast to an integer by calling the JavaScript parseInt() function. Next, the showMovies() method is called to display the page of movies.

The $(window).trigger() method is called to raise the hashchange event so that the initial page of records will be displayed.

When you click a page number, the selectPage() method is invoked. This method adds the current page index to the address by calling the following method:

$.bbq.pushState({ pageIndex: pageIndex });

For example, if you click on page number 2 then page index 1 is saved to the URL. The URL looks like this:

clip_image008

Notice that when you click on page 2 then the browser address is updated to look like:

/Default.htm#pageIndex=1

If you click on page 3 then the browser address is updated to look like:

/Default.htm#pageIndex=2

Because the browser address is updated when you navigate to a new page number, the browser backwards and forwards button will work to navigate you backwards and forwards through the page numbers. When you click page 2, and click the backwards button, you will navigate back to page 1.

Furthermore, you can bookmark a particular page of records. For example, if you bookmark the URL /Default.htm#pageIndex=1 then you will get the second page of records whenever you open the bookmark.

Summary

You should not avoid building Ajax applications because of worries concerning browser history or bookmarks. By taking advantage of a JavaScript library such as the bbq library, you can make your Ajax applications behave in exactly the same way as a normal web application.

If you liked this blog post then please Subscribe to this blog.
posted on Thursday, April 08, 2010 8:58 PM | Filed Under [ AJAX ASP.NET JavaScript jQuery ]

Comments

Gravatar
# re: jQuery, ASP.NET, and Browser History
Posted by RN
on 4/8/2010 9:20 PM

Nice use of URL Fragments<br />
Gravatar
# re: jQuery, ASP.NET, and Browser History
Posted by Raghuraman
on 4/8/2010 10:37 PM

Very Nice and detailed example !!! Thanks for the Write Up Stephen.
Gravatar
# re: jQuery, ASP.NET, and Browser History
Posted by Praveen Prasad
on 4/8/2010 11:41 PM

asp.net ajax is already having this feature<br /><br />sys.application.addHistoryPoint();<br /><br />is MS going to remove that feature in future releases!!
Gravatar
# re: jQuery, ASP.NET, and Browser History
Posted by Roland
on 4/9/2010 1:14 AM

That's great! I am building a fully AJAX application and I was wondering why a "state-of-the-art application" couldn't take advantage of the most basic features such as backward and forward buttons :p<br /><br />Stephen, as I was reading your snippet code, I noticed you use the notation "$(window)" when calling your jQuery function. Is your notation allows you to operate on DOM elements when they are loaded -- like $(document).ready() provides for it?<br /><br />Regards,<br /><br />R.
Gravatar
# re: jQuery, ASP.NET, and Browser History
Posted by Abdul Rauf
on 4/9/2010 3:47 AM

Nice, this was the information I was looking for.
Gravatar
# re: jQuery, ASP.NET, and Browser History
Posted by Damien
on 4/9/2010 4:17 AM

Very nice blog! The only thing that is missing is a downloadable solution to play around with :)
Gravatar
# More about BBQ
on 4/9/2010 7:48 AM

Thanks for the great write-up, Stephen!<br /><br />Also, if any readers are interested, there are a number of working examples linked to on the <a href="http://benalman.com/projects/jquery-bbq-plugin/">jQuery BBQ</a> project page that illustrate how BBQ can be leveraged to handle far more complex bookmarkable states, and how it can work with caching AJAX requests (among other things)
Gravatar
# re: jQuery, ASP.NET, and Browser History
Posted by dsweb1017
on 4/9/2010 1:23 PM

Nice blog... excellent approach for calling WCF services using Client Side API's. It offers an alternative approach to using server side logic<br />
Gravatar
# re: jQuery, ASP.NET, and Browser History
Posted by Sohbet
on 4/11/2010 4:33 AM

Thanks, Forever blog page..
Gravatar
# re: jQuery, ASP.NET, and Browser History
Posted by wholesale
on 4/11/2010 7:06 PM

thank you infor
Gravatar
# re: jQuery, ASP.NET, and Browser History
Posted by Mike Clark
on 4/14/2010 8:48 AM

Darn. I just bought ASP.NET MVC Framework Unleashed a few days ago, then I downloaded the RTM of VS2010. I made the mistake of starting the book's examples while using VS2010 and things are subtly different. Make that wildly different. Instead of:<br /><br /><%= Html.Textbox("Id") %><br /><br />There is:<br /><br /><%: Html.TextBoxFor(model => model.id) %><br /><br />!!!!<br /><br />Now I am depressed. Will there be a new edition of the book, covering, apparently, ASP.NET 4? Or whatever changed?
Gravatar
# re: jQuery, ASP.NET, and Browser History
Posted by JashuaNet
on 4/19/2010 5:09 AM

Hi, You can do moreover with PokeIn library under codeplex.
Gravatar
# re: jQuery, ASP.NET, and Browser History
Posted by borektarifleri
on 4/19/2010 6:16 AM

Your article helped me so much on my works. Thanks.
Gravatar
# re: jQuery, ASP.NET, and Browser History
Posted by beddingpedia
on 4/20/2010 7:35 AM

Thanks for the great write-up, Stephen!
Gravatar
# re: jQuery, ASP.NET, and Browser History
on 4/20/2010 9:26 PM

Thanks for sharing your stuff in coding Ajax application. I'm looking forward some donwloadable stuff in your next blog.
Gravatar
# re: jQuery, ASP.NET, and Browser History
Posted by wecol
on 4/21/2010 7:35 AM

thank u for sharing, i archived
Gravatar
# re: jQuery, ASP.NET, and Browser History
on 4/22/2010 4:01 AM

Very Nice and detailed example. I also want that type of blog
Gravatar
# re: jQuery, ASP.NET, and Browser History
Posted by Itinerarist
on 4/23/2010 3:06 AM

Very Nice and detailed example!
Gravatar
# re: jQuery, ASP.NET, and Browser History
Posted by 1 oz silver
on 4/25/2010 9:35 PM

Its really annoying when clicking the browser forward and backwards buttons won’t move you back and forth in browser history.
Gravatar
# re: jQuery, ASP.NET, and Browser History
Posted by cam
on 4/30/2010 8:13 PM

Very well written post, thanks for going into the details and really explaining everything in easy to grasp terms.
Gravatar
# re: jQuery, ASP.NET, and Browser History
on 5/1/2010 7:36 PM

ASP.NET is the next generation ASP, but it's not an upgraded version of ASP.
Comments have been closed on this topic.