[ Index ] |
PHP Cross Reference of Unnamed Project |
[Summary view] [Print] [Text view]
1 YUI.add('yui2-history', function(Y) { 2 var YAHOO = Y.YUI2; 3 /* 4 Copyright (c) 2011, Yahoo! Inc. All rights reserved. 5 Code licensed under the BSD License: 6 http://developer.yahoo.com/yui/license.html 7 version: 2.9.0 8 */ 9 /** 10 * The Browser History Manager provides the ability to use the back/forward 11 * navigation buttons in a DHTML application. It also allows a DHTML 12 * application to be bookmarked in a specific state. 13 * 14 * This library requires the following static markup: 15 * 16 * <iframe id="yui-history-iframe" src="path-to-real-asset-in-same-domain"></iframe> 17 * <input id="yui-history-field" type="hidden"> 18 * 19 * @module history 20 * @requires yahoo,event 21 * @namespace YAHOO.util 22 * @title Browser History Manager 23 */ 24 25 /** 26 * The History class provides the ability to use the back/forward navigation 27 * buttons in a DHTML application. It also allows a DHTML application to 28 * be bookmarked in a specific state. 29 * 30 * @class History 31 * @constructor 32 */ 33 YAHOO.util.History = (function () { 34 35 /** 36 * Our hidden IFrame used to store the browsing history. 37 * 38 * @property _histFrame 39 * @type HTMLIFrameElement 40 * @default null 41 * @private 42 */ 43 var _histFrame = null; 44 45 /** 46 * INPUT field (with type="hidden" or type="text") or TEXTAREA. 47 * This field keeps the value of the initial state, current state 48 * the list of all states across pages within a single browser session. 49 * 50 * @property _stateField 51 * @type HTMLInputElement|HTMLTextAreaElement 52 * @default null 53 * @private 54 */ 55 var _stateField = null; 56 57 /** 58 * Flag used to tell whether YAHOO.util.History.initialize has been called. 59 * 60 * @property _initialized 61 * @type boolean 62 * @default false 63 * @private 64 */ 65 var _initialized = false; 66 67 /** 68 * List of registered modules. 69 * 70 * @property _modules 71 * @type array 72 * @default [] 73 * @private 74 */ 75 var _modules = []; 76 77 /** 78 * List of fully qualified states. This is used only by Safari. 79 * 80 * @property _fqstates 81 * @type array 82 * @default [] 83 * @private 84 */ 85 var _fqstates = []; 86 87 /** 88 * location.hash is a bit buggy on Opera. I have seen instances where 89 * navigating the history using the back/forward buttons, and hence 90 * changing the URL, would not change location.hash. That's ok, the 91 * implementation of an equivalent is trivial. 92 * 93 * @method _getHash 94 * @return {string} The hash portion of the document's location 95 * @private 96 */ 97 function _getHash() { 98 var i, href; 99 href = self.location.href; 100 i = href.indexOf("#"); 101 return i >= 0 ? href.substr(i + 1) : null; 102 } 103 104 /** 105 * Stores all the registered modules' initial state and current state. 106 * On Safari, we also store all the fully qualified states visited by 107 * the application within a single browser session. The storage takes 108 * place in the form field specified during initialization. 109 * 110 * @method _storeStates 111 * @private 112 */ 113 function _storeStates() { 114 115 var moduleName, moduleObj, initialStates = [], currentStates = []; 116 117 for (moduleName in _modules) { 118 if (YAHOO.lang.hasOwnProperty(_modules, moduleName)) { 119 moduleObj = _modules[moduleName]; 120 initialStates.push(moduleName + "=" + moduleObj.initialState); 121 currentStates.push(moduleName + "=" + moduleObj.currentState); 122 } 123 } 124 125 _stateField.value = initialStates.join("&") + "|" + currentStates.join("&"); 126 } 127 128 /** 129 * Sets the new currentState attribute of all modules depending on the new 130 * fully qualified state. Also notifies the modules which current state has 131 * changed. 132 * 133 * @method _handleFQStateChange 134 * @param {string} fqstate Fully qualified state 135 * @private 136 */ 137 function _handleFQStateChange(fqstate) { 138 139 var i, len, moduleName, moduleObj, modules, states, tokens, currentState; 140 141 if (!fqstate) { 142 // Notifies all modules 143 for (moduleName in _modules) { 144 if (YAHOO.lang.hasOwnProperty(_modules, moduleName)) { 145 moduleObj = _modules[moduleName]; 146 moduleObj.currentState = moduleObj.initialState; 147 moduleObj.onStateChange(_decode(moduleObj.currentState)); 148 } 149 } 150 return; 151 } 152 153 modules = []; 154 states = fqstate.split("&"); 155 for (i = 0, len = states.length; i < len; i++) { 156 tokens = states[i].split("="); 157 if (tokens.length === 2) { 158 moduleName = tokens[0]; 159 currentState = tokens[1]; 160 modules[moduleName] = currentState; 161 } 162 } 163 164 for (moduleName in _modules) { 165 if (YAHOO.lang.hasOwnProperty(_modules, moduleName)) { 166 moduleObj = _modules[moduleName]; 167 currentState = modules[moduleName]; 168 if (!currentState || moduleObj.currentState !== currentState) { 169 moduleObj.currentState = typeof currentState === 'undefined' ? moduleObj.initialState : currentState; 170 moduleObj.onStateChange(_decode(moduleObj.currentState)); 171 } 172 } 173 } 174 } 175 176 /** 177 * Update the IFrame with our new state. 178 * 179 * @method _updateIFrame 180 * @private 181 * @return {boolean} true if successful. false otherwise. 182 */ 183 function _updateIFrame (fqstate) { 184 185 var html, doc; 186 187 html = '<html><body><div id="state">' + 188 YAHOO.lang.escapeHTML(fqstate) + 189 '</div></body></html>'; 190 191 try { 192 doc = _histFrame.contentWindow.document; 193 doc.open(); 194 doc.write(html); 195 doc.close(); 196 return true; 197 } catch (e) { 198 return false; 199 } 200 } 201 202 /** 203 * Periodically checks whether our internal IFrame is ready to be used. 204 * 205 * @method _checkIframeLoaded 206 * @private 207 */ 208 function _checkIframeLoaded() { 209 210 var doc, elem, fqstate, hash; 211 212 if (!_histFrame.contentWindow || !_histFrame.contentWindow.document) { 213 // Check again in 10 msec... 214 setTimeout(_checkIframeLoaded, 10); 215 return; 216 } 217 218 // Start the thread that will have the responsibility to 219 // periodically check whether a navigate operation has been 220 // requested on the main window. This will happen when 221 // YAHOO.util.History.navigate has been called or after 222 // the user has hit the back/forward button. 223 224 doc = _histFrame.contentWindow.document; 225 elem = doc.getElementById("state"); 226 // We must use innerText, and not innerHTML because our string contains 227 // the "&" character (which would end up being escaped as "&") and 228 // the string comparison would fail... 229 fqstate = elem ? elem.innerText : null; 230 231 hash = _getHash(); 232 233 setInterval(function () { 234 235 var newfqstate, states, moduleName, moduleObj, newHash, historyLength; 236 237 doc = _histFrame.contentWindow.document; 238 elem = doc.getElementById("state"); 239 // See my comment above about using innerText instead of innerHTML... 240 newfqstate = elem ? elem.innerText : null; 241 242 newHash = _getHash(); 243 244 if (newfqstate !== fqstate) { 245 246 fqstate = newfqstate; 247 _handleFQStateChange(fqstate); 248 249 if (!fqstate) { 250 states = []; 251 for (moduleName in _modules) { 252 if (YAHOO.lang.hasOwnProperty(_modules, moduleName)) { 253 moduleObj = _modules[moduleName]; 254 states.push(moduleName + "=" + moduleObj.initialState); 255 } 256 } 257 newHash = states.join("&"); 258 } else { 259 newHash = fqstate; 260 } 261 262 // Allow the state to be bookmarked by setting the top window's 263 // URL fragment identifier. Note that here, we are on IE, and 264 // IE does not touch the browser history when setting the hash 265 // (unlike all the other browsers). I used to write: 266 // self.location.replace( "#" + hash ); 267 // but this had a side effect when the page was not the top frame. 268 self.location.hash = newHash; 269 hash = newHash; 270 271 _storeStates(); 272 273 } else if (newHash !== hash) { 274 275 // The hash has changed. The user might have clicked on a link, 276 // or modified the URL directly, or opened the same application 277 // bookmarked in a specific state using a bookmark. However, we 278 // know the hash change was not caused by a hit on the back or 279 // forward buttons, or by a call to navigate() (because it would 280 // have been handled above) We must handle these cases, which is 281 // why we also need to keep track of hash changes on IE! 282 283 // Note that IE6 has some major issues with this kind of user 284 // interaction (the history stack gets completely messed up) 285 // but it seems to work fine on IE7. 286 287 hash = newHash; 288 289 // Now, store a new history entry. The following will cause the 290 // code above to execute, doing all the dirty work for us... 291 _updateIFrame(newHash); 292 } 293 294 }, 50); 295 296 _initialized = true; 297 YAHOO.util.History.onLoadEvent.fire(); 298 } 299 300 /** 301 * Finish up the initialization of the Browser History Manager. 302 * 303 * @method _initialize 304 * @private 305 */ 306 function _initialize() { 307 308 var i, len, parts, tokens, moduleName, moduleObj, initialStates, initialState, currentStates, currentState, counter, hash; 309 310 // Decode the content of our storage field... 311 parts = _stateField.value.split("|"); 312 313 if (parts.length > 1) { 314 315 initialStates = parts[0].split("&"); 316 for (i = 0, len = initialStates.length; i < len; i++) { 317 tokens = initialStates[i].split("="); 318 if (tokens.length === 2) { 319 moduleName = tokens[0]; 320 initialState = tokens[1]; 321 322 moduleObj = YAHOO.lang.hasOwnProperty(_modules, moduleName) 323 && _modules[moduleName]; 324 325 if (moduleObj) { 326 moduleObj.initialState = initialState; 327 } 328 } 329 } 330 331 currentStates = parts[1].split("&"); 332 for (i = 0, len = currentStates.length; i < len; i++) { 333 tokens = currentStates[i].split("="); 334 if (tokens.length >= 2) { 335 moduleName = tokens[0]; 336 currentState = tokens[1]; 337 338 moduleObj = YAHOO.lang.hasOwnProperty(_modules, moduleName) 339 && _modules[moduleName]; 340 341 if (moduleObj) { 342 moduleObj.currentState = currentState; 343 } 344 } 345 } 346 } 347 348 if (parts.length > 2) { 349 _fqstates = parts[2].split(","); 350 } 351 352 if (YAHOO.env.ua.ie) { 353 354 if (typeof document.documentMode === "undefined" || document.documentMode < 8) { 355 356 // IE < 8 or IE8 in quirks mode or IE7 standards mode 357 _checkIframeLoaded(); 358 359 } else { 360 361 // IE8 in IE8 standards mode 362 YAHOO.util.Event.on(top, "hashchange", 363 function () { 364 var hash = _getHash(); 365 _handleFQStateChange(hash); 366 _storeStates(); 367 }); 368 369 _initialized = true; 370 YAHOO.util.History.onLoadEvent.fire(); 371 372 } 373 374 } else { 375 376 // Start the thread that will have the responsibility to 377 // periodically check whether a navigate operation has been 378 // requested on the main window. This will happen when 379 // YAHOO.util.History.navigate has been called or after 380 // the user has hit the back/forward button. 381 382 // On Gecko and Opera, we just need to watch the hash... 383 hash = _getHash(); 384 385 setInterval(function () { 386 387 var state, newHash, newCounter; 388 389 newHash = _getHash(); 390 if (newHash !== hash) { 391 hash = newHash; 392 _handleFQStateChange(hash); 393 _storeStates(); 394 } 395 396 }, 50); 397 398 _initialized = true; 399 YAHOO.util.History.onLoadEvent.fire(); 400 } 401 } 402 403 /** 404 * Wrapper around <code>decodeURIComponent()</code> that also converts + 405 * chars into spaces. 406 * 407 * @method _decode 408 * @param {String} string string to decode 409 * @return {String} decoded string 410 * @private 411 * @since 2.9.0 412 */ 413 function _decode(string) { 414 return decodeURIComponent(string.replace(/\+/g, ' ')); 415 } 416 417 /** 418 * Wrapper around <code>encodeURIComponent()</code> that converts spaces to 419 * + chars. 420 * 421 * @method _encode 422 * @param {String} string string to encode 423 * @return {String} encoded string 424 * @private 425 * @since 2.9.0 426 */ 427 function _encode(string) { 428 return encodeURIComponent(string).replace(/%20/g, '+'); 429 } 430 431 return { 432 433 /** 434 * Fired when the Browser History Manager is ready. If you subscribe to 435 * this event after the Browser History Manager has been initialized, 436 * it will not fire. Therefore, it is recommended to use the onReady 437 * method instead. 438 * 439 * @event onLoadEvent 440 * @see onReady 441 */ 442 onLoadEvent: new YAHOO.util.CustomEvent("onLoad"), 443 444 /** 445 * Executes the supplied callback when the Browser History Manager is 446 * ready. This will execute immediately if called after the Browser 447 * History Manager onLoad event has fired. 448 * 449 * @method onReady 450 * @param {function} fn what to execute when the Browser History Manager is ready. 451 * @param {object} obj an optional object to be passed back as a parameter to fn. 452 * @param {boolean|object} overrideContext If true, the obj passed in becomes fn's execution scope. 453 * @see onLoadEvent 454 */ 455 onReady: function (fn, obj, overrideContext) { 456 457 if (_initialized) { 458 459 setTimeout(function () { 460 var ctx = window; 461 if (overrideContext) { 462 if (overrideContext === true) { 463 ctx = obj; 464 } else { 465 ctx = overrideContext; 466 } 467 } 468 fn.call(ctx, "onLoad", [], obj); 469 }, 0); 470 471 } else { 472 473 YAHOO.util.History.onLoadEvent.subscribe(fn, obj, overrideContext); 474 475 } 476 }, 477 478 /** 479 * Registers a new module. 480 * 481 * @method register 482 * @param {string} module Non-empty string uniquely identifying the 483 * module you wish to register. 484 * @param {string} initialState The initial state of the specified 485 * module corresponding to its earliest history entry. 486 * @param {function} onStateChange Callback called when the 487 * state of the specified module has changed. 488 * @param {object} obj An arbitrary object that will be passed as a 489 * parameter to the handler. 490 * @param {boolean} overrideContext If true, the obj passed in becomes the 491 * execution scope of the listener. 492 */ 493 register: function (module, initialState, onStateChange, obj, overrideContext) { 494 495 var scope, wrappedFn; 496 497 if (typeof module !== "string" || YAHOO.lang.trim(module) === "" || 498 typeof initialState !== "string" || 499 typeof onStateChange !== "function") { 500 throw new Error("Missing or invalid argument"); 501 } 502 503 if (YAHOO.lang.hasOwnProperty(_modules, module)) { 504 // Here, we used to throw an exception. However, users have 505 // complained about this behavior, so we now just return. 506 return; 507 } 508 509 // Note: A module CANNOT be registered after calling 510 // YAHOO.util.History.initialize. Indeed, we set the initial state 511 // of each registered module in YAHOO.util.History.initialize. 512 // If you could register a module after initializing the Browser 513 // History Manager, you would not read the correct state using 514 // YAHOO.util.History.getCurrentState when coming back to the 515 // page using the back button. 516 if (_initialized) { 517 throw new Error("All modules must be registered before calling YAHOO.util.History.initialize"); 518 } 519 520 // Make sure the strings passed in do not contain our separators "," and "|" 521 module = _encode(module); 522 initialState = _encode(initialState); 523 524 // If the user chooses to override the scope, we use the 525 // custom object passed in as the execution scope. 526 scope = null; 527 if (overrideContext === true) { 528 scope = obj; 529 } else { 530 scope = overrideContext; 531 } 532 533 wrappedFn = function (state) { 534 return onStateChange.call(scope, state, obj); 535 }; 536 537 _modules[module] = { 538 name: module, 539 initialState: initialState, 540 currentState: initialState, 541 onStateChange: wrappedFn 542 }; 543 }, 544 545 /** 546 * Initializes the Browser History Manager. Call this method 547 * from a script block located right after the opening body tag. 548 * 549 * @method initialize 550 * @param {string|HTML Element} stateField <input type="hidden"> used 551 * to store application states. Must be in the static markup. 552 * @param {string|HTML Element} histFrame IFrame used to store 553 * the history (only required on Internet Explorer) 554 * @public 555 */ 556 initialize: function (stateField, histFrame) { 557 558 if (_initialized) { 559 // The browser history manager has already been initialized. 560 return; 561 } 562 563 if (YAHOO.env.ua.opera && typeof history.navigationMode !== "undefined") { 564 // Disable Opera's fast back/forward navigation mode and puts 565 // it in compatible mode. This makes anchor-based history 566 // navigation work after the page has been navigated away 567 // from and re-activated, at the cost of slowing down 568 // back/forward navigation to and from that page. 569 history.navigationMode = "compatible"; 570 } 571 572 if (typeof stateField === "string") { 573 stateField = document.getElementById(stateField); 574 } 575 576 if (!stateField || 577 stateField.tagName.toUpperCase() !== "TEXTAREA" && 578 (stateField.tagName.toUpperCase() !== "INPUT" || 579 stateField.type !== "hidden" && 580 stateField.type !== "text")) { 581 throw new Error("Missing or invalid argument"); 582 } 583 584 _stateField = stateField; 585 586 // IE < 8 or IE8 in quirks mode or IE7 standards mode 587 if (YAHOO.env.ua.ie && (typeof document.documentMode === "undefined" || document.documentMode < 8)) { 588 589 if (typeof histFrame === "string") { 590 histFrame = document.getElementById(histFrame); 591 } 592 593 if (!histFrame || histFrame.tagName.toUpperCase() !== "IFRAME") { 594 throw new Error("Missing or invalid argument"); 595 } 596 597 _histFrame = histFrame; 598 } 599 600 // Note that the event utility MUST be included inline in the page. 601 // If it gets loaded later (which you may want to do to improve the 602 // loading speed of your site), the onDOMReady event never fires, 603 // and the history library never gets fully initialized. 604 YAHOO.util.Event.onDOMReady(_initialize); 605 }, 606 607 /** 608 * Call this method when you want to store a new entry in the browser's history. 609 * 610 * @method navigate 611 * @param {string} module Non-empty string representing your module. 612 * @param {string} state String representing the new state of the specified module. 613 * @return {boolean} Indicates whether the new state was successfully added to the history. 614 * @public 615 */ 616 navigate: function (module, state) { 617 618 var states; 619 620 if (typeof module !== "string" || typeof state !== "string") { 621 throw new Error("Missing or invalid argument"); 622 } 623 624 states = {}; 625 states[module] = state; 626 627 return YAHOO.util.History.multiNavigate(states); 628 }, 629 630 /** 631 * Call this method when you want to store a new entry in the browser's history. 632 * 633 * @method multiNavigate 634 * @param {object} states Associative array of module-state pairs to set simultaneously. 635 * @return {boolean} Indicates whether the new state was successfully added to the history. 636 * @public 637 */ 638 multiNavigate: function (states) { 639 640 var currentStates, moduleName, moduleObj, currentState, fqstate; 641 642 if (typeof states !== "object") { 643 throw new Error("Missing or invalid argument"); 644 } 645 646 if (!_initialized) { 647 throw new Error("The Browser History Manager is not initialized"); 648 } 649 650 for (moduleName in states) { 651 if (!YAHOO.lang.hasOwnProperty(_modules, _encode(moduleName))) { 652 throw new Error("The following module has not been registered: " + moduleName); 653 } 654 } 655 656 // Generate our new full state string mod1=xxx&mod2=yyy 657 currentStates = []; 658 659 for (moduleName in _modules) { 660 if (YAHOO.lang.hasOwnProperty(_modules, moduleName)) { 661 moduleObj = _modules[moduleName]; 662 if (YAHOO.lang.hasOwnProperty(states, moduleName)) { 663 currentState = states[_decode(moduleName)]; 664 } else { 665 currentState = _decode(moduleObj.currentState); 666 } 667 668 // Make sure the strings passed in do not contain our separators "," and "|" 669 moduleName = _encode(moduleName); 670 currentState = _encode(currentState); 671 672 currentStates.push(moduleName + "=" + currentState); 673 } 674 } 675 676 fqstate = currentStates.join("&"); 677 678 if (YAHOO.env.ua.ie && (typeof document.documentMode === "undefined" || document.documentMode < 8)) { 679 680 return _updateIFrame(fqstate); 681 682 } else { 683 684 // Known bug: On Safari 1.x and 2.0, if you have tab browsing 685 // enabled, Safari will show an endless loading icon in the 686 // tab. This has apparently been fixed in recent WebKit builds. 687 // One work around found by Dav Glass is to submit a form that 688 // points to the same document. This indeed works on Safari 1.x 689 // and 2.0 but creates bigger problems on WebKit. So for now, 690 // we'll consider this an acceptable bug, and hope that Apple 691 // comes out with their next version of Safari very soon. 692 self.location.hash = fqstate; 693 694 return true; 695 } 696 }, 697 698 /** 699 * Returns the current state of the specified module. 700 * 701 * @method getCurrentState 702 * @param {string} module Non-empty string representing your module. 703 * @return {string} The current state of the specified module. 704 * @public 705 */ 706 getCurrentState: function (module) { 707 708 var moduleObj; 709 710 if (typeof module !== "string") { 711 throw new Error("Missing or invalid argument"); 712 } 713 714 if (!_initialized) { 715 throw new Error("The Browser History Manager is not initialized"); 716 } 717 718 moduleObj = YAHOO.lang.hasOwnProperty(_modules, module) 719 && _modules[module]; 720 721 if (!moduleObj) { 722 throw new Error("No such registered module: " + module); 723 } 724 725 return _decode(moduleObj.currentState); 726 }, 727 728 /** 729 * Returns the state of a module according to the URL fragment 730 * identifier. This method is useful to initialize your modules 731 * if your application was bookmarked from a particular state. 732 * 733 * @method getBookmarkedState 734 * @param {string} module Non-empty string representing your module. 735 * @return {string} The bookmarked state of the specified module. 736 * @public 737 */ 738 getBookmarkedState: function (module) { 739 740 var i, len, idx, hash, states, tokens, moduleName; 741 742 if (typeof module !== "string") { 743 throw new Error("Missing or invalid argument"); 744 } 745 746 // Use location.href instead of location.hash which is already 747 // URL-decoded, which creates problems if the state value 748 // contained special characters... 749 idx = self.location.href.indexOf("#"); 750 if (idx >= 0) { 751 hash = self.location.href.substr(idx + 1); 752 states = hash.split("&"); 753 for (i = 0, len = states.length; i < len; i++) { 754 tokens = states[i].split("="); 755 if (tokens.length === 2) { 756 moduleName = tokens[0]; 757 if (moduleName === module) { 758 return _decode(tokens[1]); 759 } 760 } 761 } 762 } 763 764 return null; 765 }, 766 767 /** 768 * Returns the value of the specified query string parameter. 769 * This method is not used internally by the Browser History Manager. 770 * However, it is provided here as a helper since many applications 771 * using the Browser History Manager will want to read the value of 772 * url parameters to initialize themselves. 773 * 774 * @method getQueryStringParameter 775 * @param {string} paramName Name of the parameter we want to look up. 776 * @param {string} queryString Optional URL to look at. If not specified, 777 * this method uses the URL in the address bar. 778 * @return {string} The value of the specified parameter, or null. 779 * @public 780 */ 781 getQueryStringParameter: function (paramName, url) { 782 783 var i, len, idx, queryString, params, tokens; 784 785 url = url || self.location.href; 786 787 idx = url.indexOf("?"); 788 queryString = idx >= 0 ? url.substr(idx + 1) : url; 789 790 // Remove the hash if any 791 idx = queryString.lastIndexOf("#"); 792 queryString = idx >= 0 ? queryString.substr(0, idx) : queryString; 793 794 params = queryString.split("&"); 795 796 for (i = 0, len = params.length; i < len; i++) { 797 tokens = params[i].split("="); 798 if (tokens.length >= 2) { 799 if (tokens[0] === paramName) { 800 return _decode(tokens[1]); 801 } 802 } 803 } 804 805 return null; 806 } 807 808 }; 809 810 })(); 811 YAHOO.register("history", YAHOO.util.History, {version: "2.9.0", build: "2800"}); 812 813 }, '2.9.0' ,{"requires": ["yui2-yahoo", "yui2-event"]});
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 |