hash.js 8.2 KB

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