This site uses cookies for analytics, personalized content and ads. By continuing to browse this site, you agree to this use. Learn more
Microsoft Logo
Gray Pipe
  • Developer Network
    • Downloads
      • Visual Studio
      • SDKs
      • Trial software
    • Programs
      • Subscriptions
      • Students
      • ISV
      • Startups
      • Events
    • Community
      • Magazine
      • Forums
      • Blogs
      • Channel 9
    • Documentation
      • APIs and reference
      • Dev centers
      • Samples
      • Retired content
Developer Network Developer

Subscriber portal

Get tools
magazine
  • Issues and downloads
    • All issues
    • 2019
      • February 2019
      • January 2019
    • 2018
      • Connect(); 2018
      • December 2018
      • November 2018
      • October 2018
      • September 2018
      • August 2018
      • July 2018
      • June 2018
      • May 2018
      • April 2018
      • March 2018
      • February 2018
      • January 2018
    • 2017
      • Connect(); 2017
      • December 2017
      • November 2017
      • October 2017
      • September 2017
      • August 2017
      • July 2017
      • June 2017
      • May 2017
      • April 2017
      • March 2017
      • February 2017
      • January 2017
    • 2016
      • December 2016
      • Connect(); 2016
      • November 2016
      • October 2016
      • September 2016
      • August 2016
      • July 2016
      • June 2016
      • May 2016
      • April 2016
      • March 2016
      • February 2016
      • January 2016
    • 2015
      • December 2015
      • November 2015
      • Windows 10 issue
      • October 2015
      • September 2015
      • August 2015
      • July 2015
      • June 2015
      • May 2015
      • April 2015
      • March 2015
      • February 2015
      • January 2015
    • 2014
      • Special 2014
      • December 2014
      • November 2014
      • October 2014
      • September 2014
      • August 2014
      • July 2014
      • June 2014
      • May 2014
      • April 2014
      • March 2014
      • February 2014
      • January 2014
    • 2013
      • Government 2013
      • December 2013
      • November 2013
      • October 2013
      • September 2013
      • August 2013
      • July 2013
      • June 2013
      • May 2013
      • April 2013
      • March 2013
      • February 2013
      • January 2013
    • 2012
      • December 2012
      • November 2012
      • Windows 8
      • October 2012
      • September 2012
      • August 2012
      • July 2012
      • June 2012
      • May 2012
      • April 2012
      • March 2012
      • February 2012
      • January 2012
    • 2011
      • December 2011
      • November 2011
      • October 2011
      • September 2011
      • August 2011
      • July 2011
      • June 2011
      • May 2011
      • April 2011
      • March 2011
      • February 2011
      • January 2011
    • 2010
      • December 2010
      • November 2010
      • October 2010
      • September 2010
      • August 2010
      • July 2010
      • June 2010
      • May 2010
      • April 2010
      • March 2010
      • February 2010
      • January 2010
    • 2009
      • December 2009
      • November 2009
      • October 2009
      • September 2009
      • August 2009
      • July 2009
      • June 2009
      • May 2009
      • April 2009
      • March 2009
      • February 2009
      • January 2009
  • Subscribe
  • Submit article
search clear
We’re sorry. The content you requested has been removed. You’ll be auto redirected in 1 second.

History and Back Button Support

Elijah Manor | May 13, 2010

 

Developing interactive websites is great for usability, but one common pitfall of modern websites is the lack of History and Back button support when JavaScript libraries and AJAX techniques are used.

Users expect to return to the previous state of the web page when they click the Back button; however, in a typical AJAX application, the Back button might take a user to a state from several actions ago. Not only does this confuse users, but most likely they think that your software has a bug and are less satisfied with their experience overall.

Part of the reason for this lack of support is that developers have to add additional logic to intercept the Back button and respond to it accordingly. Because the web browser isn’t aware that you are manipulating the DOM behind the scenes, it does not keep track of that for you.

To address this issue, several projects have been written, such as Mikage Sawatari’sjQuery History plugin, Asual’sjQuery Address plugin, Jim Palmer’sjQuery jHistory plugin, and Ben Alman’sjQuery BBQ plugin. These libraries all provides techniques to support the Back button and enable bookmarkable links, but some of them are more successful than others when you take into account ease of use, cross-browser support, and active development.

The jQuery BBQ Plugin

In this article I focus on the jQuery BBQ plugin written by Ben Alman. The plugin’s name, BBQ, is an acronym for Back Button and Query library. From here on, I refer to the jQuery BBQ plugin as BBQ.

