MooTools History Plugin

By  on  

One of the reasons I love AJAX technology so much is because it allows us to avoid unnecessary page loads.  Why download the header, footer, and other static data multiple times if that specific data never changes?  It's a waste of time, processing, and bandwidth.  Unfortunately, at this point in the web, constant refreshes are the norm -- but they don't have to be.  Christoph Pojer, a MooTools Core Developer, has added History to his PojerTools PowerTools library.  History replaces traditional same-site URL loading by providing a method to catch link clicks, load page content via AJAX (Mootools' Request.HTML class), modify the document's location object to keep "history" records, and re-evaluate content links to allow developers to create a fast, efficient one-page website.

The tradition method of dynamic history/"back button" management has always been hash-based JavaScript technology.  Newer technology, including HTML5's window.onpopstate and history.pushState methods, allow for more reliable methods for managing history.  MooTools' History plugin supports modern and legacy methods for history management.  Let me show you how to quickly implement the MooTools History plugin.

HTML Structure

The History plugin doesn't require any HTML structure adjustments but at least one designated content should be identified; you can, of course, have as many content areas as you like, but you'll most likely need multiple AJAX requests to retrieve their content, unless you use a Request.JSON request to retrieve content for multiple areas of the page. For this simple demo, we'll define a header, footer, and menu, and content area:

<div id="body">
	
	<!-- header -->
	<header>
		<a href="/" data-noxhr>David Walsh Blog</a> 
		<div>MooTools History Plugin Demo</div>
		<div>This is a simple example of the MooTools History plugin created by Christoph Pojer</div>
		
	</header>
		
	<!-- menu -->
	<ul id="demoMenu">
		<li><a href="mootools-history.php">Home</a></li>
		<li><a href="mootools-history-david.php">About David Walsh</a></li>
		<li><a href="mootools-history-mootools.php">About MooTools</a></li>
		<li><a href="mootools-history-christoph">About Christoph Pojer</a></li>
	</ul>
	
	<!-- content area -->
	<article id="contentHolder">
		
		<!-- initial page content goes here -->
		
	</article>
	
	
	<!-- footer -->
	<footer>
	
	</footer>
</div>

The content area is the only area which will have its content change. The page should load as usual

The MooTools JavaScript

Assuming that the MooTools History plugin has been included in the page, there are a few functions that should be created upon domready.  The first is a method which will perform the request for content when a link is clicked:

// Content holder (all content placed within this element)
var contentHolder = document.id("contentHolder");

// Create a Request object which will be reused when links are clicked
var request = new Request.HTML({
	onSuccess: function(nodeTree,elements,html) {
		// Set the content into the content holder
		contentHolder.set("html",html);
		// Execute directions that should be executed whenever a page changes
		onPageUpdate();
	}
});

// Create a function that loads the page content
var loadPage = function(url) {
	// Make a HTML request to get the content for this page
	request.send({ url: url });
};

The next step is creating a method (which is theoretically option, but you'll usually want to do something once content has loaded) which will execute every time content is received:

// Function that will execute whenever a page gets changed
var onPageUpdate = function() {
	
	// Do whatever you'd like here!  
	
	// Possibly manually record a Google Analytics page view?
	
};

History doesn't request that you do anything when content is received, but you'll likely want to do something. Why manually record a page view within Google Analytics?

This next piece is important in turning links to static pages into AJAX-ified History triggers. Just one big Element.Delegation event delegation call will do the work for not just the initial page load, but every History AJAX load after that:

// The listener that manages all clicks
var listener = function(evt){
	evt.preventDefault(); // Prevent following the URL
	History.push(this.get('href')); // Push the new URL into History
};

// Add event delegation to add clicks.  Both of these work:
//document.body.addEvent("click:relay(a:not([href=#]):not([href^=http://]):not([data-noxhr]))",listener);
document.body.addEvent("click:relay(a:not([href=#],[href^=http://],[data-noxhr]))",listener);

When any same-site, non-hashed link is clicked, the listener method stops the event and pushes the new URL into History, changing the address bar and managing back/forward button click.

A back function is also created so that we can provide a "back" link and a "forward" link to travel back and forward in page history, if we choose to use it:

// Listener for the "Back" link
var back = function(evt){
	evt.preventDefault();
	History.back(); // Go back
};

// Listener for the "Forward" link
var forward = function(evt){
	evt.preventDefault();
	History.forward(); // Go back
};

// Add to links
document.id("backLink").addEvent("click",back);
document.id("forwardLink").addEvent("click",forward);

The next step is adding a change event to History itself to run our loadPage function when the page URL changes:

// When the history changes, update the content 
History.addEvent('change',loadPage);

If the client doesn't support the history.pushState method, the History plugin evaluates the hash and loads the page as necessary:

// Handle the initial load of the page if the browser does not support pushState, check if the hash is set
if(!History.hasPushState()) {
	// Check if there is a hash
	var hash = document.location.hash.substr(1);
	if (!hash) return;

	// If the hash equals the current page, don't do anything
	var path = document.location.pathname.split('/');
	path = path[path.length - 1];
	if (hash == path) return;

	// Load the page specified in the hash
	loadPage(hash);
}

Lastly, running the onPageUpdate upon domready load doesn't hurt since events are only added once within onPageUpdate:

// Update the page
onPageUpdate();

Now the page is ready to support History-based, AJAX-driven content swapping.  Thanks to the onPageUpdate function, links are added to events as they come in so that even AJAX-retrieved content can be managed with History.

Tips and Strategies for Hash/History-Managed Websites

Plugins like Christoph's History masterpiece are very helpful in enriching the user experience but do require a bit of developer logic:

  • Use Event Delegation - Remember that with a History-style system, assigning events to elements directly may not be the best solution because those elements may be gone with the next link click. Using event delegation instead of traditional event assignments may save you a lot of trouble. Read my MooTools Element.Delegation post if you aren't familiar with event delegation.
  • Don't Assume JavaScript Support - Keep in mind that the client may not support JavaScript.  Search engines have added JavaScript support but it's important to use URLs that will work with both on a History-managed site and a JavaScript-less website.
  • Use AJAX Detection - MooTools provides an AJAX-specific header within the Request class called HTTP_X_REQUESTED_WITH.  Click here to learn how to use it to detect AJAX requests.  You will want to be able to detect AJAX so that those requests simply return the content and not the header and footer (etc.) with it. You could write a client-side script/regex to parse out the content but that's largely inefficient. My demo uses PHP to store the page content in variables as follows:

    // Load pages based on querystring
    $qstring = $_SERVER['QUERY_STRING'];
    if($qstring == 'home' || $qstring == '') {
    	$content.= '<h1>Welcome Home!</h1>';
    	$content.= '<p>History Management via popstate or hashchange. Replaces the URL of the page without a reload and falls back to Hashchange on older browsers.</p><p>This demo page aims to teach you how you can use Christoph Pojer\'s outstanding History widget to load only the content you need, dynamically and reliably.</p>';
    }
    elseif($qstring == 'about-david') {
    	$content.= '<h1>About David Walsh</h1>';
    	$content.= '<p>My name is David Walsh. I\'m a 27 year old Web Developer from Madison, Wisconsin. In the web world, I am:</p>
    	<ul>
    	<li>Founder and Lead Developer for Wynq Web Labs.</li>
    	<li>Software Engineer for SitePen.</li>
    	<li>Core Developer for the MooTools JavaScript framework.</li>
    	<li>Co-Founder of Script & Style, a website aimed at making web developers and designers better.</li>
    	</ul>
    	<p>I don\'t design the websites - I make them work.</p>
    	<p>I am also an admirer of the great <a href="?about-christoph">Christoph Pojer!</a>.</p>';
    }
    // and more....
    // Page not found
    else {
    	$content.= '<h1>Page Not Found</h1>';
    	$content.= '<p>The page you were attempting to find could not be found.</p>';
    }
    
    // If request was via AJAX, push it out.
    if(!empty($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
    	echo $content;
    	exit();
    }
    
    Obviously your content management system would be pulling content from a database or other static files, but you get the point -- load content before any page output, sniff for AJAX, and push content out accordingly. If it's not an AJAX request, push that content into the content area's HTML via traditional methods.

These tips should set you up well to use a History-based system.  Remember that JavaScript is meant to enhance -- keep in mind that your user (or search engine bot) may not support JavaScript, so be sure to test your website thoroughly!

Give the example hell.  Click from page to page, use the back button, refresh the page, etc.  History is rock solid!

Thanks to Christoph Pojer for his outstanding MooTools History plugin.  Many History-style plugins have existed but the browsers haven't been as feature-rich as they are now.  If you have any suggestions, tips, or experiences to share about creating hash-based websites, please share them.

Recent Features

  • By
    CSS vs. JS Animation: Which is Faster?

    How is it possible that JavaScript-based animation has secretly always been as fast — or faster — than CSS transitions? And, how is it possible that Adobe and Google consistently release media-rich mobile sites that rival the performance of native apps? This article serves as a point-by-point...

  • By
    Send Text Messages with PHP

    Kids these days, I tell ya.  All they care about is the technology.  The video games.  The bottled water.  Oh, and the texting, always the texting.  Back in my day, all we had was...OK, I had all of these things too.  But I still don't get...

Incredible Demos

Discussion

  1. This is pretty cool. However, I think it should be used with some caution. Gawker has shown us that pure AJAX sites can very easily go awry.

    • I did think of Gawker when putting this together and I agree. Their site isn’t bad but it’s … different.

    • The idea is that you only actually request real URLs that will always work if you request them manually. The whole Gawker thing could have been avoided *very easily*, but it seems the devs didn’t care enough.

  2. Alex

    Has anybody tried HashNav??

    http://mootools.net/forge/p/hashnav

  3. Michael

    This looks awesome, but outside of my abilities at the moment, it seems.

  4. This looks awesome, I’ll have to try it out later.
    If you’re using jQuery, a very similar plugin exists called pjax (what github uses for all their fancy page transitions): https://github.com/defunkt/jquery-pjax

  5. great plugin chris!

  6. Aldo

    Do the same is easily done with DOJO?
    I’d go with dojo.back, but it doesn’t seem to handle pushstate.

    • AFAIK, dojo.hash has implemented pushtate

  7. Andy

    Can someone speak to the cross browser compatibility of this? Will it work in IE6?

  8. false

    @Alex : Not this plugin, but i’ve tried this one : http://davidwalsh.name/mootools-history

    and it works, i tested it here : http://www.playfun.fr

  9. Why not use the HTML5 History API so you can modify the URL directly instead of using a hash which inherits some pretty ugly problems. There are things like History.js that fixes the cross-browser bugs with the HTML5 History API, has support for mootools, and for HTML4 browsers has an optional hash fallback: https://github.com/balupton/History.js/

    For ready why hashes are not ideal: https://github.com/balupton/History.js/wiki/Intelligent-State-Handling

    • false

      This one is a really powerful technique but looks a little bit heavy (22ko for the mootools one) and i don’t like the generated url for IE<10, why they have a long ID in the adress bar instead an hash ? uggly :/

  10. I would really like to thank you for this wonderful plugin and tutorial. I’m Joomla developer and recently a client of mine want to put media player on his new website with uninterrupted music while visitors browse different pages. Also he want good SEO structure witch at the same time was nearly impossible.

    But then I discover your tutorial and it was solution out of the bright sky. Based on your tutorial I have develop fully featured history template framework for Joomla 1.6 that support all Joomla features. It amazingly satisfy all client needs and more. Again thank you and Christoph Pojer for this brilliant plugin and tutorial.

    If some one want to see this framework at action, it can be found at http://www.stateofmind.rs/

  11. I’ve created a similar effect using the jQuery history plugin. The main reason I needed this was to animate the page transition, this isn’t possible (from what I know) if the server reloads. Here’s the link: http://troop.ws Notice that if you click a link and then use the browsers back button, it will animate back to the previous page. Interested to hear everyone’s opinions!

  12. Chikrn

    Hi, great plugin ^^
    I have a question about this, how change title in browser, after LoadPage ?

    • false

      document.title = “your title”;

  13. Haja

    This plugin cant handle ajax pages that have different structure than the index :(

    Suppose that my the other-page.html (on the plugin Demo) loads only image+text, without the side navigation. If my first load hits other-page.html, nothing is load by ajax and i’ll not have my side bar…

    Is there a way to handle it ?

    Sorry if my english is not good coz i’m French.

    Thanks !

  14. Roark

    Hey David,

    I’ve succesfully implemented this in one of my projects, however I can’t seem to access any of the dom elements with scripts that appear after the html that it loads.
    (I set evalcripts to true in the request)

    i.e.

    //html that is requested.

    alert(document.id("somediv")); //this doesn't work it alerts undefined
    alert('this works'); //this works

    Not sure when the elements I load into the dom become accessible?
    Any help on this would be greatly appreciated!

  15. Gabriel

    Hi, and thank you for plugin and demo.
    A question: is there a way to know “how” the history is changing? Is possible to know if the user is going back or forward or his doing a refresh?

  16. Jeff

    Where can we download the source code of this to play around with?

  17. Slamms

    Hi, love the example. Im using .NET and am having an issue with page refresh, in your sample it works fine. When I do it “with aspx or ashx files” the whole thing refreshes to the aspx/ashx page. I know im missing something but cannot work it out. Can you explain the way you relate the URL’s to the single PHP document? And how it handles page refresh “f5”

    Hope that makes sense.

    Thanks in advance

  18. Arey

    Hy david, can you provide download link your demo. This fantastic…

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!