1 /** 2 * @fileOverview event-hashchange 3 * @author yiminghe@gmail.com , xiaomacji@gmail.com 4 */ 5 KISSY.add('event/hashchange', function (S, Event, DOM, UA, special) { 6 7 var win = S.Env.host, 8 doc = win.document, 9 docMode = doc['documentMode'], 10 ie = docMode || UA['ie'], 11 HASH_CHANGE = 'hashchange'; 12 13 // ie8 支持 hashchange 14 // 但IE8以上切换浏览器模式到IE7(兼容模式), 15 // 会导致 'onhashchange' in window === true,但是不触发事件 16 17 // 1. 不支持 hashchange 事件,支持 hash 历史导航(opera??):定时器监控 18 // 2. 不支持 hashchange 事件,不支持 hash 历史导航(ie67) : iframe + 定时器 19 if ((!( 'on' + HASH_CHANGE in win)) || ie && ie < 8) { 20 21 function getIframeDoc(iframe) { 22 return iframe.contentWindow.document; 23 } 24 25 var POLL_INTERVAL = 50, 26 IFRAME_TEMPLATE = "<html><head><title>" + (doc.title || "") + 27 " - {hash}</title>{head}</head><body>{hash}</body></html>", 28 29 getHash = function () { 30 // 不能 location.hash 31 // http://xx.com/#yy?z=1 32 // ie6 => location.hash = #yy 33 // 其他浏览器 => location.hash = #yy?z=1 34 var url = location.href; 35 return '#' + url.replace(/^[^#]*#?(.*)$/, '$1'); 36 }, 37 38 timer, 39 40 // 用于定时器检测,上次定时器记录的 hash 值 41 lastHash, 42 43 poll = function () { 44 var hash = getHash(); 45 if (hash !== lastHash) { 46 // S.log("poll success :" + hash + " :" + lastHash); 47 // 通知完调用者 hashchange 事件前设置 lastHash 48 lastHash = hash; 49 // ie<8 同步 : hashChange -> onIframeLoad 50 hashChange(hash); 51 } 52 timer = setTimeout(poll, POLL_INTERVAL); 53 }, 54 55 hashChange = ie && ie < 8 ? function (hash) { 56 // S.log("set iframe html :" + hash); 57 var html = S.substitute(IFRAME_TEMPLATE, { 58 // 防止 hash 里有代码造成 xss 59 // 后面通过 innerText,相当于 unEscapeHTML 60 hash:S.escapeHTML(hash), 61 // 一定要加哦 62 head:DOM.isCustomDomain() ? ("<script>document.domain = '" + 63 doc.domain 64 + "';</script>") : "" 65 }), 66 iframeDoc = getIframeDoc(iframe); 67 try { 68 // 写入历史 hash 69 iframeDoc.open(); 70 // 取时要用 innerText !! 71 // 否则取 innerHtml 会因为 escapeHtml 导置 body.innerHTMl != hash 72 iframeDoc.write(html); 73 iframeDoc.close(); 74 // 立刻同步调用 onIframeLoad !!!! 75 } catch (e) { 76 // S.log('doc write error : ', 'error'); 77 // S.log(e, 'error'); 78 } 79 } : function () { 80 notifyHashChange(); 81 }, 82 83 notifyHashChange = function () { 84 // S.log("hash changed : " + getHash()); 85 Event.fire(win, HASH_CHANGE); 86 }, 87 setup = function () { 88 if (!timer) { 89 poll(); 90 } 91 }, 92 tearDown = function () { 93 timer && clearTimeout(timer); 94 timer = 0; 95 }, 96 iframe; 97 98 // ie6, 7, 覆盖一些function 99 if (ie < 8) { 100 101 /* 102 前进后退 : start -> notifyHashChange 103 直接输入 : poll -> hashChange -> start 104 iframe 内容和 url 同步 105 */ 106 setup = function () { 107 if (!iframe) { 108 var iframeSrc = DOM.getEmptyIframeSrc(); 109 //http://www.paciellogroup.com/blog/?p=604 110 iframe = DOM.create('<iframe ' + 111 (iframeSrc ? 'src="' + iframeSrc + '"' : '') + 112 ' style="display: none" ' + 113 'height="0" ' + 114 'width="0" ' + 115 'tabindex="-1" ' + 116 'title="empty"/>'); 117 // Append the iframe to the documentElement rather than the body. 118 // Keeping it outside the body prevents scrolling on the initial 119 // page load 120 DOM.prepend(iframe, doc.documentElement); 121 122 // init,第一次触发,以后都是 onIframeLoad 123 Event.add(iframe, "load", function () { 124 Event.remove(iframe, "load"); 125 // Update the iframe with the initial location hash, if any. This 126 // will create an initial history entry that the user can return to 127 // after the state has changed. 128 hashChange(getHash()); 129 Event.add(iframe, "load", onIframeLoad); 130 poll(); 131 }); 132 133 // Whenever `document.title` changes, update the Iframe's title to 134 // prettify the back/next history menu entries. Since IE sometimes 135 // errors with "Unspecified error" the very first time this is set 136 // (yes, very useful) wrap this with a try/catch block. 137 doc.onpropertychange = function () { 138 try { 139 if (event.propertyName === 'title') { 140 getIframeDoc(iframe).title = 141 doc.title + " - " + getHash(); 142 } 143 } catch (e) { 144 } 145 }; 146 147 /* 148 前进后退 : onIframeLoad -> 触发 149 直接输入 : timer -> hashChange -> onIframeLoad -> 触发 150 触发统一在 start(load) 151 iframe 内容和 url 同步 152 */ 153 function onIframeLoad() { 154 // S.log('iframe start load..'); 155 156 // 2011.11.02 note: 不能用 innerHtml 会自动转义!! 157 // #/x?z=1&y=2 => #/x?z=1&y=2 158 var c = S.trim(getIframeDoc(iframe).body.innerText), 159 ch = getHash(); 160 161 // 后退时不等 162 // 定时器调用 hashChange() 修改 iframe 同步调用过来的(手动改变 location)则相等 163 if (c != ch) { 164 S.log("set loc hash :" + c); 165 location.hash = c; 166 // 使lasthash为 iframe 历史, 不然重新写iframe, 167 // 会导致最新状态(丢失前进状态) 168 169 // 后退则立即触发 hashchange, 170 // 并更新定时器记录的上个 hash 值 171 lastHash = c; 172 } 173 notifyHashChange(); 174 } 175 } 176 }; 177 178 tearDown = function () { 179 timer && clearTimeout(timer); 180 timer = 0; 181 Event.detach(iframe); 182 DOM.remove(iframe); 183 iframe = 0; 184 }; 185 } 186 187 special[HASH_CHANGE] = { 188 setup:function () { 189 if (this !== win) { 190 return; 191 } 192 // 第一次启动 hashchange 时取一下,不能类库载入后立即取 193 // 防止类库嵌入后,手动修改过 hash, 194 lastHash = getHash(); 195 // 不用注册 dom 事件 196 setup(); 197 }, 198 tearDown:function () { 199 if (this !== win) { 200 return; 201 } 202 tearDown(); 203 } 204 }; 205 } 206 }, { 207 requires:["./base", "dom", "ua", "./special"] 208 }); 209 210 /** 211 * 已知 bug : 212 * - ie67 有时后退后取得的 location.hash 不和地址栏一致,导致必须后退两次才能触发 hashchange 213 * 214 * v1 : 2010-12-29 215 * v1.1: 支持非IE,但不支持onhashchange事件的浏览器(例如低版本的firefox、safari) 216 * refer : http://yiminghe.javaeye.com/blog/377867 217 * https://github.com/cowboy/jquery-hashchange 218 */