Ben put a lot of effort into making his plugin supportable across browsers. In his words, the plug-in has been “Tested with jQuery 1.3.2, 1.4.1, 1.4.2 in Internet Explorer 6–8, Firefox 2–3.7, Safari 3–4, Chrome 4–5, Opera 9.6–10.1, Mobile Safari 3.1.1.”

One of the many things I look for in a library before I invest my time and risk adding it to my project is active involvement by the author. I want to know that bugs are being addressed and that enhancements are being made. The last thing I want is a library that gets stagnant and prevents me from upgrading to a new version of jQuery.

Ben has made an intentional effort to keep BBQ up to date with the latest version of jQuery, provided full documentation of his plugin, and written a suite of more than three hundred unit tests. You can check on the progress of his project on the GitHub project page.

Navigating with the URL Hash

Before I get into the details of BBQ, let’s briefly review the current state of the browser and fragment identifiers. Browsers currently support the #hash syntax in a URL to navigate up and down the same page. For example, I’m sure you’ve done something like this sometime (you can view, run, and edit this code example from jsFiddle):

HTML/XHTML
Copy
<!-- You can view, run, and edit this code example from http://jsfiddle.net/elijahmanor/rn7As/ -->

<!-- Deprecated technique using a named anchor -->

<a name="top">Top of Page</a>



<!-- Preferred approach using element with an id attribute  -->

<div id="top">Top of Page</div>



<!-- The rest of your HTML -->



<!-- Anchor to fragment 

    (deprecated named anchor or element with id attribute) -->

<a href="#top">Go To Top</a>

If you click the Go To Top anchor tag, the page navigates to the location of the element with the id attribute of “top” (or a named anchor with the name attribute of “top”).

Not only are you sent to Top of Page, but the URL is appended with the fragment identifier “#top”. If you bookmark this page and return to it at a later time, you go to the same position.

By using this syntax, developers can mark URLs with special data and then respond by using JavaScript, which is just what our first example will do.

The jQuery hashchange Event

