source: Dev/branches/rest-dojo-ui/client/dojo/hash.js @ 263

Last change on this file since 263 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

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