jQuery UI Tabs with Hashchange

Jun 25, 2012
Dario Mora

Every day, there are more common web applications that make extensive use of JavaScript. This, among other things, makes a user interface much more feature-rich. Some sites that make use of this are the Google mail client Gmail or the famous social networking site, Facebook. In these applications, the entire interface is controlled by JavaScript. Accessing a link usually does not involve a new HTTP request; instead, the current document is updated with new content via Ajax.

But there is a problem when the web page is working in that way; the back and forward buttons on the browser do not work because the Ajax does store updates of the page in the browser history.

The trick to making the browser history work both backward and forward is using the hash character “#”.  For example, if you’re in a web page and want to make a link to a part of it, use the hash character followed by the keyword.

<h1 id="top">Some Title</h1>
<p>very long paragraph </p>
<a href="#top">Go top</a>

One advantage of these links is that they do not imply a new HTTP request and they work immediately. In addition, the browser is aware of them, so they are stored in history.

To accomplish the use of the tabs widget and the history in the browser, we need a method, from JavaScript, that lets us know when the record is updated to “#link”. This allows you, to know when you link to another section within the current document. In HTML5 the hash change is resolved with the event windows.onhashchange. However, this new event is not available in all browsers, so some hacks are needed to allow our application to work in some browsers.

In the case of using jQuery, the jQuery has a plugin called BBQ. This plugin enables very basic bookmarkable #hash history via a cross-browser HTML5 window.onhashchange event. You can read more about how it works here:

http://benalman.com/projects/jquery-hashchange-plugin/

Here is a simple example. We only have a list of tabs, and each will show its contents.

First, we would need to include the jQuery library as well as the jQuery BBQ plugin.

<!DOCTYPE html>
<html lang="es">
<head>
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.15/themes/base/jquery-ui.css" type="text/css" media="all" />

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js" type="text/javascript"></script> 

<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.15/jquery-ui.min.js" type="text/javascript"></script> 

<script type="text/javascript" src="jquery.ba-bbq.js"></script>  
</head>
<body>

</body>
</html>

Then we define a list of links and content blocks:

<!DOCTYPE html>
<html lang="es">
<head>
<link rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.15/themes/base/jquery-ui.css" type="text/css" media="all" />

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js" type="text/javascript"></script> 

<script src="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.15/jquery-ui.min.js" type="text/javascript"></script> 

<script type="text/javascript" src="jquery.ba-bbq.js"></script>
</head>
<body>

<div id="tabs">
 <ul>
<li><a href="#tabs-1">Nunc tincidunt</a></li>
<li><a href="#tabs-2">Proin dolor</a></li>
<li><a href="#tabs-3">Aenean lacinia</a></li>
</ul>
<div id="tabs-1">
<p>Proin elit arcu, rutrum commodo, vehicula tempus, commodo a, risus. Curabitur nec arcu. Donec sollicitudin mi sit amet mauris. Nam elementum quam ullamcorper ante. Etiam aliquet massa et lorem. Mauris dapibus lacus auctor risus. Aenean tempor ullamcorper leo. Vivamus sed magna quis ligula eleifend adipiscing. Duis orci. Aliquam sodales tortor vitae ipsum. Aliquam nulla. Duis aliquam molestie erat. Ut et mauris vel pede varius sollicitudin. Sed ut dolor nec orci tincidunt interdum. Phasellus ipsum. Nunc tristique tempus lectus.</p>
</div>
<div id="tabs-2">
<p>Morbi tincidunt, dui sit amet facilisis feugiat, odio metus gravida ante, ut pharetra massa metus id nunc. Duis scelerisque molestie turpis. Sed fringilla, massa eget luctus malesuada, metus eros molestie lectus, ut tempus eros massa ut dolor. Aenean aliquet fringilla sem. Suspendisse sed ligula in ligula suscipit aliquam. Praesent in eros vestibulum mi adipiscing adipiscing. Morbi facilisis. Curabitur ornare consequat nunc. Aenean vel metus. Ut posuere viverra nulla. Aliquam erat volutpat. Pellentesque convallis. Maecenas feugiat, tellus pellentesque pretium posuere, felis lorem euismod felis, eu ornare leo nisi vel felis. Mauris consectetur tortor et purus.</p>
</div>
<div id="tabs-3">
<p>Mauris eleifend est et turpis. Duis id erat. Suspendisse potenti. Aliquam vulputate, pede vel vehicula accumsan, mi neque rutrum erat, eu congue orci lorem eget lorem. Vestibulum non ante. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Fusce sodales. Quisque eu urna vel enim commodo pellentesque. Praesent eu risus hendrerit ligula tempus pretium. Curabitur lorem enim, pretium nec, feugiat nec, luctus a, lacus.</p>
<p>Duis cursus. Maecenas ligula eros, blandit nec, pharetra at, semper at, magna. Nullam ac lacus. Nulla facilisi. Praesent viverra justo vitae neque. Praesent blandit adipiscing velit. Suspendisse potenti. Donec mattis, pede vel pharetra blandit, magna ligula faucibus eros, id euismod lacus dolor eget odio. Nam scelerisque. Donec non libero sed nulla mattis commodo. Ut sagittis. Donec nisi lectus, feugiat porttitor, tempor ac, tempor vitae, pede. Aenean vehicula velit eu tellus interdum rutrum. Maecenas commodo. Pellentesque nec elit. Fusce in lacus. Vivamus a libero vitae lectus hendrerit hendrerit.</p>
</div>
</div>
</body>
</html>

 