Underneath the covers, the BBQ uses the HTML5 hashchange event to respond when changes occur to the fragment identifier (#hash) in the URL. Ben has given hashchange cross-browser support inside his jQuery hashchange event. Currently, only Internet Explorer 8, Firefox 3.6, and Chrome 5 provide native support. Ben used some special tricks to get the hashchange event to work in the other browsers and in older versions, but you can read more about that on his website if you are interested.

Example Using the hashchange Event

Depending on your application, you might get away with using only the jQuery hashchange event and not the entire BBQ plugin. If your application already uses #hash to maintain state inside your web page, you can use the jQuery hashchange event by itself. In a later example, I’ll show how to use the full BBQ plugin and describe when that’s necessary.

I’ve put together a simple jQuery tab example (see Figure 1) that uses #hash values in the href attribute of each tab anchor element.


Figure 1 A simple jQuery tab example using #hash links in the anchor href attribute.

 

HTML/XHTML
Copy
<!-- You can view, run, & edit this code example from http://jsfiddle.net/elijahmanor/BWrQP/ -->

<div class="tabs">

    <ul>

        <li class="tab"><a href="#div1">Tab 1</a></li>

        <li class="tab"><a href="#div2">Tab 2</a></li>

        <li class="tab"><a href="#div3">Tab 3</a></li>

    </ul>

    <div id="div1" class="content">Div 1</div>

    <div id="div2" class="content">Div 2</div>

    <div id="div3" class="content">Div 3</div>

</div>



//When a tab is clicked, pass it to the updateTabs method

$(".tabs .tab a").live("click", function(e) {

   updateTabs($($(this).attr("href")));

});



//Grab hash off URL (default to first tab) and update

$(window).bind("hashchange", function(e) {

   var anchor = $(location.hash);

   if (anchor.length === 0) {

      anchor = $(".tabs div:eq(0)");

   }

   updateTabs(anchor);

});



//Pass in the tab and show appropriate contents

function updateTabs(tab) {

   $(".tabs .tab a")

      .removeClass("active")

      .filter(function(index) {

         return $(this).attr("href") === '#' + tab.attr("id");

      }).addClass("active");

   $(".tabs .content").hide();

   tab.show();

}



//Fire the hashchange event when the page first loads

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

In this example, I used the standard #hash technique to maintain state in the browser, much like the Back To Top example shown earlier. I didn’t need to do anything special to get the #hash value in the URL. The browser does this for you because it recognizes this technique. Instead of navigating to a different location on the page, I used the hashchange event to respond to the change.

As you might expect, I have an event handler attached to the anchor element inside the tab, and once it’s clicked, I update the tabs accordingly.

There are two special things that you should notice that are different in this example.

One is the presence of $(window).bind("hashchange", function() {});. This is the key entry point for the hashchange event. We are listening for any change to location.hash and responding to it in the event handler.

This event handler grabs location.hash, which happens to be the hreffrom the tab’s anchor hrefattribute. The location.hash matches the id attribute of the tab’s contents div. If the #hash is not present, we default to the first tab.

The second thing you should notice is the $(window).trigger('hashchange'); statement at the end of the code sample. The only reason for this trigger event is the initial page load. If someone navigates to the page with a #hash as part of the URL, you want to honor that request and display the appropriate tab that matches the #hash.

Introduction to the jQuery BBQ Plugin API

Before I start showing another code example, let’s examine the jQuery BBQ plugin a little bit and highlight part of its API.

You should know about three main methods when you first start using the jQuery BBQ plugin. The three main actions are to push the current “state” (think of this as a snapshot of a point in time) of the page, to get the state again so you can respond to it, and then to remove the state when you are not using it.

jQuery.bbq.pushState

Adds the current state to the browser history

Updates location.hash

Triggers the hashchange event

jQuery.bbq.getState

Retrieves the current state from the browser history

Returns either a specific key from location.hash or the entire state

jQuery.bbq.removeState

Removes one or more keys from the current browser history

Creates a new state by updating location.hash

Triggers the hashchange event

Example Using the jQuery BBQ Plugin

Let’s take a slightly different approach from the previous example, and this time not use the href attribute to include a #hash value. Since we aren’t using #hash values as part of our anchor’s href attribute, we need to handle pushing, getting, and removing #hash values to the URL manually.

This example (see Figure 2) will look and behave the same as the previous one, but it uses the BBQ plugin behind the scenes to manage the historical state.


Figure 2 A simple jQuery tab example using the jQuery BBQ plugin to manage the history state of the tabs.

 

HTML/XHTML
Copy
<!-- You can view, run, & edit this code example from http://jsfiddle.net/elijahmanor/cskSw/ -->

<div class="tabs">

   <ul>

      <li class="tab">

         <span class="{contentId: '#div1'}">Tab 1</span>

      </li>

      <li class="tab">

         <span class="{contentId: '#div2'}">Tab 2</span>

      </li>

      <li class="tab">

         <span class="{contentId: '#div3'}">Tab 3</span>

      </li>

   </ul>

   <div id="div1" class="content">Div 1</div>

   <div id="div2" class="content">Div 2</div>

   <div id="div3" class="content">Div 3</div>

</div>

The preceding HTML snippet provides metadata inside the tab’s class attribute to associate an idof the div contents that should be displayed. This metadata can be retrieved using the jQuery Metadata plugin. For those of you not familiar with the jQuery Metadata plugin, it provides a way to embed JSON information inside an attribute and to retrieve the deserialized version at run time.

HTML/XHTML
Copy
//Push state of tabIndex to BBQ and handle logic in hashchange

$(".tabs .tab span").live("click", function(e) {

    $.bbq.pushState({ tabIndex: $(this).parent().index() });

    return false;

});



//Get tabIndex state from BBQ and update based on selection

$(window).bind("hashchange", function(e) {

    var tabIndex = $.bbq.getState("tabIndex") || "0",

        tab = $('.tabs .tab').eq(tabIndex);

    

    updateTabs(tab);

});





//Pass in the tab and show appropriate contents

function updateTabs(tab) {

    var title = tab.find('span');

    $(".tabs .tab span").removeClass("active");

    title.addClass("active");

    $(".tabs .content").hide();

    $(title.metadata().contentId).show();         

}



//Fire the hashchange event when the page first loads

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

This time, instead of having both the tab’s click and the window’s hashchange event handlers call the updateTabs method, I’ve removed this from the tab event handler, and now it only pushes its state to the jQuery BBQ plugin. This allows the hashchange event handler to handle the logic to update the user interface accordingly.

The tab’s click event handler needs to push its state to the jQuery BBQ plugin because we aren’t using the same #hash technique that the browser understands. (See the Back To Top example shown at the beginning of the article.) Behind the scenes, the jQuery BBQ plugin updates the URL so that the browser understands that you’ve updated the state of the page, and it keeps that information in its history.

Advanced jQuery BBQ Plugin Example

The tabs code snippets you’ve seen in the last two examples aren’t the most complicated scenarios in the world. Let’s next focus on something a little more advanced.

Here, we’re going to build a sortable list that you can reorder by dragging items up or down. If you double-click one of the items, a dialog appears showing information obtained via AJAX. After a user interacts with the application for a while (reordering items, opening dialogs, closing dialogs, and so on), the goal is to revert each action when the browser’s Back button is clicked.

First let’s take a look at the HTML we’re dealing with. We have an unordered list with a series of list items, each containing a Twitter userName and a count indicating the number of Tweets to retrieve. There is also a divdialog element in which the output of the AJAX request is displayed. (See Figure 3.)


Figure 3: Complex example of reordering and dialog boxes with back button support via BBQ

 

HTML/XHTML
Copy
<!-- You can view, run, & edit this code example from http://jsfiddle.net/elijahmanor/YLDGX/ -->

<div class="demo">

   <ul id="sortable">

      <li id="1" class="ui-state-default { userName : 'jquery', count : '1' }"><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>jQuery</li>

      <li id="2" class="ui-state-default { userName : 'jeresig', count : '2' }"><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>John Resig</li>

      <!-- Other List Items -->

      <li id="7" class="ui-state-default { userName : 'elijahmanor', count : '3' }"><span class="ui-icon ui-icon-arrowthick-2-n-s"></span>Elijah Manor</li>

   </ul>

</div>



<div id="dialog" title="Tweets" style="display: none;">

   <div id="output"></div>

</div>

When a list item is sorted, the code generates a unique key (number of milliseconds since midnight January 1, 1970) and uses it to cache the order of list items inside an array. The unique sequence key is then pushed to the jQuery BBQ plugin.

In a similar fashion, when a list item is double-clicked, the id of the item is pushed to BBQ. This information is then used to open a dialog. It is also noteworthy that when the dialog is clicked, the dialog information is removed from BBQ. (You can view, run, and edit the following code example from jsFiddle.)

JavaScript
Copy
//Setup sortable list and save sequence array in cache

var sortable = $("#sortable").sortable({

   delay: 500,

   update: function(event, ui) {

      //Generate unique id & store list item ids in cache

      var indexes = $(this).sortable("toArray");

      var sequenceId = new Date().valueOf().toString();

      $(this).data('sequence').cache[sequenceId] = indexes;

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

   }

}).disableSelection();



//When a list item is clicked push that dialog id to BBQ

$("#sortable li").live('dblclick', function(e) {

   $.bbq.pushState({ dialogId : $(this).attr('id')});

   return false;

});



//Set initial sequence state of page on 1st page load

var initialSequence = sortable.sortable("toArray");

sortable.data('sequence', {

   cache: {

      '': initialSequence

   }

});



//Setup dialog and when closed remove the dialog id from BBQ

var dialog = $('#dialog').dialog({

   autoOpen: false,

   width: 400,

   modal: true,

   buttons: {

      "Close": function() {

         $(this).dialog("close");

      }

   },

   close : function(event, ui) {

      $.bbq.removeState("dialogId");

   }

});



//Open dialog and sort list items if BBQ contains state

$(window).bind("hashchange", function(e) {

      //sequenceId: Key in cache for sequence indexes

   var sequenceId = e.getState("sequenceId") || '',

      //dialogId: Id of the list item used in dialog

      dialogId = e.getState("dialogId") || '',

      //metadata: Metadata attached to the list item

      metadata = dialogId ? 

         $('#' + dialogId).metadata() : null,

      //indexes: Sequence array of the list item ids

      indexes = sortable.data('sequence').cache[sequenceId];

    

   //Open dialog using id of list item or close if not present

   if(dialogId) {

      appendTweets(metadata.userName, 

         metadata.count, $("#output")); 

      dialog.dialog('open');

   } else {

      dialog.dialog('close');

   }

    

   //Sort the list items based on the indexes stored in cache

   $("#sortable li").reorder(indexes);

});



//Pull X number of tweets for userName and append to selection

function appendTweets(userName, count, selection) {

   var url = 'http://twitter.com/status/user_timeline/' + 

      userName + '.json?count=' + count + '&callback=?';

    

   dialog.dialog('option', 'title', '@' + userName);

   selection.empty();

   $.getJSON(url, function(data) {

      console.dir(data);

      var list = $('<ol />');

      $.each(data, function(i, item) {

         console.log(item.text);

         $('<li />', { text : item.text }).appendTo(list);

      });

      list.appendTo(selection);

   });

}



//Reorder items based on the ids within the indexes parameter

$.fn.reorder = function(indexes) {

   if (!indexes) { return this; }

    

   $.each(indexes, function(index, id) {

      var item = $('#' + id);

      if (item.attr("id") === id) {

         var parent = item.parent();

         item.remove().appendTo(parent);

      }

   });

    

   return this;

};



//Fire the hashchange event when the page first loads

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

Again, the code is rearranged slightly from how you might write it without supporting the Back button. In that case, you would probably place the logic inside your event handlers. Instead, I used those locations to push their state to BBQ so that the hashchange event handler could handle the logic.

Inside the hashchange event handler, we pull out the id of the list item from the BBQ. If an id exists, that means a dialog should be displayed. Metadata is attached to the list item with Twitter information that is used for the AJAX content to display in the dialog.

The hashchange event handler also grabs the sequence ID, which is the key for the cached list of ordered items from a particular state (point in time). The code then reorders the unordered list using a little jQuery plugin that I wrote.

A Deeper Look at the Documentation

The previous examples in this article mainly revolve around the three methods pushState,getState, and removeState.

However, the jQuery BBQ plugin provides many more features that you can utilize in your applications. Many of these methods are used behind the scenes when you call the three state methods.

For example, you might be aware of the jQuery.param method that is provided by the jQuery core library. This method serializes an array or object into a format suitable for a URL query string. However, the jQuery core library doesn’t provide a method to deserialize the URL query string. That is where the BBQ comes into play.

BBQ provides a jQuery.deparam method to deserialize the URL query string into an object. In addition, Ben provides the jQuery.deparam.querystring and jQuery.deparam.fragment methods that deserialize certain portions of the URL. As you might imagine, these methods are vital to the inner workings of the plugin, but the awesome part is that they are also exposed for you to use as well!

If you are interested in these methods and many others, feel free to check out the extensive BBQ documentation and supporting examples.

Who Is This Ben Alman?

In addition to the jQuery BBB plugin, Ben Alman has contributed many other useful projects, including the following:

jQuery equalizeBottoms   Allows you to set the height of multiple items to the same value.

jQuery outside events   Allows you to trigger an event when the user interacts outside a particular element.

jQuery replaceText   Allows you to replace text within a selection without messing up all the elements and attribute values.

jQuery resize event   Allows you to bind to a resize event of a particular DOM element.

jQuery throttle / debounce   Allows you to limit your methods by time. This can be useful if you don’t want to bombard your server with tons of requests.

Not only has Ben developed many high-quality projects, but he has also contributed valuable changes back to the jQuery core library. If you are serious about jQuery, you would be wise to follow Ben Alman on his blog, Twitter, and GitHub.

Conclusion

In today’s highly interactive JavaScript- and AJAX-based solutions, you don’t want to lose functionality that the user is used to and expects, such as support for the History and Back buttons and bookmarks. With the jQuery BBQ plugin, you can provide these features to users without jeopardizing high user satisfaction.

If you are interested in continuing your jQuery learning, I encourage you to follow me on Twitter for a fresh set of jQuery links and to check out my blog for my daily Tech Tweets roundup that contains numerous jQuery links to aid in your learning process.

 

About the Author

Elijah Manor is a Christian and a family man. He develops at appendTo as a Senior Architect providing corporate jQuery support, training, and consulting. He is an ASP.NET  MVP, ASPInsider, and specializes in ASP.NET MVC and jQuery development. He enjoys blogging about the things he learns. He is also active on Twitter and provides daily up-to-date Tech Tweets.

Find Elijah on:

  • Twitter - @elijahmanor
  • Elijah's Blog
  • Elijah's Web Dev Tweets

 

MSDN Magazine Blog

 

More MSDN Magazine Blog entries >


Current Issue


February 2019

Browse All MSDN Magazines


Subscribe to MSDN Flash newsletter


Receive the MSDN Flash e-mail newsletter every other week, with news and information personalized to your interests and areas of focus.

Follow us
  • https://www.facebook.com/microsoftdeveloper
  • https://twitter.com/msdev
  • https://plus.google.com/111221966647232053570/
Sign up for the MSDN Newsletter
Is this page helpful?
Your feedback about this content is important.
Let us know what you think.
Additional feedback?
1500 characters remaining
Thank you!
We appreciate your feedback.

Dev centers

  • Windows
  • Office
  • Visual Studio
  • Microsoft Azure
  • More...

Learning resources

  • Microsoft Virtual Academy
  • Channel 9
  • MSDN Magazine

Community

  • Forums
  • Blogs
  • Codeplex

Support

  • Self support

Programs

  • BizSpark (for startups)
  • Microsoft Imagine (for students)
United States (English)
  • Newsletter
  • Privacy & cookies
  • Terms of use
  • Trademarks
logo © 2019 Microsoft