New file |
0,0 → 1,389 |
if(!dojo._hasResource["dojo.back"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. |
dojo._hasResource["dojo.back"] = true; |
dojo.provide("dojo.back"); |
|
(function() { |
|
var back = dojo.back; |
|
// everyone deals with encoding the hash slightly differently |
|
function getHash(){ |
var h = window.location.hash; |
if(h.charAt(0) == "#") { h = h.substring(1); } |
return dojo.isMozilla ? h : decodeURIComponent(h); |
} |
|
function setHash(h){ |
if(!h) { h = "" }; |
window.location.hash = encodeURIComponent(h); |
historyCounter = history.length; |
} |
|
// if we're in the test for these methods, expose them on dojo.back. ok'd with alex. |
if(dojo.exists("tests.back-hash")){ |
back.getHash = getHash; |
back.setHash = setHash; |
} |
|
var initialHref = (typeof(window) !== "undefined") ? window.location.href : ""; |
var initialHash = (typeof(window) !== "undefined") ? getHash() : ""; |
var initialState = null; |
|
var locationTimer = null; |
var bookmarkAnchor = null; |
var historyIframe = null; |
var forwardStack = []; |
var historyStack = []; |
var moveForward = false; |
var changingUrl = false; |
var historyCounter; |
|
function handleBackButton(){ |
//summary: private method. Do not call this directly. |
|
//The "current" page is always at the top of the history stack. |
//console.debug("handlingBackButton"); |
var current = historyStack.pop(); |
if(!current){ return; } |
var last = historyStack[historyStack.length-1]; |
if(!last && historyStack.length == 0){ |
last = 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"); |
} |
} |
forwardStack.push(current); |
//console.debug("done handling back"); |
} |
|
back.goBack = handleBackButton; |
|
function handleForwardButton(){ |
//summary: private method. Do not call this directly. |
//console.debug("handling forward"); |
var last = 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"); |
} |
historyStack.push(last); |
//console.debug("done handling forward"); |
} |
|
back.goForward = handleForwardButton; |
|
function createState(url, args, hash){ |
//summary: private method. Do not call this directly. |
return {"url": url, "kwArgs": args, "urlHash": hash}; //Object |
} |
|
function getUrlQuery(url){ |
//summary: private method. Do not call this directly. |
var segments = url.split("?"); |
if (segments.length < 2){ |
return null; //null |
} |
else{ |
return segments[1]; //String |
} |
} |
|
function loadIframeHistory(){ |
//summary: private method. Do not call this directly. |
var url = (djConfig["dojoIframeHistoryUrl"] || dojo.moduleUrl("dojo", "resources/iframe_history.html")) + "?" + (new Date()).getTime(); |
moveForward = true; |
if (historyIframe) { |
(dojo.isSafari) ? historyIframe.location = url : window.frames[historyIframe.name].location = url; |
} else { |
//console.warn("dojo.back: Not initialised. You need to call dojo.back.init() from a <script> block that lives inside the <body> tag."); |
} |
return url; //String |
} |
|
function checkLocation(){ |
//console.debug("checking url"); |
if(!changingUrl){ |
var hsl = historyStack.length; |
|
var hash = getHash(); |
|
if((hash === initialHash||window.location.href == 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); |
handleBackButton(); |
return; |
} |
|
// first check to see if we could have gone forward. We always halt on |
// a no-hash item. |
if(forwardStack.length > 0){ |
if(forwardStack[forwardStack.length-1].urlHash === hash){ |
handleForwardButton(); |
return; |
} |
} |
|
// ok, that didn't work, try someplace back in the history stack |
if((hsl >= 2)&&(historyStack[hsl-2])){ |
if(historyStack[hsl-2].urlHash === hash){ |
handleBackButton(); |
return; |
} |
} |
|
if(dojo.isSafari && dojo.isSafari < 3){ |
var hisLen = history.length; |
if(hisLen > historyCounter) handleForwardButton(); |
else if(hisLen < historyCounter) handleBackButton(); |
historyCounter = hisLen; |
} |
} |
//console.debug("done checking"); |
}; |
|
back.init = function(){ |
//summary: Initializes the undo stack. This must be called from a <script> |
// block that lives inside the <body> tag to prevent bugs on IE. |
if(dojo.byId("dj_history")){ return; } // prevent reinit |
var src = djConfig["dojoIframeHistoryUrl"] || dojo.moduleUrl("dojo", "resources/iframe_history.html"); |
document.write('<iframe style="border:0;width:1px;height:1px;position:absolute;visibility:hidden;bottom:0;right:0;" name="dj_history" id="dj_history" src="' + src + '"></iframe>'); |
}; |
|
back.setInitialState = function(/*Object*/args){ |
//summary: |
// Sets the state object and back callback for the very first page |
// that is loaded. |
//description: |
// It is recommended that you call this method as part of an event |
// listener that is registered via dojo.addOnLoad(). |
//args: Object |
// See the addToHistory() function for the list of valid args properties. |
initialState = createState(initialHref, args, initialHash); |
}; |
|
//FIXME: Make these doc comments not be awful. At least they're not wrong. |
//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. |
|
|
/*===== |
dojo.__backArgs = function(kwArgs){ |
// back: Function? |
// A function to be called when this state is reached via the user |
// clicking the back button. |
// forward: Function? |
// Upon return to this state from the "back, forward" combination |
// of navigation steps, this function will be called. Somewhat |
// analgous to the semantic of an "onRedo" event handler. |
// changeUrl: Boolean?|String? |
// Boolean indicating whether or not to create a unique hash for |
// this state. If a string is passed instead, it is used as the |
// hash. |
} |
=====*/ |
|
back.addToHistory = function(/*dojo.__backArgs*/ args){ |
// summary: |
// adds a state object (args) to the history list. |
// description: |
// 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 |
// |
// example: |
// | dojo.back.addToHistory({ |
// | back: function(){ console.debug('back pressed'); }, |
// | forward: function(){ console.debug('forward pressed'); }, |
// | changeUrl: true |
// | }); |
|
// BROWSER 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. |
|
//If addToHistory is called, then that means we prune the |
//forward stack -- the user went back, then wanted to |
//start a new forward path. |
forwardStack = []; |
|
var hash = null; |
var url = null; |
if(!historyIframe){ |
if(djConfig["useXDomain"] && !djConfig["dojoIframeHistoryUrl"]){ |
console.debug("dojo.back: When using cross-domain Dojo builds," |
+ " please save iframe_history.html to your domain and set djConfig.dojoIframeHistoryUrl" |
+ " to the path on your domain to iframe_history.html"); |
} |
historyIframe = window.frames["dj_history"]; |
} |
if(!bookmarkAnchor){ |
bookmarkAnchor = document.createElement("a"); |
dojo.body().appendChild(bookmarkAnchor); |
bookmarkAnchor.style.display = "none"; |
} |
if(args["changeUrl"]){ |
hash = ""+ ((args["changeUrl"]!==true) ? args["changeUrl"] : (new Date()).getTime()); |
|
//If the current hash matches the new one, just replace the history object with |
//this new one. It doesn't make sense to track different state objects for the same |
//logical URL. This matches the browser behavior of only putting in one history |
//item no matter how many times you click on the same #hash link, at least in Firefox |
//and Safari, and there is no reliable way in those browsers to know if a #hash link |
//has been clicked on multiple times. So making this the standard behavior in all browsers |
//so that dojo.back's behavior is the same in all browsers. |
if(historyStack.length == 0 && initialState.urlHash == hash){ |
initialState = createState(url, args, hash); |
return; |
}else if(historyStack.length > 0 && historyStack[historyStack.length - 1].urlHash == hash){ |
historyStack[historyStack.length - 1] = createState(url, args, hash); |
return; |
} |
|
changingUrl = true; |
setTimeout(function() { |
setHash(hash); |
changingUrl = false; |
}, 1); |
bookmarkAnchor.href = hash; |
|
if(dojo.isIE){ |
url = loadIframeHistory(); |
|
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(getHash() != ""){ |
setTimeout(function() { setHash(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; |
} |
|
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(getHash() != ""){ |
setHash(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.isIE){ |
// start the timer |
if(!locationTimer){ |
locationTimer = setInterval(checkLocation, 200); |
} |
|
} |
}else{ |
url = loadIframeHistory(); |
} |
|
historyStack.push(createState(url, args, hash)); |
}; |
|
back._iframeLoaded = function(evt, ifrLoc){ |
//summary: |
// private method. Do not call this directly. |
var query = getUrlQuery(ifrLoc.href); |
if(query == null){ |
// alert("iframeLoaded"); |
// we hit the end of the history, so we should go back |
if(historyStack.length == 1){ |
handleBackButton(); |
} |
return; |
} |
if(moveForward){ |
// we were expecting it, so it's not either a forward or backward movement |
moveForward = false; |
return; |
} |
|
//Check the back stack first, since it is more likely. |
//Note that only one step back or forward is supported. |
if(historyStack.length >= 2 && query == getUrlQuery(historyStack[historyStack.length-2].url)){ |
handleBackButton(); |
} |
else if(forwardStack.length > 0 && query == getUrlQuery(forwardStack[forwardStack.length-1].url)){ |
handleForwardButton(); |
} |
}; |
})(); |
|
} |