Last, add the JavaScript code:

<script type="text/javascript">

$(document).ready(function(){

	var tabs = $('#tabs');

	var tab_a_selector = 'ul.ui-tabs-nav a';

	tabs.tabs({ event: 'change' });

	tabs.find( tab_a_selector ).click(function(){
		var state = {};
		var id = $(this).closest( '#tabs' ).attr( 'id' );
		var idx = $(this).parent().prevAll().length;
		state[ id ] = idx;
		$.bbq.pushState( state );

	});

	$(window).bind( 'hashchange', function(e) {
		tabs.each(function(){
			var idx = $.bbq.getState( this.id, true ) || 0;
			$(this).find( tab_a_selector ).eq( idx ).triggerHandler( 'change' );
		});
	});

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

});

</script> 

 

The code does the following:

  • Define a var with the selector for the "tab widgets" to handle.
 var tabs = $('#tabs');
  • This selector will be reused when selecting actual tab widget A elements.
 var tab_a_selector = 'ul.ui-tabs-nav a';
  • Enable tabs on all tab widgets. The “event” property must be overridden so that the tabs aren't changed on click, and any custom event name can be specified. Note that if you define a callback for the 'select' event, it will be executed for the selected tab whenever the hash changes.
 tabs.tabs({ event: 'change' });
  • Define our own click handler for the tabs, overriding the default.
tabs.find( tab_a_selector ).click(function(){
 var state = {};
  • Get the ID of this tab widget.
 var id = $(this).closest( '#tabs' ).attr( 'id' );
  • Get the index of this tab.
 var idx = $(this).parent().prevAll().length;
  • Set the state.
 state[ id ] = idx;
 $.bbq.pushState( state );
});
  • Define a function that will be attached to the event 'hashchange'. This event is triggered each time you refresh the #hash. 
$(window).bind( 'hashchange', function(e) {
  • Iterate over all tab widgets.
tabs.each(function(){
  • Get the index for this tab widget from the hash, based on the appropriate id property. In jQuery 1.4, you should use e.getState() instead of $.bbq.getState(). The second, 'true' argument coerces the string value to a number.
var idx = $.bbq.getState( this.id, true ) || 0;
  • Select the appropriate tab for this tab widget by triggering the custom event specified in the .tabs() init above (you could keep track of what tab each widget is on using .data, and only select a tab if it has changed).
$(this).find( tab_a_selector ).eq( idx ).triggerHandler( 'change' );
 });
})
  • Since the event is only triggered when the hash changes, we need to trigger the event now, to handle the hash the page may have loaded with.
$(window).trigger( 'hashchange' );
});

 

Now any tab buttons that are clicked will update the URL hash, so these tabs can be bookmarked.

jQuery BBQ enables simple, yet powerful bookmarkable #hash history via a cross-browser window.onhashchange event.