UNPKG

page-app

Version:

Builder for rich single-page js apps (frontend)

1,170 lines (1,115 loc) 46.3 kB
/* * 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);