]> git.wh0rd.org - tt-rss.git/blame - lib/dojo/hash.js
upgrade Dojo to 1.6.1
[tt-rss.git] / lib / dojo / hash.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.hash"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code.
9dojo._hasResource["dojo.hash"] = true;
2f01fe57 10dojo.provide("dojo.hash");
81bea17a
AD
11
12
a089699c
AD
13//TODOC: where does this go?
14// summary:
15// Methods for monitoring and updating the hash in the browser URL.
16//
17// example:
18// dojo.subscribe("/dojo/hashchange", context, callback);
19//
20// function callback (hashValue){
21// // do something based on the hash value.
22// }
23
2f01fe57 24(function(){
a089699c
AD
25 dojo.hash = function(/* String? */ hash, /* Boolean? */ replace){
26 // summary:
27 // Gets or sets the hash string.
28 // description:
29 // Handles getting and setting of location.hash.
30 // - If no arguments are passed, acts as a getter.
31 // - If a string is passed, acts as a setter.
81bea17a
AD
32 // hash:
33 // the hash is set - #string.
a089699c 34 // replace:
81bea17a
AD
35 // If true, updates the hash value in the current history
36 // state instead of creating a new history state.
a089699c
AD
37 // returns:
38 // when used as a getter, returns the current hash string.
39 // when used as a setter, returns the new hash string.
40
41 // getter
42 if(!arguments.length){
43 return _getHash();
44 }
45 // setter
46 if(hash.charAt(0) == "#"){
47 hash = hash.substring(1);
48 }
49 if(replace){
50 _replace(hash);
51 }else{
52 location.href = "#" + hash;
53 }
54 return hash; // String
81bea17a 55 };
a089699c
AD
56
57 // Global vars
81bea17a 58 var _recentHash, _ieUriMonitor, _connect,
a089699c
AD
59 _pollFrequency = dojo.config.hashPollFrequency || 100;
60
61 //Internal functions
62 function _getSegment(str, delimiter){
63 var i = str.indexOf(delimiter);
81bea17a 64 return (i >= 0) ? str.substring(i+1) : "";
a089699c
AD
65 }
66
67 function _getHash(){
68 return _getSegment(location.href, "#");
69 }
70
71 function _dispatchEvent(){
72 dojo.publish("/dojo/hashchange", [_getHash()]);
73 }
74
75 function _pollLocation(){
76 if(_getHash() === _recentHash){
77 return;
78 }
79 _recentHash = _getHash();
80 _dispatchEvent();
81 }
82
83 function _replace(hash){
84 if(_ieUriMonitor){
85 if(_ieUriMonitor.isTransitioning()){
86 setTimeout(dojo.hitch(null,_replace,hash), _pollFrequency);
87 return;
88 }
89 var href = _ieUriMonitor.iframe.location.href;
90 var index = href.indexOf('?');
91 // main frame will detect and update itself
92 _ieUriMonitor.iframe.location.replace(href.substring(0, index) + "?" + hash);
93 return;
94 }
95 location.replace("#"+hash);
81bea17a 96 !_connect && _pollLocation();
a089699c
AD
97 }
98
99 function IEUriMonitor(){
100 // summary:
81bea17a 101 // Determine if the browser's URI has changed or if the user has pressed the
a089699c
AD
102 // back or forward button. If so, call _dispatchEvent.
103 //
104 // description:
105 // IE doesn't add changes to the URI's hash into the history unless the hash
106 // value corresponds to an actual named anchor in the document. To get around
107 // this IE difference, we use a background IFrame to maintain a back-forward
108 // history, by updating the IFrame's query string to correspond to the
109 // value of the main browser location's hash value.
110 //
111 // E.g. if the value of the browser window's location changes to
112 //
113 // #action=someAction
114 //
115 // ... then we'd update the IFrame's source to:
116 //
117 // ?action=someAction
118 //
119 // This design leads to a somewhat complex state machine, which is
120 // described below:
121 //
122 // s1: Stable state - neither the window's location has changed nor
123 // has the IFrame's location. Note that this is the 99.9% case, so
124 // we optimize for it.
125 // Transitions: s1, s2, s3
126 // s2: Window's location changed - when a user clicks a hyperlink or
127 // code programmatically changes the window's URI.
128 // Transitions: s4
129 // s3: Iframe's location changed as a result of user pressing back or
130 // forward - when the user presses back or forward, the location of
131 // the background's iframe changes to the previous or next value in
132 // its history.
133 // Transitions: s1
134 // s4: IEUriMonitor has programmatically changed the location of the
135 // background iframe, but it's location hasn't yet changed. In this
136 // case we do nothing because we need to wait for the iframe's
137 // location to reflect its actual state.
138 // Transitions: s4, s5
139 // s5: IEUriMonitor has programmatically changed the location of the
140 // background iframe, and the iframe's location has caught up with
141 // reality. In this case we need to transition to s1.
142 // Transitions: s1
143 //
144 // The hashchange event is always dispatched on the transition back to s1.
145 //
146
147 // create and append iframe
148 var ifr = document.createElement("iframe"),
149 IFRAME_ID = "dojo-hash-iframe",
150 ifrSrc = dojo.config.dojoBlankHtmlUrl || dojo.moduleUrl("dojo", "resources/blank.html");
81bea17a
AD
151
152 if(dojo.config.useXDomain && !dojo.config.dojoBlankHtmlUrl){
153 console.warn("dojo.hash: When using cross-domain Dojo builds,"
154 + " please save dojo/resources/blank.html to your domain and set djConfig.dojoBlankHtmlUrl"
155 + " to the path on your domain to blank.html");
156 }
157
a089699c
AD
158 ifr.id = IFRAME_ID;
159 ifr.src = ifrSrc + "?" + _getHash();
160 ifr.style.display = "none";
161 document.body.appendChild(ifr);
162
163 this.iframe = dojo.global[IFRAME_ID];
164 var recentIframeQuery, transitioning, expectedIFrameQuery, docTitle, ifrOffline,
165 iframeLoc = this.iframe.location;
166
167 function resetState(){
168 _recentHash = _getHash();
169 recentIframeQuery = ifrOffline ? _recentHash : _getSegment(iframeLoc.href, "?");
170 transitioning = false;
171 expectedIFrameQuery = null;
172 }
173
174 this.isTransitioning = function(){
175 return transitioning;
81bea17a 176 };
a089699c
AD
177
178 this.pollLocation = function(){
179 if(!ifrOffline) {
180 try{
181 //see if we can access the iframe's location without a permission denied error
182 var iframeSearch = _getSegment(iframeLoc.href, "?");
183 //good, the iframe is same origin (no thrown exception)
184 if(document.title != docTitle){ //sync title of main window with title of iframe.
185 docTitle = this.iframe.document.title = document.title;
186 }
187 }catch(e){
188 //permission denied - server cannot be reached.
189 ifrOffline = true;
190 console.error("dojo.hash: Error adding history entry. Server unreachable.");
191 }
192 }
193 var hash = _getHash();
194 if(transitioning && _recentHash === hash){
195 // we're in an iframe transition (s4 or s5)
196 if(ifrOffline || iframeSearch === expectedIFrameQuery){
197 // s5 (iframe caught up to main window or iframe offline), transition back to s1
198 resetState();
199 _dispatchEvent();
200 }else{
201 // s4 (waiting for iframe to catch up to main window)
202 setTimeout(dojo.hitch(this,this.pollLocation),0);
203 return;
204 }
205 }else if(_recentHash === hash && (ifrOffline || recentIframeQuery === iframeSearch)){
206 // we're in stable state (s1, iframe query == main window hash), do nothing
207 }else{
208 // the user has initiated a URL change somehow.
209 // sync iframe query <-> main window hash
210 if(_recentHash !== hash){
211 // s2 (main window location changed), set iframe url and transition to s4
212 _recentHash = hash;
213 transitioning = true;
214 expectedIFrameQuery = hash;
215 ifr.src = ifrSrc + "?" + expectedIFrameQuery;
216 ifrOffline = false; //we're updating the iframe src - set offline to false so we can check again on next poll.
217 setTimeout(dojo.hitch(this,this.pollLocation),0); //yielded transition to s4 while iframe reloads.
218 return;
219 }else if(!ifrOffline){
220 // s3 (iframe location changed via back/forward button), set main window url and transition to s1.
221 location.href = "#" + iframeLoc.search.substring(1);
222 resetState();
223 _dispatchEvent();
224 }
225 }
226 setTimeout(dojo.hitch(this,this.pollLocation), _pollFrequency);
81bea17a 227 };
a089699c
AD
228 resetState(); // initialize state (transition to s1)
229 setTimeout(dojo.hitch(this,this.pollLocation), _pollFrequency);
230 }
231 dojo.addOnLoad(function(){
232 if("onhashchange" in dojo.global && (!dojo.isIE || (dojo.isIE >= 8 && document.compatMode != "BackCompat"))){ //need this IE browser test because "onhashchange" exists in IE8 in IE7 mode
81bea17a 233 _connect = dojo.connect(dojo.global,"onhashchange",_dispatchEvent);
a089699c
AD
234 }else{
235 if(document.addEventListener){ // Non-IE
236 _recentHash = _getHash();
237 setInterval(_pollLocation, _pollFrequency); //Poll the window location for changes
238 }else if(document.attachEvent){ // IE7-
239 //Use hidden iframe in versions of IE that don't have onhashchange event
240 _ieUriMonitor = new IEUriMonitor();
81bea17a 241 }
a089699c
AD
242 // else non-supported browser, do nothing.
243 }
244 });
2f01fe57 245})();
a089699c 246
2f01fe57 247}