279 lines
9.8 KiB
JavaScript
279 lines
9.8 KiB
JavaScript
/*
|
|
Copyright (c) 2004-2006, The Dojo Foundation
|
|
All Rights Reserved.
|
|
|
|
Licensed under the Academic Free License version 2.1 or above OR the
|
|
modified BSD license. For more information on Dojo licensing, see:
|
|
|
|
http://dojotoolkit.org/community/licensing.shtml
|
|
*/
|
|
|
|
dojo.provide("dojo.undo.browser");
|
|
dojo.require("dojo.io");
|
|
|
|
try{
|
|
if((!djConfig["preventBackButtonFix"])&&(!dojo.hostenv.post_load_)){
|
|
document.write("<iframe style='border: 0px; width: 1px; height: 1px; position: absolute; bottom: 0px; right: 0px; visibility: visible;' name='djhistory' id='djhistory' src='"+(dojo.hostenv.getBaseScriptUri()+'iframe_history.html')+"'></iframe>");
|
|
}
|
|
}catch(e){/* squelch */}
|
|
|
|
if(dojo.render.html.opera){
|
|
dojo.debug("Opera is not supported with dojo.undo.browser, so back/forward detection will not work.");
|
|
}
|
|
|
|
/* NOTES:
|
|
* Safari 1.2:
|
|
* back button "works" fine, however it's not possible to actually
|
|
* DETECT that you've moved backwards by inspecting window.location.
|
|
* Unless there is some other means of locating.
|
|
* FIXME: perhaps we can poll on history.length?
|
|
* Safari 2.0.3+ (and probably 1.3.2+):
|
|
* works fine, except when changeUrl is used. When changeUrl is used,
|
|
* Safari jumps all the way back to whatever page was shown before
|
|
* the page that uses dojo.undo.browser support.
|
|
* IE 5.5 SP2:
|
|
* back button behavior is macro. It does not move back to the
|
|
* previous hash value, but to the last full page load. This suggests
|
|
* that the iframe is the correct way to capture the back button in
|
|
* these cases.
|
|
* Don't test this page using local disk for MSIE. MSIE will not create
|
|
* a history list for iframe_history.html if served from a file: URL.
|
|
* The XML served back from the XHR tests will also not be properly
|
|
* created if served from local disk. Serve the test pages from a web
|
|
* server to test in that browser.
|
|
* IE 6.0:
|
|
* same behavior as IE 5.5 SP2
|
|
* Firefox 1.0:
|
|
* the back button will return us to the previous hash on the same
|
|
* page, thereby not requiring an iframe hack, although we do then
|
|
* need to run a timer to detect inter-page movement.
|
|
*/
|
|
dojo.undo.browser = {
|
|
initialHref: window.location.href,
|
|
initialHash: window.location.hash,
|
|
|
|
moveForward: false,
|
|
historyStack: [],
|
|
forwardStack: [],
|
|
historyIframe: null,
|
|
bookmarkAnchor: null,
|
|
locationTimer: null,
|
|
|
|
/**
|
|
* setInitialState sets the state object and back callback for the very first page that is loaded.
|
|
* It is recommended that you call this method as part of an event listener that is registered via
|
|
* dojo.addOnLoad().
|
|
*/
|
|
setInitialState: function(args){
|
|
this.initialState = {"url": this.initialHref, "kwArgs": args, "urlHash": this.initialHash};
|
|
},
|
|
|
|
//FIXME: Would like to support arbitrary back/forward jumps. Have to rework iframeLoaded among other things.
|
|
//FIXME: is there a slight race condition in moz using change URL with the timer check and when
|
|
// the hash gets set? I think I have seen a back/forward call in quick succession, but not consistent.
|
|
/**
|
|
* addToHistory takes one argument, and it is an object that defines the following functions:
|
|
* - To support getting back button notifications, the object argument should implement a
|
|
* function called either "back", "backButton", or "handle". The string "back" will be
|
|
* passed as the first and only argument to this callback.
|
|
* - To support getting forward button notifications, the object argument should implement a
|
|
* function called either "forward", "forwardButton", or "handle". The string "forward" will be
|
|
* passed as the first and only argument to this callback.
|
|
* - If you want the browser location string to change, define "changeUrl" on the object. If the
|
|
* value of "changeUrl" is true, then a unique number will be appended to the URL as a fragment
|
|
* identifier (http://some.domain.com/path#uniquenumber). If it is any other value that does
|
|
* not evaluate to false, that value will be used as the fragment identifier. For example,
|
|
* if changeUrl: 'page1', then the URL will look like: http://some.domain.com/path#page1
|
|
*
|
|
* Full example:
|
|
*
|
|
* dojo.undo.browser.addToHistory({
|
|
* back: function() { alert('back pressed'); },
|
|
* forward: function() { alert('forward pressed'); },
|
|
* changeUrl: true
|
|
* });
|
|
*/
|
|
addToHistory: function(args){
|
|
var hash = null;
|
|
if(!this.historyIframe){
|
|
this.historyIframe = window.frames["djhistory"];
|
|
}
|
|
if(!this.bookmarkAnchor){
|
|
this.bookmarkAnchor = document.createElement("a");
|
|
(document.body||document.getElementsByTagName("body")[0]).appendChild(this.bookmarkAnchor);
|
|
this.bookmarkAnchor.style.display = "none";
|
|
}
|
|
if((!args["changeUrl"])||(dojo.render.html.ie)){
|
|
var url = dojo.hostenv.getBaseScriptUri()+"iframe_history.html?"+(new Date()).getTime();
|
|
this.moveForward = true;
|
|
dojo.io.setIFrameSrc(this.historyIframe, url, false);
|
|
}
|
|
if(args["changeUrl"]){
|
|
this.changingUrl = true;
|
|
hash = "#"+ ((args["changeUrl"]!==true) ? args["changeUrl"] : (new Date()).getTime());
|
|
setTimeout("window.location.href = '"+hash+"'; dojo.undo.browser.changingUrl = false;", 1);
|
|
this.bookmarkAnchor.href = hash;
|
|
|
|
if(dojo.render.html.ie){
|
|
var oldCB = args["back"]||args["backButton"]||args["handle"];
|
|
|
|
//The function takes handleName as a parameter, in case the
|
|
//callback we are overriding was "handle". In that case,
|
|
//we will need to pass the handle name to handle.
|
|
var tcb = function(handleName){
|
|
if(window.location.hash != ""){
|
|
setTimeout("window.location.href = '"+hash+"';", 1);
|
|
}
|
|
//Use apply to set "this" to args, and to try to avoid memory leaks.
|
|
oldCB.apply(this, [handleName]);
|
|
}
|
|
|
|
//Set interceptor function in the right place.
|
|
if(args["back"]){
|
|
args.back = tcb;
|
|
}else if(args["backButton"]){
|
|
args.backButton = tcb;
|
|
}else if(args["handle"]){
|
|
args.handle = tcb;
|
|
}
|
|
|
|
//If addToHistory is called, then that means we prune the
|
|
//forward stack -- the user went back, then wanted to
|
|
//start a new forward path.
|
|
this.forwardStack = [];
|
|
var oldFW = args["forward"]||args["forwardButton"]||args["handle"];
|
|
|
|
//The function takes handleName as a parameter, in case the
|
|
//callback we are overriding was "handle". In that case,
|
|
//we will need to pass the handle name to handle.
|
|
var tfw = function(handleName){
|
|
if(window.location.hash != ""){
|
|
window.location.href = hash;
|
|
}
|
|
if(oldFW){ // we might not actually have one
|
|
//Use apply to set "this" to args, and to try to avoid memory leaks.
|
|
oldFW.apply(this, [handleName]);
|
|
}
|
|
}
|
|
|
|
//Set interceptor function in the right place.
|
|
if(args["forward"]){
|
|
args.forward = tfw;
|
|
}else if(args["forwardButton"]){
|
|
args.forwardButton = tfw;
|
|
}else if(args["handle"]){
|
|
args.handle = tfw;
|
|
}
|
|
|
|
}else if(dojo.render.html.moz){
|
|
// start the timer
|
|
if(!this.locationTimer){
|
|
this.locationTimer = setInterval("dojo.undo.browser.checkLocation();", 200);
|
|
}
|
|
}
|
|
}
|
|
|
|
this.historyStack.push({"url": url, "kwArgs": args, "urlHash": hash});
|
|
},
|
|
|
|
checkLocation: function(){
|
|
if (!this.changingUrl){
|
|
var hsl = this.historyStack.length;
|
|
|
|
if((window.location.hash == this.initialHash||window.location.href == this.initialHref)&&(hsl == 1)){
|
|
// FIXME: could this ever be a forward button?
|
|
// we can't clear it because we still need to check for forwards. Ugg.
|
|
// clearInterval(this.locationTimer);
|
|
this.handleBackButton();
|
|
return;
|
|
}
|
|
// first check to see if we could have gone forward. We always halt on
|
|
// a no-hash item.
|
|
if(this.forwardStack.length > 0){
|
|
if(this.forwardStack[this.forwardStack.length-1].urlHash == window.location.hash){
|
|
this.handleForwardButton();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// ok, that didn't work, try someplace back in the history stack
|
|
if((hsl >= 2)&&(this.historyStack[hsl-2])){
|
|
if(this.historyStack[hsl-2].urlHash==window.location.hash){
|
|
this.handleBackButton();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
iframeLoaded: function(evt, ifrLoc){
|
|
if(!dojo.render.html.opera){
|
|
var query = this._getUrlQuery(ifrLoc.href);
|
|
if(query == null){
|
|
// alert("iframeLoaded");
|
|
// we hit the end of the history, so we should go back
|
|
if(this.historyStack.length == 1){
|
|
this.handleBackButton();
|
|
}
|
|
return;
|
|
}
|
|
if(this.moveForward){
|
|
// we were expecting it, so it's not either a forward or backward movement
|
|
this.moveForward = false;
|
|
return;
|
|
}
|
|
|
|
//Check the back stack first, since it is more likely.
|
|
//Note that only one step back or forward is supported.
|
|
if(this.historyStack.length >= 2 && query == this._getUrlQuery(this.historyStack[this.historyStack.length-2].url)){
|
|
this.handleBackButton();
|
|
}
|
|
else if(this.forwardStack.length > 0 && query == this._getUrlQuery(this.forwardStack[this.forwardStack.length-1].url)){
|
|
this.handleForwardButton();
|
|
}
|
|
}
|
|
},
|
|
|
|
handleBackButton: function(){
|
|
//The "current" page is always at the top of the history stack.
|
|
var current = this.historyStack.pop();
|
|
if(!current){ return; }
|
|
var last = this.historyStack[this.historyStack.length-1];
|
|
if(!last && this.historyStack.length == 0){
|
|
last = this.initialState;
|
|
}
|
|
if (last){
|
|
if(last.kwArgs["back"]){
|
|
last.kwArgs["back"]();
|
|
}else if(last.kwArgs["backButton"]){
|
|
last.kwArgs["backButton"]();
|
|
}else if(last.kwArgs["handle"]){
|
|
last.kwArgs.handle("back");
|
|
}
|
|
}
|
|
this.forwardStack.push(current);
|
|
},
|
|
|
|
handleForwardButton: function(){
|
|
var last = this.forwardStack.pop();
|
|
if(!last){ return; }
|
|
if(last.kwArgs["forward"]){
|
|
last.kwArgs.forward();
|
|
}else if(last.kwArgs["forwardButton"]){
|
|
last.kwArgs.forwardButton();
|
|
}else if(last.kwArgs["handle"]){
|
|
last.kwArgs.handle("forward");
|
|
}
|
|
this.historyStack.push(last);
|
|
},
|
|
|
|
_getUrlQuery: function(url){
|
|
var segments = url.split("?");
|
|
if (segments.length < 2){
|
|
return null;
|
|
}
|
|
else{
|
|
return segments[1];
|
|
}
|
|
}
|
|
}
|