hash.js 8.4 KB

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