]> git.pond.sub.org Git - eow/blob - static/dojo-release-1.1.1/dojo/back.js
add Dojo 1.1.1
[eow] / static / dojo-release-1.1.1 / dojo / back.js
1 if(!dojo._hasResource["dojo.back"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
2 dojo._hasResource["dojo.back"] = true;
3 dojo.provide("dojo.back");
4
5 /*=====
6 dojo.back = {
7         // summary: Browser history management resources
8 }
9 =====*/
10
11
12 (function(){ 
13         var back = dojo.back;
14
15         // everyone deals with encoding the hash slightly differently
16
17         function getHash(){ 
18                 var h = window.location.hash;
19                 if(h.charAt(0) == "#"){ h = h.substring(1); }
20                 return dojo.isMozilla ? h : decodeURIComponent(h); 
21         }
22         
23         function setHash(h){
24                 if(!h){ h = ""; }
25                 window.location.hash = encodeURIComponent(h);
26                 historyCounter = history.length;
27         }
28         
29         // if we're in the test for these methods, expose them on dojo.back. ok'd with alex.
30         if(dojo.exists("tests.back-hash")){
31                 back.getHash = getHash;
32                 back.setHash = setHash;         
33         }
34         
35         var initialHref = (typeof(window) !== "undefined") ? window.location.href : "";
36         var initialHash = (typeof(window) !== "undefined") ? getHash() : "";
37         var initialState = null;
38
39         var locationTimer = null;
40         var bookmarkAnchor = null;
41         var historyIframe = null;
42         var forwardStack = [];
43         var historyStack = [];
44         var moveForward = false;
45         var changingUrl = false;
46         var historyCounter;
47
48         function handleBackButton(){
49                 //summary: private method. Do not call this directly.
50
51                 //The "current" page is always at the top of the history stack.
52                 //console.debug("handlingBackButton");
53                 var current = historyStack.pop();
54                 if(!current){ return; }
55                 var last = historyStack[historyStack.length-1];
56                 if(!last && historyStack.length == 0){
57                         last = initialState;
58                 }
59                 if(last){
60                         if(last.kwArgs["back"]){
61                                 last.kwArgs["back"]();
62                         }else if(last.kwArgs["backButton"]){
63                                 last.kwArgs["backButton"]();
64                         }else if(last.kwArgs["handle"]){
65                                 last.kwArgs.handle("back");
66                         }
67                 }
68                 forwardStack.push(current);
69                 //console.debug("done handling back");
70         }
71
72         back.goBack = handleBackButton;
73
74         function handleForwardButton(){
75                 //summary: private method. Do not call this directly.
76                 //console.debug("handling forward");
77                 var last = forwardStack.pop();
78                 if(!last){ return; }
79                 if(last.kwArgs["forward"]){
80                         last.kwArgs.forward();
81                 }else if(last.kwArgs["forwardButton"]){
82                         last.kwArgs.forwardButton();
83                 }else if(last.kwArgs["handle"]){
84                         last.kwArgs.handle("forward");
85                 }
86                 historyStack.push(last);
87                 //console.debug("done handling forward");
88         }
89
90         back.goForward = handleForwardButton;
91
92         function createState(url, args, hash){
93                 //summary: private method. Do not call this directly.
94                 return {"url": url, "kwArgs": args, "urlHash": hash};   //Object
95         }
96
97         function getUrlQuery(url){
98                 //summary: private method. Do not call this directly.
99                 var segments = url.split("?");
100                 if(segments.length < 2){
101                         return null; //null
102                 }
103                 else{
104                         return segments[1]; //String
105                 }
106         }
107         
108         function loadIframeHistory(){
109                 //summary: private method. Do not call this directly.
110                 var url = (dojo.config["dojoIframeHistoryUrl"] || dojo.moduleUrl("dojo", "resources/iframe_history.html")) + "?" + (new Date()).getTime();
111                 moveForward = true;
112         if(historyIframe){
113                     dojo.isSafari ? historyIframe.location = url : window.frames[historyIframe.name].location = url;
114         }else{
115             //console.warn("dojo.back: Not initialised. You need to call dojo.back.init() from a <script> block that lives inside the <body> tag.");
116         }
117                 return url; //String
118         }
119
120         function checkLocation(){
121                 //console.debug("checking url");
122                 if(!changingUrl){
123                         var hsl = historyStack.length;
124                         
125                         var hash = getHash();
126
127                         if((hash === initialHash||window.location.href == initialHref)&&(hsl == 1)){
128                                 // FIXME: could this ever be a forward button?
129                                 // we can't clear it because we still need to check for forwards. Ugg.
130                                 // clearInterval(this.locationTimer);
131                                 handleBackButton();
132                                 return;
133                         }
134                         
135                         // first check to see if we could have gone forward. We always halt on
136                         // a no-hash item.
137                         if(forwardStack.length > 0){
138                                 if(forwardStack[forwardStack.length-1].urlHash === hash){
139                                         handleForwardButton();
140                                         return;
141                                 }
142                         }
143         
144                         // ok, that didn't work, try someplace back in the history stack
145                         if((hsl >= 2)&&(historyStack[hsl-2])){
146                                 if(historyStack[hsl-2].urlHash === hash){
147                                         handleBackButton();
148                                         return;
149                                 }
150                         }
151                         
152                         if(dojo.isSafari && dojo.isSafari < 3){
153                                 var hisLen = history.length;
154                                 if(hisLen > historyCounter) handleForwardButton();
155                                 else if(hisLen < historyCounter) handleBackButton();
156                           historyCounter = hisLen;
157                         }
158                 }
159                 //console.debug("done checking");
160         };
161         
162         back.init = function(){
163                 //summary: Initializes the undo stack. This must be called from a <script> 
164                 //         block that lives inside the <body> tag to prevent bugs on IE.
165                 if(dojo.byId("dj_history")){ return; } // prevent reinit
166                 var src = dojo.config["dojoIframeHistoryUrl"] || dojo.moduleUrl("dojo", "resources/iframe_history.html");
167                 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>');
168         };
169
170         back.setInitialState = function(/*Object*/args){
171                 //summary: 
172                 //              Sets the state object and back callback for the very first page
173                 //              that is loaded.
174                 //description:
175                 //              It is recommended that you call this method as part of an event
176                 //              listener that is registered via dojo.addOnLoad().
177                 //args: Object
178                 //              See the addToHistory() function for the list of valid args properties.
179                 initialState = createState(initialHref, args, initialHash);
180         };
181
182         //FIXME: Make these doc comments not be awful. At least they're not wrong.
183         //FIXME: Would like to support arbitrary back/forward jumps. Have to rework iframeLoaded among other things.
184         //FIXME: is there a slight race condition in moz using change URL with the timer check and when
185         //       the hash gets set? I think I have seen a back/forward call in quick succession, but not consistent.
186
187         
188         /*=====
189         dojo.__backArgs = function(kwArgs){
190                 // back: Function?
191                 //              A function to be called when this state is reached via the user
192                 //              clicking the back button.
193                 //      forward: Function?
194                 //              Upon return to this state from the "back, forward" combination
195                 //              of navigation steps, this function will be called. Somewhat
196                 //              analgous to the semantic of an "onRedo" event handler.
197                 //      changeUrl: Boolean?|String?
198                 //              Boolean indicating whether or not to create a unique hash for
199                 //              this state. If a string is passed instead, it is used as the
200                 //              hash.
201         }
202         =====*/
203
204         back.addToHistory = function(/*dojo.__backArgs*/ args){
205                 //      summary: 
206                 //              adds a state object (args) to the history list. 
207                 //      description:
208                 //              To support getting back button notifications, the object
209                 //              argument should implement a function called either "back",
210                 //              "backButton", or "handle". The string "back" will be passed as
211                 //              the first and only argument to this callback.
212                 //      
213                 //              To support getting forward button notifications, the object
214                 //              argument should implement a function called either "forward",
215                 //              "forwardButton", or "handle". The string "forward" will be
216                 //              passed as the first and only argument to this callback.
217                 //
218                 //              If you want the browser location string to change, define "changeUrl" on the object. If the
219                 //              value of "changeUrl" is true, then a unique number will be appended to the URL as a fragment
220                 //              identifier (http://some.domain.com/path#uniquenumber). If it is any other value that does
221                 //              not evaluate to false, that value will be used as the fragment identifier. For example,
222                 //              if changeUrl: 'page1', then the URL will look like: http://some.domain.com/path#page1
223                 //
224                 //      example:
225                 //              |       dojo.back.addToHistory({
226                 //              |               back: function(){ console.debug('back pressed'); },
227                 //              |               forward: function(){ console.debug('forward pressed'); },
228                 //              |               changeUrl: true
229                 //              |       });
230
231                 //      BROWSER NOTES:
232                 //  Safari 1.2: 
233                 //      back button "works" fine, however it's not possible to actually
234                 //      DETECT that you've moved backwards by inspecting window.location.
235                 //      Unless there is some other means of locating.
236                 //      FIXME: perhaps we can poll on history.length?
237                 //      Safari 2.0.3+ (and probably 1.3.2+):
238                 //      works fine, except when changeUrl is used. When changeUrl is used,
239                 //      Safari jumps all the way back to whatever page was shown before
240                 //      the page that uses dojo.undo.browser support.
241                 //      IE 5.5 SP2:
242                 //      back button behavior is macro. It does not move back to the
243                 //      previous hash value, but to the last full page load. This suggests
244                 //      that the iframe is the correct way to capture the back button in
245                 //      these cases.
246                 //      Don't test this page using local disk for MSIE. MSIE will not create 
247                 //      a history list for iframe_history.html if served from a file: URL. 
248                 //      The XML served back from the XHR tests will also not be properly 
249                 //      created if served from local disk. Serve the test pages from a web 
250                 //      server to test in that browser.
251                 //      IE 6.0:
252                 //      same behavior as IE 5.5 SP2
253                 //      Firefox 1.0+:
254                 //      the back button will return us to the previous hash on the same
255                 //      page, thereby not requiring an iframe hack, although we do then
256                 //      need to run a timer to detect inter-page movement.
257
258                 //If addToHistory is called, then that means we prune the
259                 //forward stack -- the user went back, then wanted to
260                 //start a new forward path.
261                 forwardStack = []; 
262
263                 var hash = null;
264                 var url = null;
265                 if(!historyIframe){
266                         if(dojo.config["useXDomain"] && !dojo.config["dojoIframeHistoryUrl"]){
267                                 console.debug("dojo.back: When using cross-domain Dojo builds,"
268                                         + " please save iframe_history.html to your domain and set djConfig.dojoIframeHistoryUrl"
269                                         + " to the path on your domain to iframe_history.html");
270                         }
271                         historyIframe = window.frames["dj_history"];
272                 }
273                 if(!bookmarkAnchor){
274                         bookmarkAnchor = document.createElement("a");
275                         dojo.body().appendChild(bookmarkAnchor);
276                         bookmarkAnchor.style.display = "none";
277                 }
278                 if(args["changeUrl"]){
279                         hash = ""+ ((args["changeUrl"]!==true) ? args["changeUrl"] : (new Date()).getTime());
280                         
281                         //If the current hash matches the new one, just replace the history object with
282                         //this new one. It doesn't make sense to track different state objects for the same
283                         //logical URL. This matches the browser behavior of only putting in one history
284                         //item no matter how many times you click on the same #hash link, at least in Firefox
285                         //and Safari, and there is no reliable way in those browsers to know if a #hash link
286                         //has been clicked on multiple times. So making this the standard behavior in all browsers
287                         //so that dojo.back's behavior is the same in all browsers.
288                         if(historyStack.length == 0 && initialState.urlHash == hash){
289                                 initialState = createState(url, args, hash);
290                                 return;
291                         }else if(historyStack.length > 0 && historyStack[historyStack.length - 1].urlHash == hash){
292                                 historyStack[historyStack.length - 1] = createState(url, args, hash);
293                                 return;
294                         }
295
296                         changingUrl = true;
297                         setTimeout(function() { 
298                                         setHash(hash); 
299                                         changingUrl = false;                                    
300                                 }, 1);
301                         bookmarkAnchor.href = hash;
302                         
303                         if(dojo.isIE){
304                                 url = loadIframeHistory();
305
306                                 var oldCB = args["back"]||args["backButton"]||args["handle"];
307
308                                 //The function takes handleName as a parameter, in case the
309                                 //callback we are overriding was "handle". In that case,
310                                 //we will need to pass the handle name to handle.
311                                 var tcb = function(handleName){
312                                         if(getHash() != ""){
313                                                 setTimeout(function() { setHash(hash); }, 1);
314                                         }
315                                         //Use apply to set "this" to args, and to try to avoid memory leaks.
316                                         oldCB.apply(this, [handleName]);
317                                 };
318                 
319                                 //Set interceptor function in the right place.
320                                 if(args["back"]){
321                                         args.back = tcb;
322                                 }else if(args["backButton"]){
323                                         args.backButton = tcb;
324                                 }else if(args["handle"]){
325                                         args.handle = tcb;
326                                 }
327                 
328                                 var oldFW = args["forward"]||args["forwardButton"]||args["handle"];
329                 
330                                 //The function takes handleName as a parameter, in case the
331                                 //callback we are overriding was "handle". In that case,
332                                 //we will need to pass the handle name to handle.
333                                 var tfw = function(handleName){
334                                         if(getHash() != ""){
335                                                 setHash(hash);
336                                         }
337                                         if(oldFW){ // we might not actually have one
338                                                 //Use apply to set "this" to args, and to try to avoid memory leaks.
339                                                 oldFW.apply(this, [handleName]);
340                                         }
341                                 };
342
343                                 //Set interceptor function in the right place.
344                                 if(args["forward"]){
345                                         args.forward = tfw;
346                                 }else if(args["forwardButton"]){
347                                         args.forwardButton = tfw;
348                                 }else if(args["handle"]){
349                                         args.handle = tfw;
350                                 }
351
352                         }else if(!dojo.isIE){
353                                 // start the timer
354                                 if(!locationTimer){
355                                         locationTimer = setInterval(checkLocation, 200);
356                                 }
357                                 
358                         }
359                 }else{
360                         url = loadIframeHistory();
361                 }
362
363                 historyStack.push(createState(url, args, hash));
364         };
365
366         back._iframeLoaded = function(evt, ifrLoc){
367                 //summary: 
368                 //              private method. Do not call this directly.
369                 var query = getUrlQuery(ifrLoc.href);
370                 if(query == null){ 
371                         // alert("iframeLoaded");
372                         // we hit the end of the history, so we should go back
373                         if(historyStack.length == 1){
374                                 handleBackButton();
375                         }
376                         return;
377                 }
378                 if(moveForward){
379                         // we were expecting it, so it's not either a forward or backward movement
380                         moveForward = false;
381                         return;
382                 }
383         
384                 //Check the back stack first, since it is more likely.
385                 //Note that only one step back or forward is supported.
386                 if(historyStack.length >= 2 && query == getUrlQuery(historyStack[historyStack.length-2].url)){
387                         handleBackButton();
388                 }else if(forwardStack.length > 0 && query == getUrlQuery(forwardStack[forwardStack.length-1].url)){
389                         handleForwardButton();
390                 }
391         };
392  })();
393
394 }