page-app
Version:
Builder for rich single-page js apps (frontend)
1,170 lines (1,115 loc) • 46.3 kB
JavaScript
/*
* History API JavaScript Library v4.0.5
*
* Support: IE6+, FF3+, Opera 9+, Safari, Chrome and other
*
* Copyright 2011-2013, Dmitrii Pakhtinov ( spb.piksel@gmail.com )
*
* http://spb-piksel.ru/
*
* Dual licensed under the MIT and GPL licenses:
* http://www.opensource.org/licenses/mit-license.php
* http://www.gnu.org/licenses/gpl.html
*
* Update: 20.08.13 21:16
*/
(function(window) {
// Prevent the code from running if there is no window.history object
if (!window.history) return;
// symlink to document
var document = window.document;
// HTML element
var documentElement = document.documentElement;
// symlink to sessionStorage
var sessionStorage = null;
// symlink to constructor of Object
var Object = window['Object'];
// symlink to JSON Object
var JSON = window['JSON'];
// symlink to instance object of 'Location'
var windowLocation = window.location;
// symlink to instance object of 'History'
var windowHistory = window.history;
// new instance of 'History'. The default is a reference to the original object instance
var historyObject = windowHistory;
// symlink to method 'history.pushState'
var historyPushState = windowHistory.pushState;
// symlink to method 'history.replaceState'
var historyReplaceState = windowHistory.replaceState;
// if the browser supports HTML5-History-API
var isSupportHistoryAPI = !!historyPushState;
// verifies the presence of an object 'state' in interface 'History'
var isSupportStateObjectInHistory = 'state' in windowHistory;
// symlink to method 'Object.defineProperty'
var defineProperty = Object.defineProperty;
// new instance of 'Location', for IE8 will use the element HTMLAnchorElement, instead of pure object
var locationObject = redefineProperty({}, 't') ? {} : document.createElement('a');
// prefix for the names of events
var eventNamePrefix = '';
// String that will contain the name of the method
var addEventListenerName = window.addEventListener ? 'addEventListener' : (eventNamePrefix = 'on') && 'attachEvent';
// String that will contain the name of the method
var removeEventListenerName = window.removeEventListener ? 'removeEventListener' : 'detachEvent';
// String that will contain the name of the method
var dispatchEventName = window.dispatchEvent ? 'dispatchEvent' : 'fireEvent';
// reference native methods for the events
var addEvent = window[addEventListenerName];
var removeEvent = window[removeEventListenerName];
var dispatch = window[dispatchEventName];
// default settings
var settings = {"basepath": '/', "redirect": 0, "type": '/'};
// key for the sessionStorage
var sessionStorageKey = '__historyAPI__';
// Anchor Element for parseURL function
var anchorElement = document.createElement('a');
// last URL before change to new URL
var lastURL = windowLocation.href;
// Control URL, need to fix the bug in Opera
var checkUrlForPopState = '';
// trigger event 'onpopstate' on page load
var isFireInitialState = false;
// store a list of 'state' objects in the current session
var stateStorage = {};
// in this object will be stored custom handlers
var eventsList = {};
/**
* Properties that will be replaced in the global
* object 'window', to prevent conflicts
*
* @type {Object}
*/
var eventsDescriptors = {
"onhashchange": null,
"onpopstate": null
};
/**
* Fix for Chrome in iOS
* See https://github.com/devote/HTML5-History-API/issues/29
*/
var fastFixChrome = function(method, args) {
var isNeedFix = window.history !== windowHistory;
if (isNeedFix) {
window.history = windowHistory;
}
method.apply(windowHistory, args);
if (isNeedFix) {
window.history = historyObject;
}
};
/**
* Properties that will be replaced/added to object
* 'window.history', includes the object 'history.location',
* for a complete the work with the URL address
*
* @type {Object}
*/
var historyDescriptors = {
/**
* @namespace history
* @param {String} [type]
* @param {String} [basepath]
*/
"redirect": function(type, basepath) {
settings["basepath"] = basepath = basepath == null ? settings["basepath"] : basepath;
settings["type"] = type = type == null ? settings["type"] : type;
if (window.top == window.self) {
var relative = parseURL(null, false, true)._relative;
var path = windowLocation.pathname + windowLocation.search;
if (isSupportHistoryAPI) {
path = path.replace(/([^\/])$/, '$1/');
if (relative != basepath && (new RegExp("^" + basepath + "$", "i")).test(path)) {
windowLocation.replace(relative);
}
} else if (path != basepath) {
path = path.replace(/([^\/])\?/, '$1/?');
if ((new RegExp("^" + basepath, "i")).test(path)) {
windowLocation.replace(basepath + '#' + path.
replace(new RegExp("^" + basepath, "i"), type) + windowLocation.hash);
}
}
}
},
/**
* The method adds a state object entry
* to the history.
*
* @namespace history
* @param {Object} state
* @param {string} title
* @param {string} [url]
*/
pushState: function(state, title, url) {
historyPushState && fastFixChrome(historyPushState, arguments);
changeState(state, url);
},
/**
* The method updates the state object,
* title, and optionally the URL of the
* current entry in the history.
*
* @namespace history
* @param {Object} state
* @param {string} title
* @param {string} [url]
*/
replaceState: function(state, title, url) {
delete stateStorage[windowLocation.href];
historyReplaceState && fastFixChrome(historyReplaceState, arguments);
changeState(state, url, true);
},
/**
* Object 'history.location' is similar to the
* object 'window.location', except that in
* HTML4 browsers it will behave a bit differently
*
* @namespace history
*/
"location": {
set: function(value) {
window.location = value;
},
get: function() {
return isSupportHistoryAPI ? windowLocation : locationObject;
}
},
/**
* A state object is an object representing
* a user interface state.
*
* @namespace history
*/
"state": {
get: function() {
return stateStorage[windowLocation.href] || null;
}
}
};
/**
* Properties for object 'history.location'.
* Object 'history.location' is similar to the
* object 'window.location', except that in
* HTML4 browsers it will behave a bit differently
*
* @type {Object}
*/
var locationDescriptors = {
/**
* Navigates to the given page.
*
* @namespace history.location
*/
assign: function(url) {
if (('' + url).indexOf('#') === 0) {
changeState(null, url);
} else {
windowLocation.assign(url);
}
},
/**
* Reloads the current page.
*
* @namespace history.location
*/
reload: function() {
windowLocation.reload();
},
/**
* Removes the current page from
* the session history and navigates
* to the given page.
*
* @namespace history.location
*/
replace: function(url) {
if (('' + url).indexOf('#') === 0) {
changeState(null, url, true);
} else {
windowLocation.replace(url);
}
},
/**
* Returns the current page's location.
*
* @namespace history.location
*/
toString: function() {
return this.href;
},
/**
* Returns the current page's location.
* Can be set, to navigate to another page.
*
* @namespace history.location
*/
"href": {
get: function() {
return parseURL()._href;
}
},
/**
* Returns the current page's protocol.
*
* @namespace history.location
*/
"protocol": null,
/**
* Returns the current page's host and port number.
*
* @namespace history.location
*/
"host": null,
/**
* Returns the current page's host.
*
* @namespace history.location
*/
"hostname": null,
/**
* Returns the current page's port number.
*
* @namespace history.location
*/
"port": null,
/**
* Returns the current page's path only.
*
* @namespace history.location
*/
"pathname": {
get: function() {
return parseURL()._pathname;
}
},
/**
* Returns the current page's search
* string, beginning with the character
* '?' and to the symbol '#'
*
* @namespace history.location
*/
"search": {
get: function() {
return parseURL()._search;
}
},
/**
* Returns the current page's hash
* string, beginning with the character
* '#' and to the end line
*
* @namespace history.location
*/
"hash": {
set: function(value) {
changeState(null, ('' + value).replace(/^(#|)/, '#'), false, lastURL);
},
get: function() {
return parseURL()._hash;
}
}
};
/**
* Just empty function
*
* @return void
*/
function emptyFunction() {
// dummy
}
/**
* Prepares a parts of the current or specified reference for later use in the library
*
* @param {string} [href]
* @param {boolean} [isWindowLocation]
* @param {boolean} [isNotAPI]
* @return {Object}
*/
function parseURL(href, isWindowLocation, isNotAPI) {
var re = /(?:([\w0-9]+:))?(?:\/\/(?:[^@]*@)?([^\/:\?#]+)(?::([0-9]+))?)?([^\?#]*)(?:(\?[^#]+)|\?)?(?:(#.*))?/;
if (href && !isWindowLocation) {
var current = parseURL(), _pathname = current._pathname, _protocol = current._protocol;
// convert relative link to the absolute
href = /^(?:[\w0-9]+\:)?\/\//.test(href) ? href.indexOf("/") === 0
? _protocol + href : href : _protocol + "//" + current._host + (
href.indexOf("/") === 0 ? href : href.indexOf("?") === 0
? _pathname + href : href.indexOf("#") === 0
? _pathname + current._search + href : _pathname.replace(/[^\/]+$/g, '') + href
);
} else {
href = isWindowLocation ? href : windowLocation.href;
// if current browser not support History-API
if (!isSupportHistoryAPI || isNotAPI) {
// get hash fragment
href = href.replace(/^[^#]*/, '') || "#";
// form the absolute link from the hash
href = windowLocation.protocol + '//' + windowLocation.host + settings['basepath']
+ href.replace(new RegExp("^#[\/]?(?:" + settings["type"] + ")?"), "");
}
}
// that would get rid of the links of the form: /../../
anchorElement.href = href;
// decompose the link in parts
var result = re.exec(anchorElement.href);
// host name with the port number
var host = result[2] + (result[3] ? ':' + result[3] : '');
// folder
var pathname = result[4] || '/';
// the query string
var search = result[5] || '';
// hash
var hash = result[6] === '#' ? '' : (result[6] || '');
// relative link, no protocol, no host
var relative = pathname + search + hash;
// special links for set to hash-link, if browser not support History API
var nohash = pathname.replace(new RegExp("^" + settings["basepath"], "i"), settings["type"]) + search;
// result
return {
_href: result[1] + '//' + host + relative,
_protocol: result[1],
_host: host,
_hostname: result[2],
_port: result[3] || '',
_pathname: pathname,
_search: search,
_hash: hash,
_relative: relative,
_nohash: nohash,
_special: nohash + hash
}
}
/**
* Initializing storage for the custom state's object
*/
function storageInitialize() {
var storage = '';
if (sessionStorage) {
// get cache from the storage in browser
storage += sessionStorage.getItem(sessionStorageKey);
} else {
var cookie = document.cookie.split(sessionStorageKey + "=");
if (cookie.length > 1) {
storage += (cookie.pop().split(";").shift() || 'null');
}
}
try {
stateStorage = JSON.parse(storage) || {};
} catch(_e_) {
stateStorage = {};
}
// hang up the event handler to event unload page
addEvent(eventNamePrefix + 'unload', function() {
if (sessionStorage) {
// save current state's object
sessionStorage.setItem(sessionStorageKey, JSON.stringify(stateStorage));
} else {
// save the current 'state' in the cookie
var state = {};
if (state[windowLocation.href] = historyObject.state) {
document.cookie = sessionStorageKey + '=' + JSON.stringify(state);
}
}
}, false);
}
/**
* This method is implemented to override the built-in(native)
* properties in the browser, unfortunately some browsers are
* not allowed to override all the properties and even add.
* For this reason, this was written by a method that tries to
* do everything necessary to get the desired result.
*
* @param {Object} object The object in which will be overridden/added property
* @param {String} prop The property name to be overridden/added
* @param {Object} [descriptor] An object containing properties set/get
* @param {Function} [onWrapped] The function to be called when the wrapper is created
* @return {Object|Boolean} Returns an object on success, otherwise returns false
*/
function redefineProperty(object, prop, descriptor, onWrapped) {
// test only if descriptor is undefined
descriptor = descriptor || {set: emptyFunction};
// variable will have a value of true the success of attempts to set descriptors
var isDefinedSetter = !descriptor.set;
var isDefinedGetter = !descriptor.get;
// for tests of attempts to set descriptors
var test = {configurable: true, set: function() {
isDefinedSetter = 1;
}, get: function() {
isDefinedGetter = 1;
}};
try {
// testing for the possibility of overriding/adding properties
defineProperty(object, prop, test);
// running the test
object[prop] = object[prop];
// attempt to override property using the standard method
defineProperty(object, prop, descriptor);
} catch(_e_) {
}
// If the variable 'isDefined' has a false value, it means that need to try other methods
if (!isDefinedSetter || !isDefinedGetter) {
// try to override/add the property, using deprecated functions
if (object.__defineGetter__) {
// testing for the possibility of overriding/adding properties
object.__defineGetter__(prop, test.get);
object.__defineSetter__(prop, test.set);
// running the test
object[prop] = object[prop];
// attempt to override property using the deprecated functions
descriptor.get && object.__defineGetter__(prop, descriptor.get);
descriptor.set && object.__defineSetter__(prop, descriptor.set);
}
// Browser refused to override the property, using the standard and deprecated methods
if ((!isDefinedSetter || !isDefinedGetter) && object === window) {
try {
// save original value from this property
var originalValue = object[prop];
// set null to built-in(native) property
object[prop] = null;
} catch(_e_) {
}
// This rule for Internet Explorer 8
if ('execScript' in window) {
/**
* to IE8 override the global properties using
* VBScript, declaring it in global scope with
* the same names.
*/
window['execScript']('Public ' + prop, 'VBScript');
} else {
try {
/**
* This hack allows to override a property
* with the set 'configurable: false', working
* in the hack 'Safari' to 'Mac'
*/
defineProperty(object, prop, {value: emptyFunction});
} catch(_e_) {
}
}
// set old value to new variable
object[prop] = originalValue;
} else if (!isDefinedSetter || !isDefinedGetter) {
// the last stage of trying to override the property
try {
try {
// wrap the object in a new empty object
var temp = Object.create(object);
defineProperty(Object.getPrototypeOf(temp) === object ? temp : object, prop, descriptor);
for(var key in object) {
// need to bind a function to the original object
if (typeof object[key] === 'function') {
temp[key] = object[key].bind(object);
}
}
try {
// to run a function that will inform about what the object was to wrapped
onWrapped.call(temp, temp, object);
} catch(_e_) {
}
object = temp;
} catch(_e_) {
// sometimes works override simply by assigning the prototype property of the constructor
defineProperty(object.constructor.prototype, prop, descriptor);
}
} catch(_e_) {
// all methods have failed
return false;
}
}
}
return object;
}
/**
* Adds the missing property in descriptor
*
* @param {Object} object An object that stores values
* @param {String} prop Name of the property in the object
* @param {Object|null} descriptor Descriptor
* @return {Object} Returns the generated descriptor
*/
function prepareDescriptorsForObject(object, prop, descriptor) {
descriptor = descriptor || {};
// the default for the object 'location' is the standard object 'window.location'
object = object === locationDescriptors ? windowLocation : object;
// setter for object properties
descriptor.set = (descriptor.set || function(value) {
object[prop] = value;
});
// getter for object properties
descriptor.get = (descriptor.get || function() {
return object[prop];
});
return descriptor;
}
/**
* Wrapper for the methods 'addEventListener/attachEvent' in the context of the 'window'
*
* @param {String} event The event type for which the user is registering
* @param {Function} listener The method to be called when the event occurs.
* @param {Boolean} capture If true, capture indicates that the user wishes to initiate capture.
* @return void
*/
function addEventListener(event, listener, capture) {
if (event in eventsList) {
// here stored the event listeners 'popstate/hashchange'
eventsList[event].push(listener);
} else {
// FireFox support non-standart four argument aWantsUntrusted
// https://github.com/devote/HTML5-History-API/issues/13
if (arguments.length > 3) {
addEvent(event, listener, capture, arguments[3]);
} else {
addEvent(event, listener, capture);
}
}
}
/**
* Wrapper for the methods 'removeEventListener/detachEvent' in the context of the 'window'
*
* @param {String} event The event type for which the user is registered
* @param {Function} listener The parameter indicates the Listener to be removed.
* @param {Boolean} capture Was registered as a capturing listener or not.
* @return void
*/
function removeEventListener(event, listener, capture) {
var list = eventsList[event];
if (list) {
for(var i = list.length; --i;) {
if (list[i] === listener) {
list.splice(i, 1);
break;
}
}
} else {
removeEvent(event, listener, capture);
}
}
/**
* Wrapper for the methods 'dispatchEvent/fireEvent' in the context of the 'window'
*
* @param {Event|String} event Instance of Event or event type string if 'eventObject' used
* @param {*} [eventObject] For Internet Explorer 8 required event object on this argument
* @return {Boolean} If 'preventDefault' was called the value is false, else the value is true.
*/
function dispatchEvent(event, eventObject) {
var eventType = ('' + (typeof event === "string" ? event : event.type)).replace(/^on/, '');
var list = eventsList[eventType];
if (list) {
// need to understand that there is one object of Event
eventObject = typeof event === "string" ? eventObject : event;
if (eventObject.target == null) {
// need to override some of the properties of the Event object
for(var props = ['target', 'currentTarget', 'srcElement', 'type']; event = props.pop();) {
// use 'redefineProperty' to override the properties
eventObject = redefineProperty(eventObject, event, {
get: event === 'type' ? function() {
return eventType;
} : function() {
return window;
}
});
}
}
// run function defined in the attributes 'onpopstate/onhashchange' in the 'window' context
((eventType === 'popstate' ? window.onpopstate : window.onhashchange)
|| emptyFunction).call(window, eventObject);
// run other functions that are in the list of handlers
for(var i = 0, len = list.length; i < len; i++) {
list[i].call(window, eventObject);
}
return true;
} else {
return dispatch(event, eventObject);
}
}
/**
* dispatch current state event
*/
function firePopState() {
var o = document.createEvent ? document.createEvent('Event') : document.createEventObject();
if (o.initEvent) {
o.initEvent('popstate', false, false);
} else {
o.type = 'popstate';
}
o.state = historyObject.state;
// send a newly created events to be processed
dispatchEvent(o);
}
/**
* fire initial state for non-HTML5 browsers
*/
function fireInitialState() {
if (isFireInitialState) {
isFireInitialState = false;
firePopState();
}
}
/**
* Change the data of the current history for HTML4 browsers
*
* @param {Object} state
* @param {string} [url]
* @param {Boolean} [replace]
* @param {string} [lastURLValue]
* @return void
*/
function changeState(state, url, replace, lastURLValue) {
if (!isSupportHistoryAPI) {
// normalization url
var urlObject = parseURL(url);
// if current url not equal new url
if (urlObject._relative !== parseURL()._relative) {
// if empty lastURLValue to skip hash change event
lastURL = lastURLValue;
if (replace) {
// only replace hash, not store to history
windowLocation.replace("#" + urlObject._special);
} else {
// change hash and add new record to history
windowLocation.hash = urlObject._special;
}
}
}
if (!isSupportStateObjectInHistory && state) {
stateStorage[windowLocation.href] = state;
}
isFireInitialState = false;
}
/**
* Event handler function changes the hash in the address bar
*
* @param {Event} event
* @return void
*/
function onHashChange(event) {
// if not empty lastURL, otherwise skipped the current handler event
if (lastURL) {
// if checkUrlForPopState equal current url, this means that the event was raised popstate browser
if (checkUrlForPopState !== windowLocation.href) {
// otherwise,
// the browser does not support popstate event or just does not run the event by changing the hash.
firePopState();
}
// current event object
event = event || window.event;
var oldURLObject = parseURL(lastURL, true);
var newURLObject = parseURL();
// HTML4 browser not support properties oldURL/newURL
if (!event.oldURL) {
event.oldURL = oldURLObject._href;
event.newURL = newURLObject._href;
}
if (oldURLObject._hash !== newURLObject._hash) {
// if current hash not equal previous hash
dispatchEvent(event);
}
}
// new value to lastURL
lastURL = windowLocation.href;
}
/**
* The event handler is fully loaded document
*
* @param {*} [noScroll]
* @return void
*/
function onLoad(noScroll) {
// Get rid of the events popstate when the first loading a document in the webkit browsers
setTimeout(function() {
// hang up the event handler for the built-in popstate event in the browser
addEvent('popstate', function(e) {
// set the current url, that suppress the creation of the popstate event by changing the hash
checkUrlForPopState = windowLocation.href;
// for Safari browser in OS Windows not implemented 'state' object in 'History' interface
// and not implemented in old HTML4 browsers
if (!isSupportStateObjectInHistory) {
e = redefineProperty(e, 'state', {get: function() {
return historyObject.state;
}});
}
// send events to be processed
dispatchEvent(e);
}, false);
}, 0);
// for non-HTML5 browsers
if (!isSupportHistoryAPI && noScroll !== true && historyObject.location) {
// scroll window to anchor element
scrollToAnchorId(historyObject.location.hash);
// fire initial state for non-HTML5 browser after load page
fireInitialState();
}
}
/**
* handler url with anchor for non-HTML5 browsers
*
* @param e
*/
function onAnchorClick(e) {
var event = e || window.event;
var target = event.target || event.srcElement;
var defaultPrevented = "defaultPrevented" in event ? event['defaultPrevented'] : event.returnValue === false;
if (target && target.nodeName === "A" && !defaultPrevented) {
var current = parseURL();
var expect = parseURL(target.getAttribute("href", 2));
var isEqualBaseURL = current._href.split('#').shift() === expect._href.split('#').shift();
if (isEqualBaseURL && expect._hash) {
if (current._hash !== expect._hash) {
historyObject.location.hash = expect._hash;
}
scrollToAnchorId(expect._hash);
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
}
}
}
/**
* Scroll page to current anchor in url-hash
*
* @param hash
*/
function scrollToAnchorId(hash) {
var target = document.getElementById(hash = (hash || '').replace(/^#/, ''));
if (target && target.id === hash && target.nodeName === "A") {
var rect = target.getBoundingClientRect();
window.scrollTo((documentElement.scrollLeft || 0), rect.top + (documentElement.scrollTop || 0)
- (documentElement.clientTop || 0));
}
}
/**
* Library initialization
*
* @return {Boolean} return true if all is well, otherwise return false value
*/
function initialize() {
/**
* Get custom settings from the query string
*/
var scripts = document.getElementsByTagName('script');
var src = (scripts[scripts.length - 1] || {}).src || '';
var arg = src.indexOf('?') !== -1 ? src.split('?').pop() : '';
arg.replace(/(\w+)(?:=([^&]*))?/g, function(a, key, value) {
settings[key] = (value || (key === 'basepath' ? '/' : '')).replace(/^(0|false)$/, '');
});
/**
* sessionStorage throws error when cookies are disabled
* Chrome content settings when running the site in a Facebook IFrame.
* see: https://github.com/devote/HTML5-History-API/issues/34
*/
try {
sessionStorage = window['sessionStorage'];
} catch(_e_) {}
/**
* Includes support for IE6+
*/
ie6DriverStart();
/**
* hang up the event handler to listen to the events hashchange
*/
addEvent(eventNamePrefix + 'hashchange', onHashChange, false);
// a list of objects with pairs of descriptors/object
var data = [locationDescriptors, locationObject, eventsDescriptors, window, historyDescriptors, historyObject];
// if browser support object 'state' in interface 'History'
if (isSupportStateObjectInHistory) {
// remove state property from descriptor
delete historyDescriptors['state'];
}
// initializing descriptors
for(var i = 0; i < data.length; i += 2) {
for(var prop in data[i]) {
if (data[i].hasOwnProperty(prop)) {
if (typeof data[i][prop] === 'function') {
// If the descriptor is a simple function, simply just assign it an object
data[i + 1][prop] = data[i][prop];
} else {
// prepare the descriptor the required format
var descriptor = prepareDescriptorsForObject(data[i], prop, data[i][prop]);
// try to set the descriptor object
if (!redefineProperty(data[i + 1], prop, descriptor, function(n, o) {
// is satisfied if the failed override property
if (o === historyObject) {
// the problem occurs in Safari on the Mac
window.history = historyObject = data[i + 1] = n;
}
})) {
// if there is no possibility override.
// This browser does not support descriptors, such as IE7
// remove previously hung event handlers
removeEvent(eventNamePrefix + 'hashchange', onHashChange, false);
// fail to initialize :(
return false;
}
// create a repository for custom handlers onpopstate/onhashchange
if (data[i + 1] === window) {
eventsList[prop] = eventsList[prop.substr(2)] = [];
}
}
}
}
}
// redirect if necessary
if (settings['redirect']) {
historyObject['redirect']();
}
// If browser does not support object 'state' in interface 'History'
if (!isSupportStateObjectInHistory && JSON) {
storageInitialize();
}
// track clicks on anchors
if (!isSupportHistoryAPI) {
document[addEventListenerName](eventNamePrefix + "click", onAnchorClick, false);
}
if (document.readyState === 'complete') {
onLoad(true);
} else {
if (!isSupportHistoryAPI && parseURL()._relative !== settings["basepath"]) {
isFireInitialState = true;
}
/**
* Need to avoid triggering events popstate the initial page load.
* Hang handler popstate as will be fully loaded document that
* would prevent triggering event onpopstate
*/
addEvent(eventNamePrefix + 'load', onLoad, false);
}
// everything went well
return true;
}
/**
* Starting the library
*/
if (!initialize()) {
// if unable to initialize descriptors
// therefore quite old browser and there
// is no sense to continue to perform
return;
}
/**
* If the property history.emulate will be true,
* this will be talking about what's going on
* emulation capabilities HTML5-History-API.
* Otherwise there is no emulation, ie the
* built-in browser capabilities.
*
* @type {boolean}
* @const
*/
historyObject['emulate'] = !isSupportHistoryAPI;
/**
* Replace the original methods on the wrapper
*/
window[addEventListenerName] = addEventListener;
window[removeEventListenerName] = removeEventListener;
window[dispatchEventName] = dispatchEvent;
// ====================================================================================== //
// Driver for IE6+ or below
// ====================================================================================== //
function ie6DriverStart() {
/**
* Creates a static object
*
* @param object
* @returns {*}
*/
function createVBObjects(object) {
var properties = [];
var className = 'VBHistoryClass' + (new Date()).getTime() + msie++;
var staticClassParts = ["Class " + className];
for(var prop in object) {
if (object.hasOwnProperty(prop)) {
var value = object[prop];
if (value && (value.get || value.set)) {
if (value.get) {
staticClassParts.push(
'Public ' + (prop === '_' ? 'Default ' : '') + 'Property Get [' + prop + ']',
'Call VBCVal([(accessors)].[' + prop + '].get.call(me),[' + prop + '])',
'End Property'
);
}
if (value.set) {
staticClassParts.push('Public Property Let [' + prop + '](val)',
(value = 'Call [(accessors)].[' + prop + '].set.call(me,val)\nEnd Property'),
'Public Property Set [' + prop + '](val)', value);
}
} else {
properties.push(prop);
staticClassParts.push('Public [' + prop + ']');
}
}
}
staticClassParts.push(
'Private [(accessors)]',
'Private Sub Class_Initialize()',
'Set [(accessors)]=' + className + 'FactoryJS()',
'End Sub',
'End Class',
'Function ' + className + 'Factory()',
'Set ' + className + 'Factory=New ' + className,
'End Function'
);
window['execScript'](staticClassParts.join('\n'), 'VBScript');
window[className + 'FactoryJS'] = function() {
return object;
};
var result = window[className + 'Factory']();
for(var i = 0; i < properties.length; i++) {
result[properties[i]] = object[properties[i]];
}
return result;
}
/**
* Escape special symbols
*
* @param string
* @returns {string}
*/
function quote(string) {
var escapable = /[\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
var meta = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"': '\\"', '\\': '\\\\'};
return escapable.test(string) ? '"' + string.replace(escapable, function(a) {
return a in meta ? meta[a] : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' : '"' + string + '"';
}
// testing IE browser
var msie = window['eval'] && eval("/*@cc_on 1;@*/");
if (!msie || +((/msie (\d+)/i.exec(navigator.userAgent) || [, 8])[1]) > 7) {
// If it is not IE or a version greater than seven
return;
}
// save original links to methods
var originalChangeState = changeState;
var originalRedefineProperty = redefineProperty;
var currentHref = parseURL()._href;
var iFrame = document.createElement('iframe');
// insert IFRAME element to DOM
iFrame.src = "javascript:true;";
iFrame = documentElement.firstChild.appendChild(iFrame).contentWindow;
// correction value for VB Script
window['execScript'](
'Public history\nFunction VBCVal(o,r) If IsObject(o) Then Set r=o Else r=o End If End Function',
'VBScript'
);
// renew standard object
locationObject = {"_": {get: locationDescriptors.toString}};
historyObject = {
// properties to create an object in IE
"back": windowHistory.back,
"forward": windowHistory.forward,
"go": windowHistory.go,
"emulate": null,
"_": {get: function() {
return '[object History]';
}}
};
JSON = {
/**
* Analogue of JSON.parse()
*
* @param value
* @returns {*}
*/
"parse": function(value) {
try {
return new Function('', 'return ' + value)();
} catch(_e_) {
return null;
}
},
/**
* Analogue of JSON.stringify()
*
* @param value
* @returns {*}
*/
"stringify": function(value) {
var n = (typeof value).charCodeAt(2);
return n === 114 ? quote(value) : n === 109 ? isFinite(value) ? String(value) : 'null' : n === 111 || n
=== 108 ? String(value) : n === 106 ? !value ? 'null' : (function(isArray) {
var out = isArray ? '[' : '{';
if (isArray) {
for(var i = 0; i < value.length; i++) {
out += (i == 0 ? "" : ",") + JSON.stringify(value[i]);
}
} else {
for(var k in value) {
if (value.hasOwnProperty(k)) {
out += (out.length == 1 ? "" : ",") + quote(k) + ":" + JSON.stringify(value[k]);
}
}
}
return out + (isArray ? ']' : '}');
})(Object.prototype.toString.call(value) === '[object Array]') : 'void 0';
}
};
/**
* Change the data of the current history for IE6+
*/
changeState = function(state, url, replace, lastURLValue, lfirst) {
var iFrameDocument = iFrame.document;
var urlObject = parseURL(url);
isFireInitialState = false;
if (urlObject._relative === parseURL()._relative && !lfirst) {
if (state) {
stateStorage[windowLocation.href] = state;
}
return;
}
lastURL = lastURLValue;
if (replace) {
if (iFrame["lfirst"]) {
history.back();
changeState(state, urlObject._href, 0, lastURLValue, 1);
} else {
windowLocation.replace("#" + urlObject._special);
}
} else if (urlObject._href != currentHref || lfirst) {
if (!iFrame['lfirst']) {
iFrame["lfirst"] = 1;
changeState(state, currentHref, 0, lastURLValue, 1);
}
iFrameDocument.open();
iFrameDocument.write('\x3Cscript\x3Elfirst=1;parent.location.hash="'
+ urlObject._special.replace(/"/g, '\\"') + '";\x3C/script\x3E');
iFrameDocument.close();
}
if (!lfirst && state) {
stateStorage[windowLocation.href] = state;
}
};
/**
* See original method
*/
redefineProperty = function(object, prop, descriptor, onWrapped) {
if (!originalRedefineProperty.apply(this, arguments)) {
if (object === locationObject) {
locationObject[prop] = descriptor;
} else if (object === historyObject) {
historyObject[prop] = descriptor;
if (prop === 'state') {
locationObject = createVBObjects(locationObject);
window.history = historyObject = createVBObjects(historyObject);
}
} else {
object[prop] = descriptor.get && descriptor.get();
}
}
return object;
};
/**
* Tracking changes in the hash in the address bar
*/
var interval = setInterval(function() {
var href = parseURL()._href;
if (href != currentHref) {
var e = document.createEventObject();
e.oldURL = currentHref;
e.newURL = currentHref = href;
e.type = 'hashchange';
onHashChange(e);
}
}, 100);
window['JSON'] = JSON;
}
})(window);