Updated Plugin Code Format
I am going to try your plugin for an app I'm working on, but I did not like that it added so many things to the jQuery root namespace. The long, prefixed vars also made the code harder to read. So I reformatted the plugin to use a $.history namespace, and to use the recommended wrapper so it can use $ for jQuery internally. I'm passing this on.
There are no functional enhancements in these changes. But this is a syntax change, so existing code-calls would have to be changed, eg: $.historyInit() to $.history.init() This is actually a more standard format, so is very minor.
The code below uses my personal preference for the 'this' alias - an underscore (var _ = $.history). I find this makes component code very easy to read, but if you prefer the more common 'self' alias, just search & replace to change it:
- var _ = $.history ==> self = $.history
- _. ==> self.
Complete Code
(function ($) {
$.history = {
currentHash: ''
, length: 0
, lastLength: 0
, backStack: []
, forwardStack: []
, isFirst: true
, skipCheck: false // true = temporarily skip history checks
, callback: undefined // required parameter
, needIframe: $.browser.msie && ($.browser.version < 8 || document.documentMode < 8)
, init: function (callback) {
var _ = $.history;
var cur_hash = location.hash.replace(/\?.*$/, '');
_.currentHash = cur_hash;
_.callback = callback;
if (_.needIframe) {
// To stop the callback firing twice during initilization if no hash present
if (_.currentHash == '') {
_.currentHash = '#';
}
// add hidden iframe for IE
$("body").prepend('<iframe id="jQuery_history" style="display: none;" src="javascript:false;"></iframe>');
var ihistory = $("#jQuery_history")[0];
var iframe = ihistory.contentWindow.document;
iframe.open();
iframe.close();
iframe.location.hash = cur_hash;
}
else if ($.browser.safari) {
_.backStack.length = history.length;
_.lastLength = history.length;
}
if (cur_hash)
_.callback(cur_hash.replace(/^#/, ''));
setInterval(jQuery.history.check, 100);
}
, add: function (hash) {
var _ = $.history;
// This makes the looping function do something
_.backStack.push(hash);
_.forwardStack.length = 0; // clear forwardStack (true click occured)
_.isFirst = true;
}
, check: function () {
var _ = $.history;
if (_.needIframe) {
// On IE, check for location.hash of iframe
var ihistory = $("#jQuery_history")[0];
var iframe = ihistory.contentDocument || ihistory.contentWindow.document;
var cur_hash = iframe.location.hash.replace(/\?.*$/, '');
if (cur_hash != _.currentHash) {
location.hash = cur_hash;
_.currentHash = cur_hash;
_.callback(cur_hash.replace(/^#/, ''));
}
}
else if ($.browser.safari) {
if (_.lastLength == history.length && _.backStack.length > _.lastLength) {
_.backStack.shift();
}
if (!_.skipCheck) {
var historyDelta = history.length - _.backStack.length;
_.lastLength = history.length;
if (historyDelta != 0) { // back or forward button has been pushed
_.isFirst = false;
if (historyDelta < 0) { // back button has been pushed
// move items to forward stack
for (var i = 0; i < Math.abs(historyDelta); i++)
_.forwardStack.unshift(_.backStack.pop());
}
else { // forward button has been pushed
// move items to back stack
for (var i = 0; i < historyDelta; i++)
_.backStack.push(_.forwardStack.shift());
}
var cachedHash = _.backStack[_.backStack.length - 1];
if (cachedHash != undefined) {
_.currentHash = location.hash.replace(/\?.*$/, '');
_.callback(cachedHash);
}
}
else if (_.backStack[_.backStack.length - 1] == undefined && !_.isFirst) {
// back button has been pushed to beginning and URL already pointed to hash (e.g. a bookmark)
// document.URL doesn't change in Safari
_.callback(location.hash ? location.hash.replace(/^#/, '') : '');
_.isFirst = true;
}
}
}
else {
// otherwise, check for location.hash
var cur_hash = location.hash.replace(/\?.*$/, '');
if (cur_hash != _.currentHash) {
_.currentHash = cur_hash;
_.callback(cur_hash.replace(/^#/, ''));
}
}
}
, load: function (hash) {
hash = decodeURIComponent(hash.replace(/\?.*$/, ''));
var _ = $.history;
var new_hash;
if ($.browser.safari) {
new_hash = hash;
}
else {
new_hash = '#'+ hash;
location.hash = new_hash;
}
_.currentHash = new_hash;
if (_.needIframe) {
var iframe = $("#jQuery_history")[0].contentWindow.document;
iframe.open();
iframe.close();
iframe.location.hash = new_hash;
_.lastLength = history.length;
_.callback(hash);
}
else if ($.browser.safari) {
_.skipCheck = true;
// Manually keep track of the history values for Safari
_.add(hash);
// Wait a while before allowing checking so that Safari has time to update the "history" object
// correctly (otherwise the check loop would detect a false change in hash).
window.setTimeout(function(){jQuery.history.skipCheck = false;}, 200);
_.callback(hash);
// N.B. "location.hash=" must be the last line of code for Safari as execution stops afterwards.
// By explicitly using the "location.hash" command (instead of using a variable set to "location.hash") the
// URL in the browser and the "history" object are both updated correctly.
location.hash = new_hash;
}
else {
_.callback(hash);
}
}
};
})( jQuery );
Johan Steenkamp
Nice work Kevin. Many Thanks.
2010/01/05 05:52
bmigmyghv
MoPoKvEMluAYMS
2010/01/15 09:50
kulmysz
EvsHwswFcSldsAaz
2010/02/04 17:51
jciqicnt
PEQJNILVtdT
2010/02/24 05:10
apotmudlz
QSIsiCDKBzsJbzmaCLu
2010/03/07 07:54