app/assets/javascripts/jquery.history.js
/**
* jQuery History Plugin (balupton edition) - Simple History Handler/Remote for Hash, State, Bookmarking, and Forward Back Buttons
* Copyright (C) 2008-2009 Benjamin Arthur Lupton
* http://www.balupton/projects/jquery_history/
*
* This file is part of jQuery History Plugin (balupton edition).
*
* jQuery History Plugin (balupton edition) is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* jQuery History Plugin (balupton edition) is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with jQuery History Plugin (balupton edition). If not, see <http://www.gnu.org/licenses/>.
*
* @name jqsmarty: jquery.history.js
* @package jQuery History Plugin (balupton edition)
* @version 1.0.1-final
* @date July 11, 2009
* @category jquery plugin
* @author Benjamin "balupton" Lupton {@link http://www.balupton.com}
* @copyright (c) 2008-2009 Benjamin Arthur Lupton {@link http://www.balupton.com}
* @license GNU Affero General Public License - {@link http://www.gnu.org/licenses/agpl.html}
* @example Visit {@link http://jquery.com/plugins/project/jquery_history_bal} for more information.
*
*
* I would like to take this space to thank the following projects, blogs, articles and people:
* - jQuery {@link http://jquery.com/}
* - jQuery UI History - Klaus Hartl {@link http://www.stilbuero.de/jquery/ui_history/}
* - Really Simple History - Brian Dillard and Brad Neuberg {@link http://code.google.com/p/reallysimplehistory/}
* - jQuery History Plugin - Taku Sano (Mikage Sawatari) {@link http://www.mikage.to/jquery/jquery_history.html}
* - jQuery History Remote Plugin - Klaus Hartl {@link http://stilbuero.de/jquery/history/}
* - Content With Style: Fixing the back button and enabling bookmarking for ajax apps - Mike Stenhouse {@link http://www.contentwithstyle.co.uk/Articles/38/fixing-the-back-button-and-enabling-bookmarking-for-ajax-apps}
* - Bookmarks and Back Buttons {@link http://ajax.howtosetup.info/options-and-efficiencies/bookmarks-and-back-buttons/}
* - Ajax: How to handle bookmarks and back buttons - Brad Neuberg {@link http://dev.aol.com/ajax-handling-bookmarks-and-back-button}
*
**
***
* CHANGELOG
**
* v1.0.1-final, July 11, 2009
* - Restructured a little bit
* - Documented
* - Cleaned go/request
*
* v1.0.0-final, June 19, 2009
* - Been stable for over a year now, pushing live.
*
* v0.1.0-dev, July 24, 2008
* - Initial Release
*
*/
// Start of our jQuery Plugin
(function($)
{ // Create our Plugin function, with $ as the argument (we pass the jQuery object over later)
// More info: http://docs.jquery.com/Plugins/Authoring#Custom_Alias
// Debug
if (typeof console === 'undefined') {
console = typeof window.console !== 'undefined' ? window.console : {};
}
console.log = console.log || function(){};
console.debug = console.debug || console.log;
console.warn = console.warn || console.log;
console.error = console.error || function(){var args = [];for (var i = 0; i < arguments.length; i++) { args.push(arguments[i]); } alert(args.join("\n")); };
console.trace = console.trace || console.log;
console.group = console.group || console.log;
console.groupEnd = console.groupEnd || console.log;
console.profile = console.profile || console.log;
console.profileEnd = console.profileEnd || console.log;
// Declare our class
$.History = {
// Our Plugin definition
// -----------------
// Options
options: {
debug: false
},
// -----------------
// Variables
state: '',
$window: null,
$iframe: null,
handlers: {
generic: [],
specific: {}
},
// --------------------------------------------------
// Functions
/**
* Format a hash into a proper state
* @param {String} hash
*/
format: function ( hash ) {
// Format the hash
hash = hash.replace(/^.+?#/g,'').replace(/^#?\/?|\/?$/g, '');
// Return the hash
return hash;
},
/**
* Get the current state of the application
*/
getState: function ( ) {
var History = $.History;
// Get the current state
return History.state;
},
/**
* Set the current state of the application
* @param {String} hash
*/
setState: function ( state ) {
var History = $.History;
// Format the state
state = History.format(state)
// Apply the state
History.state = state;
// Return the state
return History.state;
},
/**
* Get the current hash of the browser
*/
getHash: function ( ) {
var History = $.History;
// Get hash
var hash = window.location.hash || location.hash;
// Format the hash
hash = History.format(hash);
// Return the hash
return hash;
},
/**
* Set the current hash of the browser
* @param {String} hash
*/
setHash: function ( hash ) {
var History = $.History;
// Prepare hash
hash = $.History.format(hash);
hash = hash.replace(/^\/?|\/?(\?)|\/?$/g, '/$1');
// Write hash
if ( typeof window.location.hash !== 'undefined' ) {
window.location.hash = hash;
} else {
location.hash = hash;
}
// Update IE<8 History
if ( $.browser.msie && parseInt($.browser.version, 10) < 8 )
{ // We are IE<8
$.History.$iframe.contentWindow.document.open();
$.History.$iframe.contentWindow.document.close();
$.History.$iframe.contentWindow.document.location.hash = state;
}
},
/**
* Go to the specific state - does not force a history entry like setHash
* @param {String} state
*/
go: function ( state ) {
var History = $.History;
// Format the state
state = History.format(state);
// Get the current hash
var hash = History.getHash();
// Are they different?
if ( hash !== state ) {
// Yes, create a history entry
History.setHash(state);
// Wait for hashchange
} else {
// No change, but update state and fire
History.setState(state);
History.trigger();
}
// Done
return true;
},
/**
* Fired when the hash is changed, either automaticly or manually
* @param {Event} e
*/
hashchange: function ( e ) {
var History = $.History;
// Debug
if ( History.options.debug ) {
console.debug('History.hashchange', this, arguments);
}
// Get Hash
var hash = History.getHash();
var state = History.getState();
// Prevent IE 8 from fireing this twice
if ( (!History.$iframe && state === hash) || (History.$iframe && History.hash === History.$iframe.contentWindow.document.location.hash) ) {
// For some reason this works
return false;
}
// Check
if ( state === hash ) {
// Nothing to do
return false;
}
// Update the state with the new hash
History.setState(hash);
// Fire the handler
History.trigger();
// All done
return true;
},
/**
* Bind a handler to a hash
* @param {Object} state
* @param {Object} handler
*/
bind: function ( state, handler ) {
var History = $.History;
//
if ( handler ) {
// We have a state specific handler
// Prepare
if ( typeof History.handlers.specific[state] === 'undefined' )
{ // Make it an array
History.handlers.specific[state] = [];
}
// Push new handler
History.handlers.specific[state].push(handler);
}
else {
// We have a generic handler
handler = state;
History.handlers.generic.push(handler);
}
// Done
return true;
},
/**
* Trigger a handler for a state
* @param {String} state
*/
trigger: function ( state ) {
var History = $.History;
// Prepare
if ( typeof state === 'undefined' ) {
// Use current
state = History.getState();
}
var i, n, handler, list;
// Fire specific
if ( typeof History.handlers.specific[state] !== 'undefined' ) {
// We have specific handlers
list = History.handlers.specific[state];
for ( i = 0, n = list.length; i < n; ++i ) {
// Fire the specific handler
handler = list[i];
handler(state);
}
}
// Fire generics
list = History.handlers.generic;
for ( i = 0, n = list.length; i < n; ++i ) {
// Fire the specific handler
handler = list[i];
handler(state);
}
// Done
return true;
},
// --------------------------------------------------
// Constructors
/**
* Construct our application
*/
construct: function ( ) {
var History = $.History;
// Modify the document
$(document).ready(function() {
// Prepare the document
History.domReady();
});
// Done
return true;
},
/**
* Configure our application
* @param {Object} options
*/
configure: function ( options ) {
var History = $.History;
// Set options
History.options = $.extend(History.options, options);
// Done
return true;
},
domReadied: false,
domReady: function ( ) {
var History = $.History;
// Runonce
if ( History.domRedied ) {
return;
}
History.domRedied = true;
// Define window
History.$window = $(window);
// Apply the hashchange function
History.$window.bind('hashchange', this.hashchange);
// Force hashchange support for all browsers
setTimeout(History.hashchangeLoader, 200);
// All done
return true;
},
/**
* Enable hashchange for all browsers
*/
hashchangeLoader: function () {
var History = $.History;
// More is needed for non IE8 browsers
if ( !($.browser.msie && parseInt($.browser.version) >= 8) ) {
// We are not IE8
// State our checker function, it is used to constantly check the location to detect a change
var checker;
// Handle depending on the browser
if ( $.browser.msie ) {
// We are still IE
// Append and $iframe to the document, as $iframes are required for back and forward
// Create a hidden $iframe for hash change tracking
History.$iframe = $('<iframe id="jquery-history-iframe" style="display: none;"></$iframe>').prependTo(document.body)[0];
// Create initial history entry
History.$iframe.contentWindow.document.open();
History.$iframe.contentWindow.document.close();
// Check for initial state
var hash = History.getHash();
if ( hash ) {
// Apply it to the iframe
History.$iframe.contentWindow.document.location.hash = hash;
}
// Define the checker function (for bookmarks)
checker = function ( ) {
var iframeHash = History.format(History.$iframe.contentWindow.document.location.hash);
if ( History.getState() !== iframeHash ) {
// Back Button Change
History.setHash(History.$iframe.contentWindow.document.location.hash);
}
var hash = History.getHash();
if ( History.getState() !== hash ) {
// The has has changed
History.go(hash);
}
};
}
else {
// We are not IE
// Define the checker function (for bookmarks, back, forward)
checker = function ( ) {
var hash = History.getHash();
if ( History.getState() !== hash ) {
// The has has changed
History.go(hash);
}
};
}
// Apply the checker function
setInterval(checker, 200);
}
else {
// We are IE8
var hash = History.getHash();
if (hash) {
History.$window.trigger('hashchange');
}
}
// Done
return true;
}
}; // We have finished extending/defining our Plugin
// --------------------------------------------------
// Finish up
// Instantiate
$.History.construct();
// Finished definition
})(jQuery); // We are done with our plugin, so lets call it with jQuery as the argument