1/*! 2 * History API JavaScript Library v4.2.7 3 * 4 * Support: IE8+, FF3+, Opera 9+, Safari, Chrome and other 5 * 6 * Copyright 2011-2015, Dmitrii Pakhtinov ( [email protected] ) 7 * 8 * http://spb-piksel.ru/ 9 * 10 * MIT license: 11 * http://www.opensource.org/licenses/mit-license.php 12 * 13 * Update: 2016-03-08 16:57 14 */ 15(function(factory) { 16 if (typeof define === 'function' && define['amd']) { 17 if (typeof requirejs !== 'undefined') { 18 // https://github.com/devote/HTML5-History-API/issues/73 19 var rndKey = '[history' + (new Date()).getTime() + ']'; 20 var onError = requirejs['onError']; 21 factory.toString = function() { 22 return rndKey; 23 }; 24 requirejs['onError'] = function(err) { 25 if (err.message.indexOf(rndKey) === -1) { 26 onError.call(requirejs, err); 27 } 28 }; 29 } 30 define([], factory); 31 } 32 // commonJS support 33 if (typeof exports === "object" && typeof module !== "undefined") { 34 module['exports'] = factory(); 35 } else { 36 // execute anyway 37 return factory(); 38 } 39})(function() { 40 // Define global variable 41 var global = (typeof window === 'object' ? window : this) || {}; 42 // Prevent the code from running if there is no window.history object or library already loaded 43 if (!global.history || "emulate" in global.history) return global.history; 44 // symlink to document 45 var document = global.document; 46 // HTML element 47 var documentElement = document.documentElement; 48 // symlink to constructor of Object 49 var Object = global['Object']; 50 // symlink to JSON Object 51 var JSON = global['JSON']; 52 // symlink to instance object of 'Location' 53 var windowLocation = global.location; 54 // symlink to instance object of 'History' 55 var windowHistory = global.history; 56 // new instance of 'History'. The default is a reference to the original object instance 57 var historyObject = windowHistory; 58 // symlink to method 'history.pushState' 59 var historyPushState = windowHistory.pushState; 60 // symlink to method 'history.replaceState' 61 var historyReplaceState = windowHistory.replaceState; 62 // if the browser supports HTML5-History-API 63 var isSupportHistoryAPI = isSupportHistoryAPIDetect(); 64 // verifies the presence of an object 'state' in interface 'History' 65 var isSupportStateObjectInHistory = 'state' in windowHistory; 66 // symlink to method 'Object.defineProperty' 67 var defineProperty = Object.defineProperty; 68 // new instance of 'Location', for IE8 will use the element HTMLAnchorElement, instead of pure object 69 var locationObject = redefineProperty({}, 't') ? {} : document.createElement('a'); 70 // prefix for the names of events 71 var eventNamePrefix = ''; 72 // String that will contain the name of the method 73 var addEventListenerName = global.addEventListener ? 'addEventListener' : (eventNamePrefix = 'on') && 'attachEvent'; 74 // String that will contain the name of the method 75 var removeEventListenerName = global.removeEventListener ? 'removeEventListener' : 'detachEvent'; 76 // String that will contain the name of the method 77 var dispatchEventName = global.dispatchEvent ? 'dispatchEvent' : 'fireEvent'; 78 // reference native methods for the events 79 var addEvent = global[addEventListenerName]; 80 var removeEvent = global[removeEventListenerName]; 81 var dispatch = global[dispatchEventName]; 82 // default settings 83 var settings = {"basepath": '/', "redirect": 0, "type": '/', "init": 0}; 84 // key for the sessionStorage 85 var sessionStorageKey = '__historyAPI__'; 86 // Anchor Element for parseURL function 87 var anchorElement = document.createElement('a'); 88 // last URL before change to new URL 89 var lastURL = windowLocation.href; 90 // Control URL, need to fix the bug in Opera 91 var checkUrlForPopState = ''; 92 // for fix on Safari 8 93 var triggerEventsInWindowAttributes = 1; 94 // trigger event 'onpopstate' on page load 95 var isFireInitialState = false; 96 // if used history.location of other code 97 var isUsedHistoryLocationFlag = 0; 98 // store a list of 'state' objects in the current session 99 var stateStorage = {}; 100 // in this object will be stored custom handlers 101 var eventsList = {}; 102 // stored last title 103 var lastTitle = document.title; 104 // store a custom origin 105 var customOrigin; 106 107 /** 108 * Properties that will be replaced in the global 109 * object 'window', to prevent conflicts 110 * 111 * @type {Object} 112 */ 113 var eventsDescriptors = { 114 "onhashchange": null, 115 "onpopstate": null 116 }; 117 118 /** 119 * Fix for Chrome in iOS 120 * See https://github.com/devote/HTML5-History-API/issues/29 121 */ 122 var fastFixChrome = function(method, args) { 123 var isNeedFix = global.history !== windowHistory;
124 if (isNeedFix) { 125 global.history = windowHistory; 126 } 127 method.apply(windowHistory, args); 128 if (isNeedFix) { 129 global.history = historyObject; 130 } 131 }; 132 133 /** 134 * Properties that will be replaced/added to object 135 * 'window.history', includes the object 'history.location', 136 * for a complete the work with the URL address 137 * 138 * @type {Object} 139 */ 140 var historyDescriptors = { 141 /** 142 * Setting library initialization 143 * 144 * @param {null|String} [basepath] The base path to the site; defaults to the root "/". 145 * @param {null|String} [type] Substitute the string after the anchor; by default "/". 146 * @param {null|Boolean} [redirect] Enable link translation. 147 */ 148 "setup": function(basepath, type, redirect) { 149 settings["basepath"] = ('' + (basepath == null ? settings["basepath"] : basepath)) 150 .replace(/(?:^|\/)[^\/]*$/, '/'); 151 settings["type"] = type == null ? settings["type"] : type; 152 settings["redirect"] = redirect == null ? settings["redirect"] : !!redirect; 153 }, 154 /** 155 * @namespace history 156 * @param {String} [type] 157 * @param {String} [basepath] 158 */ 159 "redirect": function(type, basepath) { 160 historyObject['setup'](basepath, type); 161 basepath = settings["basepath"]; 162 if (global.top == global.self) { 163 var relative = parseURL(null, false, true)._relative; 164 var path = windowLocation.pathname + windowLocation.search; 165 if (isSupportHistoryAPI) { 166 path = path.replace(/([^\/])$/, '$1/'); 167 if (relative != basepath && (new RegExp("^" + basepath + "$", "i")).test(path)) { 168 windowLocation.replace(relative); 169 } 170 } else if (path != basepath) { 171 path = path.replace(/([^\/])\?/, '$1/?'); 172 if ((new RegExp("^" + basepath, "i")).test(path)) { 173 windowLocation.replace(basepath + '#' + path. 174 replace(new RegExp("^" + basepath, "i"), settings["type"]) + windowLocation.hash); 175 } 176 } 177 } 178 }, 179 /** 180 * The method adds a state object entry 181 * to the history. 182 * 183 * @namespace history 184 * @param {Object} state 185 * @param {string} title 186 * @param {string} [url] 187 */ 188 pushState: function(state, title, url) { 189 var t = document.title; 190 if (lastTitle != null) { 191 document.title = lastTitle; 192 } 193 historyPushState && fastFixChrome(historyPushState, arguments); 194 changeState(state, url); 195 document.title = t; 196 lastTitle = title; 197 }, 198 /** 199 * The method updates the state object, 200 * title, and optionally the URL of the 201 * current entry in the history. 202 * 203 * @namespace history 204 * @param {Object} state 205 * @param {string} title 206 * @param {string} [url] 207 */ 208 replaceState: function(state, title, url) { 209 var t = document.title; 210 if (lastTitle != null) { 211 document.title = lastTitle; 212 } 213 delete stateStorage[windowLocation.href]; 214 historyReplaceState && fastFixChrome(historyReplaceState, arguments); 215 changeState(state, url, true); 216 document.title = t; 217 lastTitle = title; 218 }, 219 /** 220 * Object 'history.location' is similar to the 221 * object 'window.location', except that in 222 * HTML4 browsers it will behave a bit differently 223 * 224 * @namespace history 225 */ 226 "location": { 227 set: function(value) { 228 if (isUsedHistoryLocationFlag === 0) isUsedHistoryLocationFlag = 1; 229 global.location = value; 230 }, 231 get: function() { 232 if (isUsedHistoryLocationFlag === 0) isUsedHistoryLocationFlag = 1; 233 return locationObject; 234 } 235 }, 236 /** 237 * A state object is an object representing 238 * a user interface state. 239 * 240 * @namespace history 241 */ 242 "state": { 243 get: function() { 244 if (typeof stateStorage[windowLocation.href] === 'object') { 245 return JSON.parse(JSON.stringify(stateStorage[windowLocation.href])); 246 } else if(typeof stateStorage[windowLocation.href] !== 'undefined') { 247 return stateStorage[windowLocation.href]; 248 } else { 249 return null; 250 } 251 } 252 } 253 }; 254 255 /** 256 * Properties for object 'history.location'. 257 * Object 'history.location' is similar to the 258 * object 'window.location', except that in 259 * HTML4 browsers it will behave a bit differently 260 * 261 * @type {Object} 262 */ 263 var locationDescriptors = { 264 /** 265 * Navigates to the given page. 266 * 267 * @namespace history.location 268 */ 269 assign: function(url) { 270 if (!isSupportHistoryAPI && ('' + url).indexOf('#') === 0) { 271 changeState(null, url); 272 } else { 273 windowLocation.assign(url); 274 } 275 }, 276 /** 277 * Reloads the current page. 278 * 279 * @namespace history.location 280 */ 281 reload: function(flag) { 282 windowLocation.reload(flag); 283 }, 284 /** 285 * Removes the current page from 286 * the session history and navigates 287 * to the given page. 288 * 289 * @namespace history.location 290 */ 291 replace: function(url) { 292 if (!isSupportHistoryAPI && ('' + url).indexOf('#') === 0) { 293 changeState(null, url, true); 294 } else { 295 windowLocation.replace(url); 296 } 297 }, 298 /** 299 * Returns the current page's location. 300 * 301 * @namespace history.location 302 */ 303 toString: function() { 304 return this.href; 305 }, 306 /** 307 * Returns the current origin. 308 * 309 * @namespace history.location 310 */ 311 "origin": { 312 get: function() { 313 if (customOrigin !== void 0) { 314 return customOrigin; 315 } 316 if (!windowLocation.origin) { 317 return windowLocation.protocol + "//" + windowLocation.hostname + (windowLocation.port ? ':' + windowLocation.port: ''); 318 } 319 return windowLocation.origin; 320 }, 321 set: function(value) { 322 customOrigin = value; 323 } 324 }, 325 /** 326 * Returns the current page's location. 327 * Can be set, to navigate to another page. 328 * 329 * @namespace history.location 330 */ 331 "href": isSupportHistoryAPI ? null : { 332 get: function() { 333 return parseURL()._href; 334 } 335 }, 336 /** 337 * Returns the current page's protocol. 338 * 339 * @namespace history.location 340 */ 341 "protocol": null, 342 /** 343 * Returns the current page's host and port number. 344 * 345 * @namespace history.location 346 */ 347 "host": null, 348 /** 349 * Returns the current page's host. 350 * 351 * @namespace history.location 352 */ 353 "hostname": null, 354 /** 355 * Returns the current page's port number. 356 * 357 * @namespace history.location 358 */ 359 "port": null, 360 /** 361 * Returns the current page's path only. 362 * 363 * @namespace history.location 364 */ 365 "pathname": isSupportHistoryAPI ? null : { 366 get: function() { 367 return parseURL()._pathname; 368 } 369 }, 370 /** 371 * Returns the current page's search 372 * string, beginning with the character 373 * '?' and to the symbol '#' 374 * 375 * @namespace history.location 376 */ 377 "search": isSupportHistoryAPI ? null : { 378 get: function() { 379 return parseURL()._search; 380 } 381 }, 382 /** 383 * Returns the current page's hash 384 * string, beginning with the character 385 * '#' and to the end line 386 * 387 * @namespace history.location 388 */ 389 "hash": isSupportHistoryAPI ? null : { 390 set: function(value) { 391 changeState(null, ('' + value).replace(/^(#|)/, '#'), false, lastURL); 392 }, 393 get: function() { 394 return parseURL()._hash; 395 } 396 } 397 }; 398 399 /** 400 * Just empty function 401 * 402 * @return void 403 */ 404 function emptyFunction() { 405 // dummy 406 } 407 408 /** 409 * Prepares a parts of the current or specified reference for later use in the library 410 * 411 * @param {string} [href] 412 * @param {boolean} [isWindowLocation] 413 * @param {boolean} [isNotAPI] 414 * @return {Object} 415 */ 416 function parseURL(href, isWindowLocation, isNotAPI) { 417 var re = /(?:([a-zA-Z0-9\-]+\:))?(?:\/\/(?:[^@]*@)?([^\/:\?#]+)(?::([0-9]+))?)?([^\?#]*)(?:(\?[^#]+)|\?)?(?:(#.*))?/; 418 if (href != null && href !== '' && !isWindowLocation) { 419 var current = parseURL(), 420 base = document.getElementsByTagName('base')[0]; 421 if (!isNotAPI && base && base.getAttribute('href')) { 422 // Fix for IE ignoring relative base tags. 423 // See http://stackoverflow.com/questions/3926197/html-base-tag-and-local-folder-path-with-internet-explorer 424 base.href = base.href; 425 current = parseURL(base.href, null, true); 426 } 427 var _pathname = current._pathname, _protocol = current._protocol; 428 // convert to type of string 429 href = '' + href; 430 // convert relative link to the absolute 431 href = /^(?:\w+\:)?\/\//.test(href) ? href.indexOf("/") === 0 432 ? _protocol + href : href : _protocol + "//" + current._host + ( 433 href.indexOf("/") === 0 ? href : href.indexOf("?") === 0 434 ? _pathname + href : href.indexOf("#") === 0 435 ? _pathname + current._search + href : _pathname.replace(/[^\/]+$/g, '') + href 436 ); 437 } else { 438 href = isWindowLocation ? href : windowLocation.href; 439 // if current browser not support History-API 440 if (!isSupportHistoryAPI || isNotAPI) { 441 // get hash fragment 442 href = href.replace(/^[^#]*/, '') || "#"; 443 // form the absolute link from the hash 444 // https://github.com/devote/HTML5-History-API/issues/50 445 href = windowLocation.protocol.replace(/:.*$|$/, ':') + '//' + windowLocation.host + settings['basepath'] 446 + href.replace(new RegExp("^#[\/]?(?:" + settings["type"] + ")?"), ""); 447 } 448 } 449 // that would get rid of the links of the form: /../../ 450 anchorElement.href = href; 451 // decompose the link in parts 452 var result = re.exec(anchorElement.href); 453 // host name with the port number 454 var host = result[2] + (result[3] ? ':' + result[3] : ''); 455 // folder 456 var pathname = result[4] || '/'; 457 // the query string 458 var search = result[5] || ''; 459 // hash 460 var hash = result[6] === '#' ? '' : (result[6] || ''); 461 // relative link, no protocol, no host 462 var relative = pathname + search + hash; 463 // special links for set to hash-link, if browser not support History API 464 var nohash = pathname.replace(new RegExp("^" + settings["basepath"], "i"), settings["type"]) + search; 465 // result 466 return {
467 _href: result[1] + '//' + host + relative, 468 _protocol: result[1], 469 _host: host, 470 _hostname: result[2], 471 _port: result[3] || '', 472 _pathname: pathname, 473 _search: search, 474 _hash: hash, 475 _relative: relative, 476 _nohash: nohash, 477 _special: nohash + hash 478 } 479 } 480 481 /** 482 * Detect HistoryAPI support while taking into account false positives. 483 * Based on https://github.com/Modernizr/Modernizr/blob/master/feature-detects/history.js 484 */ 485 function isSupportHistoryAPIDetect(){ 486 var ua = global.navigator.userAgent; 487 // We only want Android 2 and 4.0, stock browser, and not Chrome which identifies 488 // itself as 'Mobile Safari' as well, nor Windows Phone (issue #1471). 489 if ((ua.indexOf('Android 2.') !== -1 || 490 (ua.indexOf('Android 4.0') !== -1)) && 491 ua.indexOf('Mobile Safari') !== -1 && 492 ua.indexOf('Chrome') === -1 && 493 ua.indexOf('Windows Phone') === -1) 494 { 495 return false; 496 } 497 // Return the regular check 498 return !!historyPushState; 499 } 500 501 /** 502 * Initializing storage for the custom state's object 503 */ 504 function storageInitialize() { 505 var sessionStorage; 506 /** 507 * sessionStorage throws error when cookies are disabled 508 * Chrome content settings when running the site in a Facebook IFrame. 509 * see: https://github.com/devote/HTML5-History-API/issues/34 510 * and: http://stackoverflow.com/a/12976988/669360 511 */ 512 try { 513 sessionStorage = global['sessionStorage']; 514 sessionStorage.setItem(sessionStorageKey + 't', '1'); 515 sessionStorage.removeItem(sessionStorageKey + 't'); 516 } catch(_e_) { 517 sessionStorage = { 518 getItem: function(key) { 519 var cookie = document.cookie.split(key + "="); 520 return cookie.length > 1 && cookie.pop().split(";").shift() || 'null'; 521 }, 522 setItem: function(key, value) { 523 var state = {}; 524 // insert one current element to cookie 525 if (state[windowLocation.href] = historyObject.state) { 526 document.cookie = key + '=' + JSON.stringify(state); 527 } 528 } 529 } 530 } 531 532 try { 533 // get cache from the storage in browser 534 stateStorage = JSON.parse(sessionStorage.getItem(sessionStorageKey)) || {}; 535 } catch(_e_) { 536 stateStorage = {}; 537 } 538 539 // hang up the event handler to event unload page 540 addEvent(eventNamePrefix + 'unload', function() { 541 // save current state's object 542 sessionStorage.setItem(sessionStorageKey, JSON.stringify(stateStorage)); 543 }, false); 544 } 545 546 /** 547 * This method is implemented to override the built-in(native) 548 * properties in the browser, unfortunately some browsers are 549 * not allowed to override all the properties and even add. 550 * For this reason, this was written by a method that tries to 551 * do everything necessary to get the desired result. 552 * 553 * @param {Object} object The object in which will be overridden/added property 554 * @param {String} prop The property name to be overridden/added 555 * @param {Object} [descriptor] An object containing properties set/get 556 * @param {Function} [onWrapped] The function to be called when the wrapper is created 557 * @return {Object|Boolean} Returns an object on success, otherwise returns false 558 */ 559 function redefineProperty(object, prop, descriptor, onWrapped) { 560 var testOnly = 0; 561 // test only if descriptor is undefined 562 if (!descriptor) { 563 descriptor = {set: emptyFunction}; 564 testOnly = 1; 565 } 566 // variable will have a value of true the success of attempts to set descriptors 567 var isDefinedSetter = !descriptor.set; 568 var isDefinedGetter = !descriptor.get; 569 // for tests of attempts to set descriptors 570 var test = {configurable: true, set: function() { 571 isDefinedSetter = 1; 572 }, get: function() { 573 isDefinedGetter = 1; 574 }}; 575 576 try { 577 // testing for the possibility of overriding/adding properties 578 defineProperty(object, prop, test); 579 // running the test 580 object[prop] = object[prop]; 581 // attempt to override property using the standard method 582 defineProperty(object, prop, descriptor); 583 } catch(_e_) { 584 } 585
586 // If the variable 'isDefined' has a false value, it means that need to try other methods 587 if (!isDefinedSetter || !isDefinedGetter) { 588 // try to override/add the property, using deprecated functions 589 if (object.__defineGetter__) { 590 // testing for the possibility of overriding/adding properties 591 object.__defineGetter__(prop, test.get); 592 object.__defineSetter__(prop, test.set); 593 // running the test 594 object[prop] = object[prop]; 595 // attempt to override property using the deprecated functions 596 descriptor.get && object.__defineGetter__(prop, descriptor.get); 597 descriptor.set && object.__defineSetter__(prop, descriptor.set); 598 } 599 600 // Browser refused to override the property, using the standard and deprecated methods 601 if (!isDefinedSetter || !isDefinedGetter) { 602 if (testOnly) { 603 return false; 604 } else if (object === global) { 605 // try override global properties 606 try { 607 // save original value from this property 608 var originalValue = object[prop]; 609 // set null to built-in(native) property 610 object[prop] = null; 611 } catch(_e_) { 612 } 613 // This rule for Internet Explorer 8 614 if ('execScript' in global) { 615 /** 616 * to IE8 override the global properties using 617 * VBScript, declaring it in global scope with 618 * the same names. 619 */ 620 global['execScript']('Public ' + prop, 'VBScript'); 621 global['execScript']('var ' + prop + ';', 'JavaScript'); 622 } else { 623 try { 624 /** 625 * This hack allows to override a property 626 * with the set 'configurable: false', working 627 * in the hack 'Safari' to 'Mac' 628 */ 629 defineProperty(object, prop, {value: emptyFunction}); 630 } catch(_e_) { 631 if (prop === 'onpopstate') { 632 /** 633 * window.onpopstate fires twice in Safari 8.0. 634 * Block initial event on window.onpopstate 635 * See: https://github.com/devote/HTML5-History-API/issues/69 636 */ 637 addEvent('popstate', descriptor = function() { 638 removeEvent('popstate', descriptor, false); 639 var onpopstate = object.onpopstate; 640 // cancel initial event on attribute handler 641 object.onpopstate = null; 642 setTimeout(function() { 643 // restore attribute value after short time 644 object.onpopstate = onpopstate; 645 }, 1); 646 }, false); 647 // cancel trigger events on attributes in object the window 648 triggerEventsInWindowAttributes = 0; 649 } 650 } 651 } 652 // set old value to new variable 653 object[prop] = originalValue; 654 655 } else { 656 // the last stage of trying to override the property 657 try { 658 try { 659 // wrap the object in a new empty object 660 var temp = Object.create(object); 661 defineProperty(Object.getPrototypeOf(temp) === object ? temp : object, prop, descriptor); 662 for(var key in object) { 663 // need to bind a function to the original object 664 if (typeof object[key] === 'function') { 665 temp[key] = object[key].bind(object); 666 } 667 } 668 try { 669 // to run a function that will inform about what the object was to wrapped 670 onWrapped.call(temp, temp, object); 671 } catch(_e_) { 672 } 673 object = temp; 674 } catch(_e_) { 675 // sometimes works override simply by assigning the prototype property of the constructor 676 defineProperty(object.constructor.prototype, prop, descriptor); 677 } 678 } catch(_e_) { 679 // all methods have failed 680 return false;
681 } 682 } 683 } 684 } 685 686 return object; 687 } 688 689 /** 690 * Adds the missing property in descriptor 691 * 692 * @param {Object} object An object that stores values 693 * @param {String} prop Name of the property in the object 694 * @param {Object|null} descriptor Descriptor 695 * @return {Object} Returns the generated descriptor 696 */ 697 function prepareDescriptorsForObject(object, prop, descriptor) { 698 descriptor = descriptor || {}; 699 // the default for the object 'location' is the standard object 'window.location' 700 object = object === locationDescriptors ? windowLocation : object; 701 // setter for object properties 702 descriptor.set = (descriptor.set || function(value) { 703 object[prop] = value; 704 }); 705 // getter for object properties 706 descriptor.get = (descriptor.get || function() { 707 return object[prop]; 708 }); 709 return descriptor; 710 } 711 712 /** 713 * Wrapper for the methods 'addEventListener/attachEvent' in the context of the 'window' 714 * 715 * @param {String} event The event type for which the user is registering 716 * @param {Function} listener The method to be called when the event occurs. 717 * @param {Boolean} capture If true, capture indicates that the user wishes to initiate capture. 718 * @return void 719 */ 720 function addEventListener(event, listener, capture) { 721 if (event in eventsList) { 722 // here stored the event listeners 'popstate/hashchange' 723 eventsList[event].push(listener); 724 } else { 725 // FireFox support non-standart four argument aWantsUntrusted 726 // https://github.com/devote/HTML5-History-API/issues/13 727 if (arguments.length > 3) { 728 addEvent(event, listener, capture, arguments[3]); 729 } else { 730 addEvent(event, listener, capture); 731 } 732 } 733 } 734 735 /** 736 * Wrapper for the methods 'removeEventListener/detachEvent' in the context of the 'window' 737 * 738 * @param {String} event The event type for which the user is registered 739 * @param {Function} listener The parameter indicates the Listener to be removed. 740 * @param {Boolean} capture Was registered as a capturing listener or not. 741 * @return void 742 */ 743 function removeEventListener(event, listener, capture) { 744 var list = eventsList[event]; 745 if (list) { 746 for(var i = list.length; i--;) { 747 if (list[i] === listener) { 748 list.splice(i, 1); 749 break; 750 } 751 } 752 } else { 753 removeEvent(event, listener, capture); 754 } 755 } 756 757 /** 758 * Wrapper for the methods 'dispatchEvent/fireEvent' in the context of the 'window' 759 * 760 * @param {Event|String} event Instance of Event or event type string if 'eventObject' used 761 * @param {*} [eventObject] For Internet Explorer 8 required event object on this argument 762 * @return {Boolean} If 'preventDefault' was called the value is false, else the value is true. 763 */ 764 function dispatchEvent(event, eventObject) { 765 var eventType = ('' + (typeof event === "string" ? event : event.type)).replace(/^on/, ''); 766 var list = eventsList[eventType]; 767 if (list) { 768 // need to understand that there is one object of Event 769 eventObject = typeof event === "string" ? eventObject : event; 770 if (eventObject.target == null) { 771 // need to override some of the properties of the Event object 772 for(var props = ['target', 'currentTarget', 'srcElement', 'type']; event = props.pop();) { 773 // use 'redefineProperty' to override the properties 774 eventObject = redefineProperty(eventObject, event, { 775 get: event === 'type' ? function() { 776 return eventType; 777 } : function() { 778 return global; 779 } 780 }); 781 } 782 } 783 if (triggerEventsInWindowAttributes) { 784 // run function defined in the attributes 'onpopstate/onhashchange' in the 'window' context 785 ((eventType === 'popstate' ? global.onpopstate : global.onhashchange) 786 || emptyFunction).call(global, eventObject); 787 } 788 // run other functions that are in the list of handlers 789 for(var i = 0, len = list.length; i < len; i++) { 790 list[i].call(global, eventObject); 791 } 792 return true; 793 } else {
794 return dispatch(event, eventObject); 795 } 796 } 797 798 /** 799 * dispatch current state event 800 */ 801 function firePopState() { 802 var o = document.createEvent ? document.createEvent('Event') : document.createEventObject(); 803 if (o.initEvent) { 804 o.initEvent('popstate', false, false); 805 } else { 806 o.type = 'popstate'; 807 } 808 o.state = historyObject.state; 809 // send a newly created events to be processed 810 dispatchEvent(o); 811 } 812 813 /** 814 * fire initial state for non-HTML5 browsers 815 */ 816 function fireInitialState() { 817 if (isFireInitialState) { 818 isFireInitialState = false; 819 firePopState(); 820 } 821 } 822 823 /** 824 * Change the data of the current history for HTML4 browsers 825 * 826 * @param {Object} state 827 * @param {string} [url] 828 * @param {Boolean} [replace] 829 * @param {string} [lastURLValue] 830 * @return void 831 */ 832 function changeState(state, url, replace, lastURLValue) { 833 if (!isSupportHistoryAPI) { 834 // if not used implementation history.location 835 if (isUsedHistoryLocationFlag === 0) isUsedHistoryLocationFlag = 2; 836 // normalization url 837 var urlObject = parseURL(url, isUsedHistoryLocationFlag === 2 && ('' + url).indexOf("#") !== -1); 838 // if current url not equal new url 839 if (urlObject._relative !== parseURL()._relative) { 840 // if empty lastURLValue to skip hash change event 841 lastURL = lastURLValue; 842 if (replace) { 843 // only replace hash, not store to history 844 windowLocation.replace("#" + urlObject._special); 845 } else { 846 // change hash and add new record to history 847 windowLocation.hash = urlObject._special; 848 } 849 } 850 } else { 851 lastURL = windowLocation.href; 852 } 853 if (!isSupportStateObjectInHistory && state) { 854 stateStorage[windowLocation.href] = state; 855 } 856 isFireInitialState = false; 857 } 858 859 /** 860 * Event handler function changes the hash in the address bar 861 * 862 * @param {Event} event 863 * @return void 864 */ 865 function onHashChange(event) { 866 // https://github.com/devote/HTML5-History-API/issues/46 867 var fireNow = lastURL; 868 // new value to lastURL 869 lastURL = windowLocation.href; 870 // if not empty fireNow, otherwise skipped the current handler event 871 if (fireNow) { 872 // if checkUrlForPopState equal current url, this means that the event was raised popstate browser 873 if (checkUrlForPopState !== windowLocation.href) { 874 // otherwise, 875 // the browser does not support popstate event or just does not run the event by changing the hash. 876 firePopState(); 877 } 878 // current event object 879 event = event || global.event; 880 881 var oldURLObject = parseURL(fireNow, true); 882 var newURLObject = parseURL(); 883 // HTML4 browser not support properties oldURL/newURL 884 if (!event.oldURL) { 885 event.oldURL = oldURLObject._href; 886 event.newURL = newURLObject._href; 887 } 888 if (oldURLObject._hash !== newURLObject._hash) { 889 // if current hash not equal previous hash 890 dispatchEvent(event); 891 } 892 } 893 } 894 895 /** 896 * The event handler is fully loaded document 897 * 898 * @param {*} [noScroll] 899 * @return void 900 */ 901 function onLoad(noScroll) { 902 // Get rid of the events popstate when the first loading a document in the webkit browsers 903 setTimeout(function() { 904 // hang up the event handler for the built-in popstate event in the browser 905 addEvent('popstate', function(e) { 906 // set the current url, that suppress the creation of the popstate event by changing the hash 907 checkUrlForPopState = windowLocation.href; 908 // for Safari browser in OS Windows not implemented 'state' object in 'History' interface 909 // and not implemented in old HTML4 browsers 910 if (!isSupportStateObjectInHistory) { 911 e = redefineProperty(e, 'state', {get: function() { 912 return historyObject.state; 913 }}); 914 } 915 // send events to be processed 916 dispatchEvent(e); 917 }, false); 918 }, 0); 919 // for non-HTML5 browsers 920 if (!isSupportHistoryAPI && noScroll !== true && "location" in historyObject) { 921 // scroll window to anchor element 922 scrollToAnchorId(locationObject.hash); 923 // fire initial state for non-HTML5 browser after load page 924 fireInitialState(); 925 } 926 } 927 928 /** 929 * Finds the closest ancestor anchor element (including the target itself). 930 * 931 * @param {HTMLElement} target The element to start scanning from. 932 * @return {HTMLElement} An element which is the closest ancestor anchor. 933 */ 934 function anchorTarget(target) { 935 while (target) { 936 if (target.nodeName === 'A') return target; 937 target = target.parentNode; 938 } 939 } 940 941 /** 942 * Handles anchor elements with a hash fragment for non-HTML5 browsers 943 * 944 * @param {Event} e 945 */ 946 function onAnchorClick(e) { 947 var event = e || global.event; 948 var target = anchorTarget(event.target || event.srcElement); 949 var defaultPrevented = "defaultPrevented" in event ? event['defaultPrevented'] : event.returnValue === false; 950 if (target && target.nodeName === "A" && !defaultPrevented) { 951 var current = parseURL(); 952 var expect = parseURL(target.getAttribute("href", 2)); 953 var isEqualBaseURL = current._href.split('#').shift() === expect._href.split('#').shift(); 954 if (isEqualBaseURL && expect._hash) { 955 if (current._hash !== expect._hash) { 956 locationObject.hash = expect._hash; 957 } 958 scrollToAnchorId(expect._hash); 959 if (event.preventDefault) { 960 event.preventDefault(); 961 } else { 962 event.returnValue = false;
963 } 964 } 965 } 966 } 967 968 /** 969 * Scroll page to current anchor in url-hash 970 * 971 * @param hash 972 */ 973 function scrollToAnchorId(hash) { 974 var target = document.getElementById(hash = (hash || '').replace(/^#/, '')); 975 if (target && target.id === hash && target.nodeName === "A") { 976 var rect = target.getBoundingClientRect(); 977 global.scrollTo((documentElement.scrollLeft || 0), rect.top + (documentElement.scrollTop || 0) 978 - (documentElement.clientTop || 0)); 979 } 980 } 981 982 /** 983 * Library initialization 984 * 985 * @return {Boolean} return true if all is well, otherwise return false value 986 */ 987 function initialize() { 988 /** 989 * Get custom settings from the query string 990 */ 991 var scripts = document.getElementsByTagName('script'); 992 var src = (scripts[scripts.length - 1] || {}).src || ''; 993 var arg = src.indexOf('?') !== -1 ? src.split('?').pop() : ''; 994 arg.replace(/(\w+)(?:=([^&]*))?/g, function(a, key, value) { 995 settings[key] = (value || '').replace(/^(0|false)$/, ''); 996 }); 997 998 /** 999 * hang up the event handler to listen to the events hashchange 1000 */ 1001 addEvent(eventNamePrefix + 'hashchange', onHashChange, false); 1002 1003 // a list of objects with pairs of descriptors/object 1004 var data = [locationDescriptors, locationObject, eventsDescriptors, global, historyDescriptors, historyObject]; 1005 1006 // if browser support object 'state' in interface 'History' 1007 if (isSupportStateObjectInHistory) { 1008 // remove state property from descriptor 1009 delete historyDescriptors['state']; 1010 } 1011 1012 // initializing descriptors 1013 for(var i = 0; i < data.length; i += 2) { 1014 for(var prop in data[i]) { 1015 if (data[i].hasOwnProperty(prop)) { 1016 if (typeof data[i][prop] !== 'object') { 1017 // If the descriptor is a simple function, simply just assign it an object 1018 data[i + 1][prop] = data[i][prop]; 1019 } else { 1020 // prepare the descriptor the required format 1021 var descriptor = prepareDescriptorsForObject(data[i], prop, data[i][prop]); 1022 // try to set the descriptor object 1023 if (!redefineProperty(data[i + 1], prop, descriptor, function(n, o) { 1024 // is satisfied if the failed override property 1025 if (o === historyObject) { 1026 // the problem occurs in Safari on the Mac 1027 global.history = historyObject = data[i + 1] = n; 1028 } 1029 })) { 1030 // if there is no possibility override. 1031 // This browser does not support descriptors, such as IE7 1032 1033 // remove previously hung event handlers 1034 removeEvent(eventNamePrefix + 'hashchange', onHashChange, false); 1035 1036 // fail to initialize :( 1037 return false; 1038 } 1039 1040 // create a repository for custom handlers onpopstate/onhashchange 1041 if (data[i + 1] === global) { 1042 eventsList[prop] = eventsList[prop.substr(2)] = []; 1043 } 1044 } 1045 } 1046 } 1047 } 1048 1049 // check settings 1050 historyObject['setup'](); 1051 1052 // redirect if necessary 1053 if (settings['redirect']) { 1054 historyObject['redirect'](); 1055 } 1056 1057 // initialize 1058 if (settings["init"]) { 1059 // You agree that you will use window.history.location instead window.location 1060 isUsedHistoryLocationFlag = 1; 1061 } 1062 1063 // If browser does not support object 'state' in interface 'History' 1064 if (!isSupportStateObjectInHistory && JSON) { 1065 storageInitialize(); 1066 } 1067 1068 // track clicks on anchors 1069 if (!isSupportHistoryAPI) { 1070 document[addEventListenerName](eventNamePrefix + "click", onAnchorClick, false); 1071 } 1072 1073 if (document.readyState === 'complete') { 1074 onLoad(true); 1075 } else { 1076 if (!isSupportHistoryAPI && parseURL()._relative !== settings["basepath"]) { 1077 isFireInitialState = true; 1078 } 1079 /** 1080 * Need to avoid triggering events popstate the initial page load. 1081 * Hang handler popstate as will be fully loaded document that 1082 * would prevent triggering event onpopstate 1083 */ 1084 addEvent(eventNamePrefix + 'load', onLoad, false); 1085 } 1086 1087 // everything went well 1088 return true; 1089 } 1090 1091 /** 1092 * Starting the library 1093 */ 1094 if (!initialize()) { 1095 // if unable to initialize descriptors 1096 // therefore quite old browser and there 1097 // is no sense to continue to perform 1098 return; 1099 } 1100 1101 /** 1102 * If the property history.emulate will be true, 1103 * this will be talking about what's going on 1104 * emulation capabilities HTML5-History-API. 1105 * Otherwise there is no emulation, ie the 1106 * built-in browser capabilities. 1107 * 1108 * @type {boolean} 1109 * @const 1110 */ 1111 historyObject['emulate'] = !isSupportHistoryAPI; 1112 1113 /** 1114 * Replace the original methods on the wrapper 1115 */ 1116 global[addEventListenerName] = addEventListener; 1117 global[removeEventListenerName] = removeEventListener; 1118 global[dispatchEventName] = dispatchEvent; 1119 1120 return historyObject; 1121});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.