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