Unobtrusive Ajax Navigation – Version 2

June 8, 2009 09:28 by scibuff

Last week I started a new series about creating unobtrusive Ajax navigation. The first version represented a good start and laid a solid foundation. Nevertheless, it was far from an ideal solution mainly for the lack of some basic functionality and poor organization of both the front- and back-end code.

This tutorial will focus on cleaning up the Javascript code as well as adding functionality necessary to improve the user experience. Check out the demo and/or download all files.

In general, it is always a good practice to wrap all code within a unique namespace to prevent code collisions. The following few lines of code accomplish that:

if ( !com ) { var com = {}; }
if ( !com.scibuff ) { com.scibuff = {}; }
if ( !com.scibuff.dev ) { com.scibuff.dev = {}; }
if ( !com.scibuff.dev.ajax ) {
	com.scibuff.dev.ajax = function(){
		// private fields
		var tmp = {};
		// public fields
		var pub = {};
		return pub;
	}();
}

There are two objects inside the newly created object (com.scibuff.dev.ajax), i.e. tmp and pub. The former will be used for internal methods, variables and constants for which there is no need to have public access, whereas the later will be accessible via the window object, i.e com.scibuff.dev.ajax.

Now let’s take the code from the version 1 Javascpript file and place it into the newly created structure

		tmp.setAjaxLink = function( element, clickEvent ){
			clickEvent.preventDefault();
			var url = element.attr("href");
			tmp.loadPageContent( url, "#content" );
		};
 
		tmp.loadPageContent = function( url, destination ){
			data["page"] = url;
			$( destination ).load( url, { 'ajax': 'true' }, function(){
				$( destination + ' a.ajax-link').click( function( event ) {
					tmp.setAjaxLink( $(this), event );
				});
			});
		}

Since the tmp object is used to encapsulate internal functions we will need a point of entry from outside of the namespace. We use the pub object and adding the following few lines of code:

		pub.init = function(){
			$('a.ajax-link').click( function( event ) {
				tmp.setAjaxLink( $(this), event );
			});
		}

Now we simply call the newly created function once the DOM object is fully loaded like so:

$(document).ready(function () {
	com.scibuff.dev.ajax.init();
});

From user experience perspective one of the most important features missing in the first version was the ability to bookmark a specific page (and share it on social network sites). This functionality can be quite easily achieved using the URL hash to store necessary data.

We basically need two functions, one to parse the hash string into meaningful data object and one to take a data object and format it into a hash string.

	tmp.HASH_VARS_SEPARATOR = ';'
	/**
	 * Parses the url hash string into a vars (JSON) object
	 */
	tmp.getHashVars = function(){
		var url = window.location.href;
		if ( url.match('#') ){
			var hash = url.split('#')[1];
			var hashes = hash.split( tmp.HASH_VARS_SEPARATOR );
			var data = {};
			for ( var i in hashes ){
				if ( hashes[i] != "" ){
					var tuple = hashes[i].split("=");
					data[ tuple[0] ] = unescape(tuple[1]);
				}
			}
			return data;
		}
		return {};
	}
 
	/**
	 * Sets the url hash string using the values from the vars object
	 * @param {Object} vars
	 */
	tmp.setHashVars = function( vars ){
 
		var url = window.location.href;
		if ( url.match('#') ){ url = url.split('#')[0]; }
 
		var hash = "";
		for ( var key in vars ){
			if ( vars[key] ) {
				hash += key + "=" + escape(vars[key]) + tmp.HASH_VARS_SEPARATOR;
			}
		}
		window.location = url + "#" + hash;
	}

Now we can incorporate these two new functions into the existing code. In the tmp.loadPageContent function, add

	var data = tmp.getHashVars();
	data[ pub.PAGE_VAR_NAME ] = url;
	tmp.setHashVars( data );

to change the URL whenever a new ajax request is made. Similarly, in the pub.init function we add

	var data = tmp.getHashVars();
	if ( data["page"] != "" ) {
		tmp.loadPageContent( data[ pub.PAGE_VAR_NAME ], "#content" );
	}

The load the page content from the page specified in the URL hash data when the user first arrives at our site. These few extra lines of code is all that is needed to give users the ability to deep-link and bookmark any page accessible from the Ajax Navigation.

Now we have functionality for updating the URL hash with every AJAX request and loading page content based on the data present in the hash. To finish off this piece of functionality, let’s enabled navigation using browser’s Back and Forward buttons as well as direct input to the browser’s address bar.

We need to create a process to listen to changes in the address bar URL. Unfortunately, browsers do not provide in-built events for the URL change, but we can easily mimic that functionality with a few lines of custom code using jQuery’s trigger and bind methods.

	tmp.URL_CHECK_INTERVAL = 100;
	tmp.internalUrlChange = false;
	tmp.oldURL = window.location.href;
 
	/**
	 * Checks if the window url has changed and if so,
	 * triggers a "change" events
	 */
	tmp.checkURL = function(){
		if ( tmp.oldURL != window.location.href ){
			tmp.oldURL = window.location.href;
			$( window.location ).trigger(
				pub.URL_CHANGED, { url : window.location.href }
			);
		}
	}
 
	/**
	 * This function is executed every time the browser's url
	 * is changed
	 * @param {Object} event
	 * @param {Object} data
	 */
	tmp.urlChangeHandler = function( event, data ){
		if ( !tmp.internalUrlChange ){
			var data = tmp.getHashVars();
			if ( data[ pub.PAGE_VAR_NAME ] != "" ) {
				tmp.loadPageContent( data[ pub.PAGE_VAR_NAME ], "#content" );
			}				
 
		}
		tmp.internalUrlChange = false;
	}
 
	pub.init = function(){
		...
		$( window.location ).bind( pub.URL_CHANGED, tmp.urlChangeHandler );
		setInterval( tmp.checkURL, tmp.URL_CHECK_INTERVAL );
		...
	}

The tmp.checkURL function will check the browser’s URL for changes every 0.1 second. Every time a change is detected, the tmp.urlChangeHandler function will validate the hash data and load the appropriate page content via tmp.loadPageContent. To prevent infinite loops (e.g. user clicks on a navigation link, which changes the hash triggering an AJAX request, which changes the hash, etc…) we need to add the tmp.internalUrlChange variable and set it to false whenever the URL change will be due to changing the hash internally (as opposed to input by the user).

Hopefully after these two tutorials, you can now create a fully functional unobtrusive AJAX navigation for your site(s). The next part of the series will tackle organization of the back-end code and error checking. Meanwhile, check out the demo of this tutorial and/or download the files used in this tutorial.

Be Sociable, Share!

Comments

3

Mike

Hey, nice post, very well written. You should blog more about this.

Unobtrusive Ajax Navigation – Version 3

[…] Last week we continued the tutorial on Unobtrusive AJAX Navigation by cleaning up our Javascript code as well as adding functionality which enabled users to bookmark individual pages and use the “Back” and “Forward” browser buttons for navigation. This time, we’ll focus on cleaning up the back-end scripts to make the solution easily maintainable and expandable. Check out the fully functional demo and/or download the files for this tutorial. […]

Ta Duc

Hello, nice to see this post, i google back to your page again, just notice your message. it could be nice if you can add something like “Email if there are any follow up comment” :).. Looking at your v2 and v3 :)

Leave your comment:
XHTML:You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong> . * required