[vhffs-dev] [1366] Back button handling in AJAX public part |
[ Thread Index |
Date Index
| More vhffs.org/vhffs-dev Archives
]
Revision: 1366
Author: beuss
Date: 2009-03-10 18:01:45 +0100 (Tue, 10 Mar 2009)
Log Message:
-----------
Back button handling in AJAX public part
Modified Paths:
--------------
trunk/vhffs-panel/Makefile.am
trunk/vhffs-panel/js/public.js
trunk/vhffs-panel/js/vhffs/Common.js
trunk/vhffs-public/templates/layouts/public.tt
Added Paths:
-----------
trunk/vhffs-panel/js/dojo/back.js
trunk/vhffs-panel/js/dojo/resources/
trunk/vhffs-panel/js/dojo/resources/blank.gif
trunk/vhffs-panel/js/dojo/resources/iframe_history.html
Modified: trunk/vhffs-panel/Makefile.am
===================================================================
--- trunk/vhffs-panel/Makefile.am 2009-03-10 12:40:12 UTC (rev 1365)
+++ trunk/vhffs-panel/Makefile.am 2009-03-10 17:01:45 UTC (rev 1366)
@@ -1,7 +1,10 @@
javascripts = js/prototype.js \
js/commons.js \
js/dijit/dijit.js \
+ js/dojo/back.js \
js/dojo/dojo.js \
+ js/dojo/resources/blank.gif \
+ js/dojo/resources/iframe_history.html \
js/public.js \
js/tooltip.js \
js/vhffs/Common.js \
Added: trunk/vhffs-panel/js/dojo/back.js
===================================================================
--- trunk/vhffs-panel/js/dojo/back.js (rev 0)
+++ trunk/vhffs-panel/js/dojo/back.js 2009-03-10 17:01:45 UTC (rev 1366)
@@ -0,0 +1,394 @@
+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");
+
+/*=====
+dojo.back = {
+ // summary: Browser history management resources
+}
+=====*/
+
+
+(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 = (dojo.config["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 = dojo.config["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(dojo.config["useXDomain"] && !dojo.config["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();
+ }
+ };
+ })();
+
+}
Added: trunk/vhffs-panel/js/dojo/resources/blank.gif
===================================================================
(Binary files differ)
Property changes on: trunk/vhffs-panel/js/dojo/resources/blank.gif
___________________________________________________________________
Name: svn:mime-type
+ application/octet-stream
Added: trunk/vhffs-panel/js/dojo/resources/iframe_history.html
===================================================================
--- trunk/vhffs-panel/js/dojo/resources/iframe_history.html (rev 0)
+++ trunk/vhffs-panel/js/dojo/resources/iframe_history.html 2009-03-10 17:01:45 UTC (rev 1366)
@@ -0,0 +1,79 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+<head>
+ <title></title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"></meta>
+ <script type="text/javascript">
+ // <!--
+ var noInit = false;
+
+ function defineParams(sparams){
+ if(sparams){
+ var ss = (sparams.indexOf("&") >= 0) ? "&" : "&";
+ sparams = sparams.split(ss);
+ for(var x=0; x<sparams.length; x++){
+ var tp = sparams[x].split("=");
+ if(typeof window[tp[0]] != "undefined"){
+ window[tp[0]] = ((tp[1]=="true")||(tp[1]=="false")) ? eval(tp[1]) : tp[1];
+ }
+ }
+ }
+ }
+
+ function init(){
+ // parse the query string if there is one to try to get params that
+ // we can act on. Also allow params to be in a fragment identifier.
+ var query = null;
+ var frag = null;
+ var url = document.location.href;
+ var hashIndex = url.indexOf("#");
+
+ //Extract fragment identifier
+ if(hashIndex != -1){
+ frag = url.substring(hashIndex + 1, url.length);
+ url = url.substring(0, hashIndex);
+ }
+
+ //Extract querystring
+ var parts = url.split("?");
+ if(parts.length == 2){
+ query = parts[1];
+ }
+
+ defineParams(query);
+ defineParams(frag);
+
+ if(noInit){ return; }
+ var hasParentDojo = false;
+ try{
+ hasParentDojo = window.parent != window && window.parent["dojo"];
+ }catch(e){
+ alert("Initializing iframe_history.html failed. If you are using a cross-domain Dojo build,"
+ + " please save iframe_history.html to your domain and set djConfig.dojoIframeHistoryUrl"
+ + " to the path on your domain to iframe_history.html");
+ throw e;
+ }
+
+ if(hasParentDojo){
+ //Set the page title so IE history shows up with a somewhat correct name.
+ document.title = window.parent.document.title;
+
+ //Notify parent that we are loaded.
+ var pdj = window.parent.dojo;
+ if(pdj["back"]){
+ pdj.back._iframeLoaded(null, window.location);
+ }
+ }
+
+ }
+ // -->
+ </script>
+</head>
+<body onload="try{ init(); }catch(e){ alert(e); }">
+ <h4>The Dojo Toolkit -- iframe_history.html</h4>
+
+ <p>This file is used in Dojo's back/fwd button management.</p>
+</body>
+</html>
Modified: trunk/vhffs-panel/js/public.js
===================================================================
--- trunk/vhffs-panel/js/public.js 2009-03-10 12:40:12 UTC (rev 1365)
+++ trunk/vhffs-panel/js/public.js 2009-03-10 17:01:45 UTC (rev 1366)
@@ -55,12 +55,16 @@
var form = dojo.byId('AdvancedSearchGroupForm');
dojo.connect(form, 'onsubmit', function(e) {
dojo.stopEvent(e);
+ var url = dojo.attr(form, 'action');
+ var content = dojo.formToObject(form);
+ var container = dojo.byId('public-content');
+ dojo.back.addToHistory(new vhffs.Common.pageState(url, container, content));
dojo.xhrPost({
- url: dojo.attr(form, 'action'),
- 'form': form,
+ 'url': url,
+ 'content': content,
load: function(response) {
- vhffs.Common.loadContent(dojo.byId('public-content'), response);
- vhffs.Common.ajaxizeLinks(dojo.byId('public-content'));
+ vhffs.Common.loadContent(container, response);
+ vhffs.Common.ajaxizeLinks(container);
}
});
});
@@ -184,12 +188,16 @@
var form = dojo.byId('SearchUserForm');
dojo.connect(form, 'onsubmit', function(e) {
dojo.stopEvent(e);
+ var url = dojo.attr(form, 'action');
+ var content = dojo.formToObject(form);
+ var container = dojo.byId('public-content');
+ dojo.back.addToHistory(new vhffs.Common.pageState(url, container, content));
dojo.xhrPost({
- url: dojo.attr(form, 'action'),
- 'form': form,
+ 'url': url,
+ 'content': content,
load: function(response) {
- vhffs.Common.loadContent(dojo.byId('public-content'), response);
- vhffs.Common.ajaxizeLinks(dojo.byId('public-content'));
+ vhffs.Common.loadContent(container, response);
+ vhffs.Common.ajaxizeLinks(container);
}
});
});
Modified: trunk/vhffs-panel/js/vhffs/Common.js
===================================================================
--- trunk/vhffs-panel/js/vhffs/Common.js 2009-03-10 12:40:12 UTC (rev 1365)
+++ trunk/vhffs-panel/js/vhffs/Common.js 2009-03-10 17:01:45 UTC (rev 1366)
@@ -29,9 +29,11 @@
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
-
+
dojo.provide('vhffs.Common');
+dojo.require('dojo.back');
+
dojo.declare('vhffs.Common', null, {});
dojo.mixin(vhffs.Common, {
@@ -41,6 +43,7 @@
dojo.stopEvent(e);
var href = dojo.attr(link, 'href');
if(href != '#') {
+ dojo.back.addToHistory(new vhffs.Common.pageState(href, contentTarget));
dojo.xhrGet({
url: href,
load: function(response) {
@@ -108,3 +111,58 @@
);
}
});
+
+// Back and forward handling with Ajax
+
+dojo.declare('vhffs.Common.pageState', null, {
+ /**
+ * Creates a new pageState.
+ * url is the URL to load when this page is to
+ * be restored, target the container in which the
+ * content will be injected and postObject an optional
+ * object containing post data (request will be GET if
+ * it evaluates to false).
+ */
+ constructor: function(url, target, postObject) {
+ this.url = url;
+ this.target = target;
+ this.postObject = postObject;
+ },
+
+ back: function() {
+ this.loadUrl();
+ },
+
+ forward: function() {
+ this.loadUrl();
+ },
+
+ loadUrl: function() {
+ var href = this.url;
+ var contentTarget = this.target;
+
+ if(this.postObject) {
+ var postContent = this.postObject;
+ dojo.xhrPost({
+ url: href,
+ load: function(response) {
+ vhffs.Common.loadContent(contentTarget, response);
+ },
+ content: postContent
+ });
+ } else {
+ dojo.xhrGet({
+ url: href,
+ load: function(response) {
+ vhffs.Common.loadContent(contentTarget, response);
+ }
+ });
+ }
+ }
+});
+
+dojo.addOnLoad(function() {
+ var initState = new vhffs.Common.pageState(document.location.pathname, dojo.byId('public-content'));
+ dojo.back.setInitialState(initState);
+});
+
Modified: trunk/vhffs-public/templates/layouts/public.tt
===================================================================
--- trunk/vhffs-public/templates/layouts/public.tt 2009-03-10 12:40:12 UTC (rev 1365)
+++ trunk/vhffs-public/templates/layouts/public.tt 2009-03-10 17:01:45 UTC (rev 1366)
@@ -7,9 +7,17 @@
[%# TODO: Add a parameter to include extra-js %]
+ <script language="JavaScript" type="text/javascript">
+ // Dojo configuration
+ djConfig = {
+ preventBackButtonFix: false
+ };
+ </script>
+
<script type="text/javascript" src="/js/dojo/dojo.js"></script>
<script type="text/javascript" src="/js/dijit/dijit.js"></script>
<script type="text/javascript" src="/js/public.js"></script>
+ <script type="text/javascript">dojo.back.init();</script>
<title>Vhffs::Virtual hosting for free software</title>
</head>
<body>
@@ -34,4 +42,4 @@
[% INCLUDE parts/footer.tt %]
</div>
</div>
-</div>
\ No newline at end of file
+</div>