source: Dev/trunk/src/client/dojo/hash.js @ 531

Last change on this file since 531 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

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