Skip to content

Instantly share code, notes, and snippets.

@balupton
Last active July 13, 2024 17:32
Show Gist options
  • Save balupton/854622 to your computer and use it in GitHub Desktop.
Save balupton/854622 to your computer and use it in GitHub Desktop.
Ajaxify a Website with the HTML5 History API using History.js, jQuery and ScrollTo
// History.js It!
// v1.0.1 - 30 September, 2012
// https://gist.github.com/854622
(function(window,undefined){
// Prepare our Variables
var
History = window.History,
$ = window.jQuery,
document = window.document;
// Check to see if History.js is enabled for our Browser
if ( !History.enabled ) {
return false;
}
// Wait for Document
$(function(){
// Prepare Variables
var
/* Application Specific Variables */
contentSelector = '#content,article:first,.article:first,.post:first',
$content = $(contentSelector).filter(':first'),
contentNode = $content.get(0),
$menu = $('#menu,#nav,nav:first,.nav:first').filter(':first'),
activeClass = 'active selected current youarehere',
activeSelector = '.active,.selected,.current,.youarehere',
menuChildrenSelector = '> li,> ul > li',
completedEventName = 'statechangecomplete',
/* Application Generic Variables */
$window = $(window),
$body = $(document.body),
rootUrl = History.getRootUrl(),
scrollOptions = {
duration: 800,
easing:'swing'
};
// Ensure Content
if ( $content.length === 0 ) {
$content = $body;
}
// Internal Helper
$.expr[':'].internal = function(obj, index, meta, stack){
// Prepare
var
$this = $(obj),
url = $this.attr('href')||'',
isInternalLink;
// Check link
isInternalLink = url.substring(0,rootUrl.length) === rootUrl || url.indexOf(':') === -1;
// Ignore or Keep
return isInternalLink;
};
// HTML Helper
var documentHtml = function(html){
// Prepare
var result = String(html)
.replace(/<\!DOCTYPE[^>]*>/i, '')
.replace(/<(html|head|body|title|meta|script)([\s\>])/gi,'<div class="document-$1"$2')
.replace(/<\/(html|head|body|title|meta|script)\>/gi,'</div>')
;
// Return
return result;
};
// Ajaxify Helper
$.fn.ajaxify = function(){
// Prepare
var $this = $(this);
// Ajaxify
$this.find('a:internal:not(.no-ajaxy)').click(function(event){
// Prepare
var
$this = $(this),
url = $this.attr('href'),
title = $this.attr('title')||null;
// Continue as normal for cmd clicks etc
if ( event.which == 2 || event.metaKey ) { return true; }
// Ajaxify this link
History.pushState(null,title,url);
event.preventDefault();
return false;
});
// Chain
return $this;
};
// Ajaxify our Internal Links
$body.ajaxify();
// Hook into State Changes
$window.bind('statechange',function(){
// Prepare Variables
var
State = History.getState(),
url = State.url,
relativeUrl = url.replace(rootUrl,'');
// Set Loading
$body.addClass('loading');
// Start Fade Out
// Animating to opacity to 0 still keeps the element's height intact
// Which prevents that annoying pop bang issue when loading in new content
$content.animate({opacity:0},800);
// Ajax Request the Traditional Page
$.ajax({
url: url,
success: function(data, textStatus, jqXHR){
// Prepare
var
$data = $(documentHtml(data)),
$dataBody = $data.find('.document-body:first'),
$dataContent = $dataBody.find(contentSelector).filter(':first'),
$menuChildren, contentHtml, $scripts;
// Fetch the scripts
$scripts = $dataContent.find('.document-script');
if ( $scripts.length ) {
$scripts.detach();
}
// Fetch the content
contentHtml = $dataContent.html()||$data.html();
if ( !contentHtml ) {
document.location.href = url;
return false;
}
// Update the menu
$menuChildren = $menu.find(menuChildrenSelector);
$menuChildren.filter(activeSelector).removeClass(activeClass);
$menuChildren = $menuChildren.has('a[href^="'+relativeUrl+'"],a[href^="/'+relativeUrl+'"],a[href^="'+url+'"]');
if ( $menuChildren.length === 1 ) { $menuChildren.addClass(activeClass); }
// Update the content
$content.stop(true,true);
$content.html(contentHtml).ajaxify().css('opacity',100).show(); /* you could fade in here if you'd like */
// Update the title
document.title = $data.find('.document-title:first').text();
try {
document.getElementsByTagName('title')[0].innerHTML = document.title.replace('<','&lt;').replace('>','&gt;').replace(' & ',' &amp; ');
}
catch ( Exception ) { }
// Add the scripts
$scripts.each(function(){
var $script = $(this), scriptText = $script.text(), scriptNode = document.createElement('script');
scriptNode.appendChild(document.createTextNode(scriptText));
contentNode.appendChild(scriptNode);
});
// Complete the change
if ( $body.ScrollTo||false ) { $body.ScrollTo(scrollOptions); } /* http://balupton.com/projects/jquery-scrollto */
$body.removeClass('loading');
$window.trigger(completedEventName);
// Inform Google Analytics of the change
if ( typeof window._gaq !== 'undefined' ) {
window._gaq.push(['_trackPageview', relativeUrl]);
}
// Inform ReInvigorate of a state change
if ( typeof window.reinvigorate !== 'undefined' && typeof window.reinvigorate.ajax_track !== 'undefined' ) {
reinvigorate.ajax_track(url);
// ^ we use the full url here as that is what reinvigorate supports
}
},
error: function(jqXHR, textStatus, errorThrown){
document.location.href = url;
return false;
}
}); // end ajax
}); // end onStateChange
}); // end onDomLoad
})(window); // end closure
@genereddick
Copy link

Error under jquery 1.9.0 is on $data = $(documentHtml(data)), and, after failing to figure out the selector, ends up here:
Sizzle.error = function( msg ) {
throw new Error( "Syntax error, unrecognized expression: " + msg );
};

@genereddick
Copy link

In the jQuery 1.9 change log there is this: http://jquery.com/upgrade-guide/1.9/#jquery-htmlstring-versus-jquery-selectorstring

In my case, the response returned from documentHtml(data) doesn't start with a < (in my case it is a conditional for modernizr) is rejected by the new jquery selector parser.

changing the var documentHtml = function(html) to return $.parseHtml seems to work for me, though I haven't explored any follow on effects or issues.

// Return
return $.parseHTML(result);

@daslicht
Copy link

You can also use Davis.js to Ajaxify/hijax you webpage:
see: http://78.47.126.11:3000

@supermensa
Copy link

As far as I can see; the script makes an ajax call for the browser back-button and previously clicked links. Can you update this gist to show how to ensure that saved states/pages are retrieved from history instead of making a new ajax request?

@alexlangberg
Copy link

The fix from genereddick works but ScrollTo is still broken with new versions of jQuery, since $.browser has been deprecated. I've tried including the old code for $.browser but it only seems to be working in Firefox.

@PepiSCZ
Copy link

PepiSCZ commented Mar 9, 2013

Why already loaded javascript is not applied to new content in content div?

@Zeokat
Copy link

Zeokat commented Mar 1, 2014

Zeokat you saved my day! thanks. Nice piece of code.

@Shobby
Copy link

Shobby commented Nov 9, 2016

I am facing an issue. I want to run jaquery on the loaded page but it not working because the page is not loaded and jquery not hit i want to show an alert when the page is loaded but not work:
jQuery(document).ready(function() { alert("hello world"); )};

i put this code in footer file but not working

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment