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