diff options
Diffstat (limited to 'lib/dojo/hash.js')
-rw-r--r-- | lib/dojo/hash.js | 354 |
1 files changed, 228 insertions, 126 deletions
diff --git a/lib/dojo/hash.js b/lib/dojo/hash.js index b73d37058..6ef74b521 100644 --- a/lib/dojo/hash.js +++ b/lib/dojo/hash.js @@ -5,133 +5,235 @@ */ -if(!dojo._hasResource["dojo.hash"]){ -dojo._hasResource["dojo.hash"]=true; +if(!dojo._hasResource["dojo.hash"]){ //_hasResource checks added by build. Do not use _hasResource directly in your code. +dojo._hasResource["dojo.hash"] = true; dojo.provide("dojo.hash"); +//TODOC: where does this go? +// summary: +// Methods for monitoring and updating the hash in the browser URL. +// +// example: +// dojo.subscribe("/dojo/hashchange", context, callback); +// +// function callback (hashValue){ +// // do something based on the hash value. +// } + (function(){ -dojo.hash=function(_1,_2){ -if(!arguments.length){ -return _3(); -} -if(_1.charAt(0)=="#"){ -_1=_1.substring(1); -} -if(_2){ -_4(_1); -}else{ -location.href="#"+_1; -} -return _1; -}; -var _5=null,_6=null,_7=dojo.config.hashPollFrequency||100; -function _8(_9,_a){ -var i=_9.indexOf(_a); -return (i>=0)?_9.substring(i+1):""; -}; -function _3(){ -return _8(location.href,"#"); -}; -function _b(){ -dojo.publish("/dojo/hashchange",[_3()]); -}; -function _c(){ -if(_3()===_5){ -return; -} -_5=_3(); -_b(); -}; -function _4(_d){ -if(_6){ -if(_6.isTransitioning()){ -setTimeout(dojo.hitch(null,_4,_d),_7); -return; -} -var _e=_6.iframe.location.href; -var _f=_e.indexOf("?"); -_6.iframe.location.replace(_e.substring(0,_f)+"?"+_d); -return; -} -location.replace("#"+_d); -_c(); -}; -function _10(){ -var ifr=document.createElement("iframe"),_11="dojo-hash-iframe",_12=dojo.config.dojoBlankHtmlUrl||dojo.moduleUrl("dojo","resources/blank.html"); -ifr.id=_11; -ifr.src=_12+"?"+_3(); -ifr.style.display="none"; -document.body.appendChild(ifr); -this.iframe=dojo.global[_11]; -var _13,_14,_15,_16,_17,_18=this.iframe.location; -function _19(){ -_5=_3(); -_13=_17?_5:_8(_18.href,"?"); -_14=false; -_15=null; -}; -this.isTransitioning=function(){ -return _14; -}; -this.pollLocation=function(){ -if(!_17){ -try{ -var _1a=_8(_18.href,"?"); -if(document.title!=_16){ -_16=this.iframe.document.title=document.title; -} -} -catch(e){ -_17=true; -console.error("dojo.hash: Error adding history entry. Server unreachable."); -} -} -var _1b=_3(); -if(_14&&_5===_1b){ -if(_17||_1a===_15){ -_19(); -_b(); -}else{ -setTimeout(dojo.hitch(this,this.pollLocation),0); -return; -} -}else{ -if(_5===_1b&&(_17||_13===_1a)){ -}else{ -if(_5!==_1b){ -_5=_1b; -_14=true; -_15=_1b; -ifr.src=_12+"?"+_15; -_17=false; -setTimeout(dojo.hitch(this,this.pollLocation),0); -return; -}else{ -if(!_17){ -location.href="#"+_18.search.substring(1); -_19(); -_b(); -} -} -} -} -setTimeout(dojo.hitch(this,this.pollLocation),_7); -}; -_19(); -setTimeout(dojo.hitch(this,this.pollLocation),_7); -}; -dojo.addOnLoad(function(){ -if("onhashchange" in dojo.global&&(!dojo.isIE||(dojo.isIE>=8&&document.compatMode!="BackCompat"))){ -dojo.connect(dojo.global,"onhashchange",_b); -}else{ -if(document.addEventListener){ -_5=_3(); -setInterval(_c,_7); -}else{ -if(document.attachEvent){ -_6=new _10(); -} -} -} -}); + dojo.hash = function(/* String? */ hash, /* Boolean? */ replace){ + // summary: + // Gets or sets the hash string. + // description: + // Handles getting and setting of location.hash. + // - If no arguments are passed, acts as a getter. + // - If a string is passed, acts as a setter. + // hash: + // String: the hash is set - #string. + // replace: + // Boolean: If true, updates the hash value in the current history + // state instead of creating a new history state. + // returns: + // when used as a getter, returns the current hash string. + // when used as a setter, returns the new hash string. + + // getter + if(!arguments.length){ + return _getHash(); + } + // setter + if(hash.charAt(0) == "#"){ + hash = hash.substring(1); + } + if(replace){ + _replace(hash); + }else{ + location.href = "#" + hash; + } + return hash; // String + } + + // Global vars + var _recentHash = null, + _ieUriMonitor = null, + _pollFrequency = dojo.config.hashPollFrequency || 100; + + //Internal functions + function _getSegment(str, delimiter){ + var i = str.indexOf(delimiter); + return (i >= 0) ? str.substring(i+1) : ""; + } + + function _getHash(){ + return _getSegment(location.href, "#"); + } + + function _dispatchEvent(){ + dojo.publish("/dojo/hashchange", [_getHash()]); + } + + function _pollLocation(){ + if(_getHash() === _recentHash){ + return; + } + _recentHash = _getHash(); + _dispatchEvent(); + } + + function _replace(hash){ + if(_ieUriMonitor){ + if(_ieUriMonitor.isTransitioning()){ + setTimeout(dojo.hitch(null,_replace,hash), _pollFrequency); + return; + } + var href = _ieUriMonitor.iframe.location.href; + var index = href.indexOf('?'); + // main frame will detect and update itself + _ieUriMonitor.iframe.location.replace(href.substring(0, index) + "?" + hash); + return; + } + location.replace("#"+hash); + _pollLocation(); + } + + function IEUriMonitor(){ + // summary: + // Determine if the browser's URI has changed or if the user has pressed the + // back or forward button. If so, call _dispatchEvent. + // + // description: + // IE doesn't add changes to the URI's hash into the history unless the hash + // value corresponds to an actual named anchor in the document. To get around + // this IE difference, we use a background IFrame to maintain a back-forward + // history, by updating the IFrame's query string to correspond to the + // value of the main browser location's hash value. + // + // E.g. if the value of the browser window's location changes to + // + // #action=someAction + // + // ... then we'd update the IFrame's source to: + // + // ?action=someAction + // + // This design leads to a somewhat complex state machine, which is + // described below: + // + // s1: Stable state - neither the window's location has changed nor + // has the IFrame's location. Note that this is the 99.9% case, so + // we optimize for it. + // Transitions: s1, s2, s3 + // s2: Window's location changed - when a user clicks a hyperlink or + // code programmatically changes the window's URI. + // Transitions: s4 + // s3: Iframe's location changed as a result of user pressing back or + // forward - when the user presses back or forward, the location of + // the background's iframe changes to the previous or next value in + // its history. + // Transitions: s1 + // s4: IEUriMonitor has programmatically changed the location of the + // background iframe, but it's location hasn't yet changed. In this + // case we do nothing because we need to wait for the iframe's + // location to reflect its actual state. + // Transitions: s4, s5 + // s5: IEUriMonitor has programmatically changed the location of the + // background iframe, and the iframe's location has caught up with + // reality. In this case we need to transition to s1. + // Transitions: s1 + // + // The hashchange event is always dispatched on the transition back to s1. + // + + // create and append iframe + var ifr = document.createElement("iframe"), + IFRAME_ID = "dojo-hash-iframe", + ifrSrc = dojo.config.dojoBlankHtmlUrl || dojo.moduleUrl("dojo", "resources/blank.html"); + ifr.id = IFRAME_ID; + ifr.src = ifrSrc + "?" + _getHash(); + ifr.style.display = "none"; + document.body.appendChild(ifr); + + this.iframe = dojo.global[IFRAME_ID]; + var recentIframeQuery, transitioning, expectedIFrameQuery, docTitle, ifrOffline, + iframeLoc = this.iframe.location; + + function resetState(){ + _recentHash = _getHash(); + recentIframeQuery = ifrOffline ? _recentHash : _getSegment(iframeLoc.href, "?"); + transitioning = false; + expectedIFrameQuery = null; + } + + this.isTransitioning = function(){ + return transitioning; + } + + this.pollLocation = function(){ + if(!ifrOffline) { + try{ + //see if we can access the iframe's location without a permission denied error + var iframeSearch = _getSegment(iframeLoc.href, "?"); + //good, the iframe is same origin (no thrown exception) + if(document.title != docTitle){ //sync title of main window with title of iframe. + docTitle = this.iframe.document.title = document.title; + } + }catch(e){ + //permission denied - server cannot be reached. + ifrOffline = true; + console.error("dojo.hash: Error adding history entry. Server unreachable."); + } + } + var hash = _getHash(); + if(transitioning && _recentHash === hash){ + // we're in an iframe transition (s4 or s5) + if(ifrOffline || iframeSearch === expectedIFrameQuery){ + // s5 (iframe caught up to main window or iframe offline), transition back to s1 + resetState(); + _dispatchEvent(); + }else{ + // s4 (waiting for iframe to catch up to main window) + setTimeout(dojo.hitch(this,this.pollLocation),0); + return; + } + }else if(_recentHash === hash && (ifrOffline || recentIframeQuery === iframeSearch)){ + // we're in stable state (s1, iframe query == main window hash), do nothing + }else{ + // the user has initiated a URL change somehow. + // sync iframe query <-> main window hash + if(_recentHash !== hash){ + // s2 (main window location changed), set iframe url and transition to s4 + _recentHash = hash; + transitioning = true; + expectedIFrameQuery = hash; + ifr.src = ifrSrc + "?" + expectedIFrameQuery; + ifrOffline = false; //we're updating the iframe src - set offline to false so we can check again on next poll. + setTimeout(dojo.hitch(this,this.pollLocation),0); //yielded transition to s4 while iframe reloads. + return; + }else if(!ifrOffline){ + // s3 (iframe location changed via back/forward button), set main window url and transition to s1. + location.href = "#" + iframeLoc.search.substring(1); + resetState(); + _dispatchEvent(); + } + } + setTimeout(dojo.hitch(this,this.pollLocation), _pollFrequency); + } + resetState(); // initialize state (transition to s1) + setTimeout(dojo.hitch(this,this.pollLocation), _pollFrequency); + } + dojo.addOnLoad(function(){ + 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 + dojo.connect(dojo.global,"onhashchange",_dispatchEvent); + }else{ + if(document.addEventListener){ // Non-IE + _recentHash = _getHash(); + setInterval(_pollLocation, _pollFrequency); //Poll the window location for changes + }else if(document.attachEvent){ // IE7- + //Use hidden iframe in versions of IE that don't have onhashchange event + _ieUriMonitor = new IEUriMonitor(); + } + // else non-supported browser, do nothing. + } + }); })(); + } |