[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 /* 2 YUI 3.17.2 (build 9c3c78e) 3 Copyright 2014 Yahoo! Inc. All rights reserved. 4 Licensed under the BSD License. 5 http://yuilibrary.com/license/ 6 */ 7 8 YUI.add('history-hash', function (Y, NAME) { 9 10 /** 11 * Provides browser history management backed by 12 * <code>window.location.hash</code>, as well as convenience methods for working 13 * with the location hash and a synthetic <code>hashchange</code> event that 14 * normalizes differences across browsers. 15 * 16 * @module history 17 * @submodule history-hash 18 * @since 3.2.0 19 * @class HistoryHash 20 * @extends HistoryBase 21 * @constructor 22 * @param {Object} config (optional) Configuration object. See the HistoryBase 23 * documentation for details. 24 */ 25 26 var HistoryBase = Y.HistoryBase, 27 Lang = Y.Lang, 28 YArray = Y.Array, 29 YObject = Y.Object, 30 GlobalEnv = YUI.namespace('Env.HistoryHash'), 31 32 SRC_HASH = 'hash', 33 34 hashNotifiers, 35 oldHash, 36 oldUrl, 37 win = Y.config.win, 38 useHistoryHTML5 = Y.config.useHistoryHTML5; 39 40 function HistoryHash() { 41 HistoryHash.superclass.constructor.apply(this, arguments); 42 } 43 44 Y.extend(HistoryHash, HistoryBase, { 45 // -- Initialization ------------------------------------------------------- 46 _init: function (config) { 47 var bookmarkedState = HistoryHash.parseHash(); 48 49 // If an initialState was provided, merge the bookmarked state into it 50 // (the bookmarked state wins). 51 config = config || {}; 52 53 this._initialState = config.initialState ? 54 Y.merge(config.initialState, bookmarkedState) : bookmarkedState; 55 56 // Subscribe to the synthetic hashchange event (defined below) to handle 57 // changes. 58 Y.after('hashchange', Y.bind(this._afterHashChange, this), win); 59 60 HistoryHash.superclass._init.apply(this, arguments); 61 }, 62 63 // -- Protected Methods ---------------------------------------------------- 64 _change: function (src, state, options) { 65 // Stringify all values to ensure that comparisons don't fail after 66 // they're coerced to strings in the location hash. 67 YObject.each(state, function (value, key) { 68 if (Lang.isValue(value)) { 69 state[key] = value.toString(); 70 } 71 }); 72 73 return HistoryHash.superclass._change.call(this, src, state, options); 74 }, 75 76 _storeState: function (src, newState) { 77 var decode = HistoryHash.decode, 78 newHash = HistoryHash.createHash(newState); 79 80 HistoryHash.superclass._storeState.apply(this, arguments); 81 82 // Update the location hash with the changes, but only if the new hash 83 // actually differs from the current hash (this avoids creating multiple 84 // history entries for a single state). 85 // 86 // We always compare decoded hashes, since it's possible that the hash 87 // could be set incorrectly to a non-encoded value outside of 88 // HistoryHash. 89 if (src !== SRC_HASH && decode(HistoryHash.getHash()) !== decode(newHash)) { 90 HistoryHash[src === HistoryBase.SRC_REPLACE ? 'replaceHash' : 'setHash'](newHash); 91 } 92 }, 93 94 // -- Protected Event Handlers --------------------------------------------- 95 96 /** 97 * Handler for hashchange events. 98 * 99 * @method _afterHashChange 100 * @param {Event} e 101 * @protected 102 */ 103 _afterHashChange: function (e) { 104 this._resolveChanges(SRC_HASH, HistoryHash.parseHash(e.newHash), {}); 105 } 106 }, { 107 // -- Public Static Properties --------------------------------------------- 108 NAME: 'historyHash', 109 110 /** 111 * Constant used to identify state changes originating from 112 * <code>hashchange</code> events. 113 * 114 * @property SRC_HASH 115 * @type String 116 * @static 117 * @final 118 */ 119 SRC_HASH: SRC_HASH, 120 121 /** 122 * <p> 123 * Prefix to prepend when setting the hash fragment. For example, if the 124 * prefix is <code>!</code> and the hash fragment is set to 125 * <code>#foo=bar&baz=quux</code>, the final hash fragment in the URL will 126 * become <code>#!foo=bar&baz=quux</code>. This can be used to help make an 127 * Ajax application crawlable in accordance with Google's guidelines at 128 * <a href="http://code.google.com/web/ajaxcrawling/">http://code.google.com/web/ajaxcrawling/</a>. 129 * </p> 130 * 131 * <p> 132 * Note that this prefix applies to all HistoryHash instances. It's not 133 * possible for individual instances to use their own prefixes since they 134 * all operate on the same URL. 135 * </p> 136 * 137 * @property hashPrefix 138 * @type String 139 * @default '' 140 * @static 141 */ 142 hashPrefix: '', 143 144 // -- Protected Static Properties ------------------------------------------ 145 146 /** 147 * Regular expression used to parse location hash/query strings. 148 * 149 * @property _REGEX_HASH 150 * @type RegExp 151 * @protected 152 * @static 153 * @final 154 */ 155 _REGEX_HASH: /([^\?#&=]+)=?([^&=]*)/g, 156 157 // -- Public Static Methods ------------------------------------------------ 158 159 /** 160 * Creates a location hash string from the specified object of key/value 161 * pairs. 162 * 163 * @method createHash 164 * @param {Object} params object of key/value parameter pairs 165 * @return {String} location hash string 166 * @static 167 */ 168 createHash: function (params) { 169 var encode = HistoryHash.encode, 170 hash = []; 171 172 YObject.each(params, function (value, key) { 173 if (Lang.isValue(value)) { 174 hash.push(encode(key) + '=' + encode(value)); 175 } 176 }); 177 178 return hash.join('&'); 179 }, 180 181 /** 182 * Wrapper around <code>decodeURIComponent()</code> that also converts + 183 * chars into spaces. 184 * 185 * @method decode 186 * @param {String} string string to decode 187 * @return {String} decoded string 188 * @static 189 */ 190 decode: function (string) { 191 return decodeURIComponent(string.replace(/\+/g, ' ')); 192 }, 193 194 /** 195 * Wrapper around <code>encodeURIComponent()</code> that converts spaces to 196 * + chars. 197 * 198 * @method encode 199 * @param {String} string string to encode 200 * @return {String} encoded string 201 * @static 202 */ 203 encode: function (string) { 204 return encodeURIComponent(string).replace(/%20/g, '+'); 205 }, 206 207 /** 208 * Gets the raw (not decoded) current location hash, minus the preceding '#' 209 * character and the hashPrefix (if one is set). 210 * 211 * @method getHash 212 * @return {String} current location hash 213 * @static 214 */ 215 getHash: (Y.UA.gecko ? function () { 216 // Gecko's window.location.hash returns a decoded string and we want all 217 // encoding untouched, so we need to get the hash value from 218 // window.location.href instead. We have to use UA sniffing rather than 219 // feature detection, since the only way to detect this would be to 220 // actually change the hash. 221 var location = Y.getLocation(), 222 matches = /#(.*)$/.exec(location.href), 223 hash = matches && matches[1] || '', 224 prefix = HistoryHash.hashPrefix; 225 226 return prefix && hash.indexOf(prefix) === 0 ? 227 hash.replace(prefix, '') : hash; 228 } : function () { 229 var location = Y.getLocation(), 230 hash = location.hash.substring(1), 231 prefix = HistoryHash.hashPrefix; 232 233 // Slight code duplication here, but execution speed is of the essence 234 // since getHash() is called every 50ms to poll for changes in browsers 235 // that don't support native onhashchange. An additional function call 236 // would add unnecessary overhead. 237 return prefix && hash.indexOf(prefix) === 0 ? 238 hash.replace(prefix, '') : hash; 239 }), 240 241 /** 242 * Gets the current bookmarkable URL. 243 * 244 * @method getUrl 245 * @return {String} current bookmarkable URL 246 * @static 247 */ 248 getUrl: function () { 249 return location.href; 250 }, 251 252 /** 253 * Parses a location hash string into an object of key/value parameter 254 * pairs. If <i>hash</i> is not specified, the current location hash will 255 * be used. 256 * 257 * @method parseHash 258 * @param {String} hash (optional) location hash string 259 * @return {Object} object of parsed key/value parameter pairs 260 * @static 261 */ 262 parseHash: function (hash) { 263 var decode = HistoryHash.decode, 264 i, 265 len, 266 match, 267 matches, 268 param, 269 params = {}, 270 prefix = HistoryHash.hashPrefix, 271 prefixIndex; 272 273 hash = Lang.isValue(hash) ? hash : HistoryHash.getHash(); 274 275 if (prefix) { 276 prefixIndex = hash.indexOf(prefix); 277 278 if (prefixIndex === 0 || (prefixIndex === 1 && hash.charAt(0) === '#')) { 279 hash = hash.replace(prefix, ''); 280 } 281 } 282 283 matches = hash.match(HistoryHash._REGEX_HASH) || []; 284 285 for (i = 0, len = matches.length; i < len; ++i) { 286 match = matches[i]; 287 288 param = match.split('='); 289 290 if (param.length > 1) { 291 params[decode(param[0])] = decode(param[1]); 292 } else { 293 params[decode(match)] = ''; 294 } 295 } 296 297 return params; 298 }, 299 300 /** 301 * Replaces the browser's current location hash with the specified hash 302 * and removes all forward navigation states, without creating a new browser 303 * history entry. Automatically prepends the <code>hashPrefix</code> if one 304 * is set. 305 * 306 * @method replaceHash 307 * @param {String} hash new location hash 308 * @static 309 */ 310 replaceHash: function (hash) { 311 var location = Y.getLocation(), 312 base = location.href.replace(/#.*$/, ''); 313 314 if (hash.charAt(0) === '#') { 315 hash = hash.substring(1); 316 } 317 318 location.replace(base + '#' + (HistoryHash.hashPrefix || '') + hash); 319 }, 320 321 /** 322 * Sets the browser's location hash to the specified string. Automatically 323 * prepends the <code>hashPrefix</code> if one is set. 324 * 325 * @method setHash 326 * @param {String} hash new location hash 327 * @static 328 */ 329 setHash: function (hash) { 330 var location = Y.getLocation(); 331 332 if (hash.charAt(0) === '#') { 333 hash = hash.substring(1); 334 } 335 336 location.hash = (HistoryHash.hashPrefix || '') + hash; 337 } 338 }); 339 340 // -- Synthetic hashchange Event ----------------------------------------------- 341 342 // TODO: YUIDoc currently doesn't provide a good way to document synthetic DOM 343 // events. For now, we're just documenting the hashchange event on the YUI 344 // object, which is about the best we can do until enhancements are made to 345 // YUIDoc. 346 347 /** 348 Synthetic <code>window.onhashchange</code> event that normalizes differences 349 across browsers and provides support for browsers that don't natively support 350 <code>onhashchange</code>. 351 352 This event is provided by the <code>history-hash</code> module. 353 354 @example 355 356 YUI().use('history-hash', function (Y) { 357 Y.on('hashchange', function (e) { 358 // Handle hashchange events on the current window. 359 }, Y.config.win); 360 }); 361 362 @event hashchange 363 @param {EventFacade} e Event facade with the following additional 364 properties: 365 366 <dl> 367 <dt>oldHash</dt> 368 <dd> 369 Previous hash fragment value before the change. 370 </dd> 371 372 <dt>oldUrl</dt> 373 <dd> 374 Previous URL (including the hash fragment) before the change. 375 </dd> 376 377 <dt>newHash</dt> 378 <dd> 379 New hash fragment value after the change. 380 </dd> 381 382 <dt>newUrl</dt> 383 <dd> 384 New URL (including the hash fragment) after the change. 385 </dd> 386 </dl> 387 @for YUI 388 @since 3.2.0 389 **/ 390 391 hashNotifiers = GlobalEnv._notifiers; 392 393 if (!hashNotifiers) { 394 hashNotifiers = GlobalEnv._notifiers = []; 395 } 396 397 Y.Event.define('hashchange', { 398 on: function (node, subscriber, notifier) { 399 // Ignore this subscription if the node is anything other than the 400 // window or document body, since those are the only elements that 401 // should support the hashchange event. Note that the body could also be 402 // a frameset, but that's okay since framesets support hashchange too. 403 if (node.compareTo(win) || node.compareTo(Y.config.doc.body)) { 404 hashNotifiers.push(notifier); 405 } 406 }, 407 408 detach: function (node, subscriber, notifier) { 409 var index = YArray.indexOf(hashNotifiers, notifier); 410 411 if (index !== -1) { 412 hashNotifiers.splice(index, 1); 413 } 414 } 415 }); 416 417 oldHash = HistoryHash.getHash(); 418 oldUrl = HistoryHash.getUrl(); 419 420 if (HistoryBase.nativeHashChange) { 421 // Wrap the browser's native hashchange event if there's not already a 422 // global listener. 423 if (!GlobalEnv._hashHandle) { 424 GlobalEnv._hashHandle = Y.Event.attach('hashchange', function (e) { 425 var newHash = HistoryHash.getHash(), 426 newUrl = HistoryHash.getUrl(); 427 428 // Iterate over a copy of the hashNotifiers array since a subscriber 429 // could detach during iteration and cause the array to be re-indexed. 430 YArray.each(hashNotifiers.concat(), function (notifier) { 431 notifier.fire({ 432 _event : e, 433 oldHash: oldHash, 434 oldUrl : oldUrl, 435 newHash: newHash, 436 newUrl : newUrl 437 }); 438 }); 439 440 oldHash = newHash; 441 oldUrl = newUrl; 442 }, win); 443 } 444 } else { 445 // Begin polling for location hash changes if there's not already a global 446 // poll running. 447 if (!GlobalEnv._hashPoll) { 448 GlobalEnv._hashPoll = Y.later(50, null, function () { 449 var newHash = HistoryHash.getHash(), 450 facade, newUrl; 451 452 if (oldHash !== newHash) { 453 newUrl = HistoryHash.getUrl(); 454 455 facade = { 456 oldHash: oldHash, 457 oldUrl : oldUrl, 458 newHash: newHash, 459 newUrl : newUrl 460 }; 461 462 oldHash = newHash; 463 oldUrl = newUrl; 464 465 YArray.each(hashNotifiers.concat(), function (notifier) { 466 notifier.fire(facade); 467 }); 468 } 469 }, null, true); 470 } 471 } 472 473 Y.HistoryHash = HistoryHash; 474 475 // HistoryHash will never win over HistoryHTML5 unless useHistoryHTML5 is false. 476 if (useHistoryHTML5 === false || (!Y.History && useHistoryHTML5 !== true && 477 (!HistoryBase.html5 || !Y.HistoryHTML5))) { 478 Y.History = HistoryHash; 479 } 480 481 482 }, '3.17.2', {"requires": ["event-synthetic", "history-base", "yui-later"]});
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Thu Aug 11 10:00:09 2016 | Cross-referenced by PHPXref 0.7.1 |