]> git.wh0rd.org - tt-rss.git/blame - lib/dojo/back.js
upgrade Dojo to 1.6.1
[tt-rss.git] / lib / dojo / back.js
CommitLineData
2f01fe57 1/*
81bea17a 2 Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved.
2f01fe57
AD
3 Available via Academic Free License >= 2.1 OR the modified BSD license.
4 see: http://dojotoolkit.org/license for details
5*/
6
7
a089699c
AD
8if(!dojo._hasResource["dojo.back"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9dojo._hasResource["dojo.back"] = true;
2f01fe57 10dojo.provide("dojo.back");
a089699c 11
81bea17a
AD
12dojo.getObject("back", true, dojo);
13
a089699c
AD
14/*=====
15dojo.back = {
16 // summary: Browser history management resources
2f01fe57 17}
a089699c
AD
18=====*/
19
20
81bea17a
AD
21(function(){
22 var back = dojo.back,
a089699c
AD
23
24 // everyone deals with encoding the hash slightly differently
25
81bea17a 26 getHash= back.getHash= function(){
a089699c
AD
27 var h = window.location.hash;
28 if(h.charAt(0) == "#"){ h = h.substring(1); }
81bea17a
AD
29 return dojo.isMozilla ? h : decodeURIComponent(h);
30 },
a089699c 31
81bea17a 32 setHash= back.setHash= function(h){
a089699c
AD
33 if(!h){ h = ""; }
34 window.location.hash = encodeURIComponent(h);
35 historyCounter = history.length;
81bea17a
AD
36 };
37
a089699c
AD
38 var initialHref = (typeof(window) !== "undefined") ? window.location.href : "";
39 var initialHash = (typeof(window) !== "undefined") ? getHash() : "";
40 var initialState = null;
41
42 var locationTimer = null;
43 var bookmarkAnchor = null;
44 var historyIframe = null;
45 var forwardStack = [];
46 var historyStack = [];
47 var moveForward = false;
48 var changingUrl = false;
49 var historyCounter;
50
51 function handleBackButton(){
52 //summary: private method. Do not call this directly.
53
54 //The "current" page is always at the top of the history stack.
55 var current = historyStack.pop();
56 if(!current){ return; }
57 var last = historyStack[historyStack.length-1];
58 if(!last && historyStack.length == 0){
59 last = initialState;
60 }
61 if(last){
62 if(last.kwArgs["back"]){
63 last.kwArgs["back"]();
64 }else if(last.kwArgs["backButton"]){
65 last.kwArgs["backButton"]();
66 }else if(last.kwArgs["handle"]){
67 last.kwArgs.handle("back");
68 }
69 }
70 forwardStack.push(current);
71 }
72
73 back.goBack = handleBackButton;
74
75 function handleForwardButton(){
76 //summary: private method. Do not call this directly.
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 }
88
89 back.goForward = handleForwardButton;
90
91 function createState(url, args, hash){
92 //summary: private method. Do not call this directly.
93 return {"url": url, "kwArgs": args, "urlHash": hash}; //Object
94 }
95
96 function getUrlQuery(url){
97 //summary: private method. Do not call this directly.
98 var segments = url.split("?");
99 if(segments.length < 2){
100 return null; //null
101 }
102 else{
103 return segments[1]; //String
104 }
105 }
106
107 function loadIframeHistory(){
108 //summary: private method. Do not call this directly.
109 var url = (dojo.config["dojoIframeHistoryUrl"] || dojo.moduleUrl("dojo", "resources/iframe_history.html")) + "?" + (new Date()).getTime();
110 moveForward = true;
111 if(historyIframe){
112 dojo.isWebKit ? historyIframe.location = url : window.frames[historyIframe.name].location = url;
113 }else{
114 //console.warn("dojo.back: Not initialised. You need to call dojo.back.init() from a <script> block that lives inside the <body> tag.");
115 }
116 return url; //String
117 }
118
119 function checkLocation(){
120 if(!changingUrl){
121 var hsl = historyStack.length;
122
123 var hash = getHash();
124
125 if((hash === initialHash||window.location.href == initialHref)&&(hsl == 1)){
126 // FIXME: could this ever be a forward button?
127 // we can't clear it because we still need to check for forwards. Ugg.
128 // clearInterval(this.locationTimer);
129 handleBackButton();
130 return;
131 }
132
133 // first check to see if we could have gone forward. We always halt on
134 // a no-hash item.
135 if(forwardStack.length > 0){
136 if(forwardStack[forwardStack.length-1].urlHash === hash){
137 handleForwardButton();
138 return;
139 }
140 }
141
142 // ok, that didn't work, try someplace back in the history stack
143 if((hsl >= 2)&&(historyStack[hsl-2])){
144 if(historyStack[hsl-2].urlHash === hash){
145 handleBackButton();
146 return;
147 }
148 }
a089699c
AD
149 }
150 };
151
152 back.init = function(){
81bea17a 153 //summary: Initializes the undo stack. This must be called from a <script>
a089699c
AD
154 // block that lives inside the <body> tag to prevent bugs on IE.
155 // description:
156 // Only call this method before the page's DOM is finished loading. Otherwise
157 // it will not work. Be careful with xdomain loading or djConfig.debugAtAllCosts scenarios,
158 // in order for this method to work, dojo.back will need to be part of a build layer.
159 if(dojo.byId("dj_history")){ return; } // prevent reinit
160 var src = dojo.config["dojoIframeHistoryUrl"] || dojo.moduleUrl("dojo", "resources/iframe_history.html");
161 if (dojo._postLoad) {
162 console.error("dojo.back.init() must be called before the DOM has loaded. "
163 + "If using xdomain loading or djConfig.debugAtAllCosts, include dojo.back "
164 + "in a build layer.");
165 } else {
166 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>');
167 }
168 };
169
170 back.setInitialState = function(/*Object*/args){
81bea17a 171 //summary:
a089699c
AD
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){
81bea17a
AD
205 // summary:
206 // adds a state object (args) to the history list.
a089699c
AD
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.
81bea17a 212 //
a089699c
AD
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 // There are problems with using dojo.back with semantically-named fragment identifiers
225 // ("hash values" on an URL). In most browsers it will be hard for dojo.back to know
226 // distinguish a back from a forward event in those cases. For back/forward support to
227 // work best, the fragment ID should always be a unique value (something using new Date().getTime()
228 // for example). If you want to detect hash changes using semantic fragment IDs, then
229 // consider using dojo.hash instead (in Dojo 1.4+).
230 //
231 // example:
232 // | dojo.back.addToHistory({
233 // | back: function(){ console.log('back pressed'); },
234 // | forward: function(){ console.log('forward pressed'); },
235 // | changeUrl: true
236 // | });
237
238 // BROWSER NOTES:
81bea17a 239 // Safari 1.2:
a089699c
AD
240 // back button "works" fine, however it's not possible to actually
241 // DETECT that you've moved backwards by inspecting window.location.
242 // Unless there is some other means of locating.
243 // FIXME: perhaps we can poll on history.length?
244 // Safari 2.0.3+ (and probably 1.3.2+):
245 // works fine, except when changeUrl is used. When changeUrl is used,
246 // Safari jumps all the way back to whatever page was shown before
247 // the page that uses dojo.undo.browser support.
248 // IE 5.5 SP2:
249 // back button behavior is macro. It does not move back to the
250 // previous hash value, but to the last full page load. This suggests
251 // that the iframe is the correct way to capture the back button in
252 // these cases.
81bea17a
AD
253 // Don't test this page using local disk for MSIE. MSIE will not create
254 // a history list for iframe_history.html if served from a file: URL.
255 // The XML served back from the XHR tests will also not be properly
256 // created if served from local disk. Serve the test pages from a web
a089699c
AD
257 // server to test in that browser.
258 // IE 6.0:
259 // same behavior as IE 5.5 SP2
260 // Firefox 1.0+:
261 // the back button will return us to the previous hash on the same
262 // page, thereby not requiring an iframe hack, although we do then
263 // need to run a timer to detect inter-page movement.
264
265 //If addToHistory is called, then that means we prune the
266 //forward stack -- the user went back, then wanted to
267 //start a new forward path.
81bea17a 268 forwardStack = [];
a089699c
AD
269
270 var hash = null;
271 var url = null;
272 if(!historyIframe){
273 if(dojo.config["useXDomain"] && !dojo.config["dojoIframeHistoryUrl"]){
274 console.warn("dojo.back: When using cross-domain Dojo builds,"
275 + " please save iframe_history.html to your domain and set djConfig.dojoIframeHistoryUrl"
276 + " to the path on your domain to iframe_history.html");
277 }
278 historyIframe = window.frames["dj_history"];
279 }
280 if(!bookmarkAnchor){
281 bookmarkAnchor = dojo.create("a", {style: {display: "none"}}, dojo.body());
282 }
283 if(args["changeUrl"]){
284 hash = ""+ ((args["changeUrl"]!==true) ? args["changeUrl"] : (new Date()).getTime());
285
286 //If the current hash matches the new one, just replace the history object with
287 //this new one. It doesn't make sense to track different state objects for the same
288 //logical URL. This matches the browser behavior of only putting in one history
289 //item no matter how many times you click on the same #hash link, at least in Firefox
290 //and Safari, and there is no reliable way in those browsers to know if a #hash link
291 //has been clicked on multiple times. So making this the standard behavior in all browsers
292 //so that dojo.back's behavior is the same in all browsers.
293 if(historyStack.length == 0 && initialState.urlHash == hash){
294 initialState = createState(url, args, hash);
295 return;
296 }else if(historyStack.length > 0 && historyStack[historyStack.length - 1].urlHash == hash){
297 historyStack[historyStack.length - 1] = createState(url, args, hash);
298 return;
299 }
300
301 changingUrl = true;
81bea17a
AD
302 setTimeout(function() {
303 setHash(hash);
304 changingUrl = false;
a089699c
AD
305 }, 1);
306 bookmarkAnchor.href = hash;
307
308 if(dojo.isIE){
309 url = loadIframeHistory();
310
311 var oldCB = args["back"]||args["backButton"]||args["handle"];
312
313 //The function takes handleName as a parameter, in case the
314 //callback we are overriding was "handle". In that case,
315 //we will need to pass the handle name to handle.
316 var tcb = function(handleName){
317 if(getHash() != ""){
318 setTimeout(function() { setHash(hash); }, 1);
319 }
320 //Use apply to set "this" to args, and to try to avoid memory leaks.
321 oldCB.apply(this, [handleName]);
322 };
323
324 //Set interceptor function in the right place.
325 if(args["back"]){
326 args.back = tcb;
327 }else if(args["backButton"]){
328 args.backButton = tcb;
329 }else if(args["handle"]){
330 args.handle = tcb;
331 }
332
333 var oldFW = args["forward"]||args["forwardButton"]||args["handle"];
334
335 //The function takes handleName as a parameter, in case the
336 //callback we are overriding was "handle". In that case,
337 //we will need to pass the handle name to handle.
338 var tfw = function(handleName){
339 if(getHash() != ""){
340 setHash(hash);
341 }
342 if(oldFW){ // we might not actually have one
343 //Use apply to set "this" to args, and to try to avoid memory leaks.
344 oldFW.apply(this, [handleName]);
345 }
346 };
347
348 //Set interceptor function in the right place.
349 if(args["forward"]){
350 args.forward = tfw;
351 }else if(args["forwardButton"]){
352 args.forwardButton = tfw;
353 }else if(args["handle"]){
354 args.handle = tfw;
355 }
356
357 }else if(!dojo.isIE){
358 // start the timer
359 if(!locationTimer){
360 locationTimer = setInterval(checkLocation, 200);
361 }
362
363 }
364 }else{
365 url = loadIframeHistory();
366 }
367
368 historyStack.push(createState(url, args, hash));
369 };
370
371 back._iframeLoaded = function(evt, ifrLoc){
81bea17a 372 //summary:
a089699c
AD
373 // private method. Do not call this directly.
374 var query = getUrlQuery(ifrLoc.href);
81bea17a 375 if(query == null){
a089699c
AD
376 // alert("iframeLoaded");
377 // we hit the end of the history, so we should go back
378 if(historyStack.length == 1){
379 handleBackButton();
380 }
381 return;
382 }
383 if(moveForward){
384 // we were expecting it, so it's not either a forward or backward movement
385 moveForward = false;
386 return;
387 }
388
389 //Check the back stack first, since it is more likely.
390 //Note that only one step back or forward is supported.
391 if(historyStack.length >= 2 && query == getUrlQuery(historyStack[historyStack.length-2].url)){
392 handleBackButton();
393 }else if(forwardStack.length > 0 && query == getUrlQuery(forwardStack[forwardStack.length-1].url)){
394 handleForwardButton();
395 }
396 };
397 })();
398
2f01fe57 399}