UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

322 lines (277 loc) 7.97 kB
/* ************************************************************************ qooxdoo - the new era of web development http://qooxdoo.org Copyright: 2004-2008 1&1 Internet AG, Germany, http://www.1und1.de License: MIT: https://opensource.org/licenses/MIT See the LICENSE file in the project's top-level directory for details. Authors: * Sebastian Werner (wpbasti) * Andreas Ecker (ecker) * Fabian Jakobs (fjakobs) * Mustafa Sak (msak) ************************************************************************ */ /** * Implements an iFrame based history manager for IE 6/7/8. * * Creates a hidden iFrame and uses document.write to store entries in the * history browser's stack. * * This class must be disposed of after use * * @internal */ qx.Class.define("qx.bom.IframeHistory", { extend: qx.bom.History, implement: [qx.core.IDisposable], construct() { super(); this.__initTimer(); }, members: { __iframe: null, __iframeReady: false, __writeStateTimner: null, __dontApplyState: null, __locationState: null, // overridden _setInitialState() { super._setInitialState(); this.__locationState = this._getHash(); }, //overridden _setHash(value) { super._setHash(value); this.__locationState = this._encode(value); }, //overridden addToHistory(state, newTitle) { if (!qx.lang.Type.isString(state)) { state = state + ""; } if (qx.lang.Type.isString(newTitle)) { this.setTitle(newTitle); this._titles[state] = newTitle; } if (this.getState() !== state) { this.setState(state); } this.fireDataEvent("request", state); }, //overridden _onHistoryLoad(state) { this._setState(state); this.fireDataEvent("request", state); if (this._titles[state] != null) { this.setTitle(this._titles[state]); } }, /** * Helper function to set state property. This will only be called * by _onHistoryLoad. It determines, that no apply of state will be called. * @param state {String} State loaded from history */ _setState(state) { this.__dontApplyState = true; this.setState(state); this.__dontApplyState = false; }, //overridden _applyState(value, old) { if (this.__dontApplyState) { return; } this._writeState(value); }, /** * Get state from the iframe * * @return {String} current state of the browser history */ _readState() { if (!this.__iframeReady) { return this._decode(this._getHash()); } var doc = this.__iframe.contentWindow.document; var elem = doc.getElementById("state"); return elem ? this._decode(elem.innerText) : ""; }, /** * Store state to the iframe * * @param state {String} state to save */ _writeState(state) { if (!this.__iframeReady) { this.__clearWriteSateTimer(); this.__writeStateTimner = qx.event.Timer.once( function () { this._writeState(state); }, this, 50 ); return; } this.__clearWriteSateTimer(); var state = this._encode(state); // IE8 is sometimes recognizing a hash change as history entry. Cause of sporadic surface of this behavior, we have to prevent setting hash. if ( qx.core.Environment.get("engine.name") == "mshtml" && qx.core.Environment.get("browser.version") != 8 ) { this._setHash(state); } var doc = this.__iframe.contentWindow.document; doc.open(); doc.write( '<html><body><div id="state">' + state + "</div></body></html>" ); doc.close(); }, /** * Helper function to clear the write state timer. */ __clearWriteSateTimer() { if (this.__writeStateTimner) { this.__writeStateTimner.stop(); this.__writeStateTimner.dispose(); } }, /** * Initialize the polling timer */ __initTimer() { this.__initIframe(function () { qx.event.Idle.getInstance().addListener( "interval", this.__onHashChange, this ); }); }, /** * Hash change listener. * * @param e {qx.event.type.Event} event instance */ __onHashChange(e) { // the location only changes if the user manually changes the fragment // identifier. var currentState = null; var locationState = this._getHash(); if (!this.__isCurrentLocationState(locationState)) { currentState = this.__storeLocationState(locationState); } else { currentState = this._readState(); } if ( qx.lang.Type.isString(currentState) && currentState != this.getState() ) { this._onHistoryLoad(currentState); } }, /** * Stores the given location state. * * @param locationState {String} location state * @return {String} */ __storeLocationState(locationState) { locationState = this._decode(locationState); this._writeState(locationState); return locationState; }, /** * Checks whether the given location state is the current one. * * @param locationState {String} location state to check * @return {Boolean} */ __isCurrentLocationState(locationState) { return ( qx.lang.Type.isString(locationState) && locationState == this.__locationState ); }, /** * Initializes the iframe * * @param handler {Function?null} if given this callback is executed after iframe is ready to use */ __initIframe(handler) { this.__iframe = this.__createIframe(); document.body.appendChild(this.__iframe); this.__waitForIFrame(function () { this._writeState(this.getState()); if (handler) { handler.call(this); } }, this); }, /** * IMPORTANT NOTE FOR IE: * Setting the source before adding the iframe to the document. * Otherwise IE will bring up a "Unsecure items ..." warning in SSL mode * * @return {qx.bom.Iframe} */ __createIframe() { var iframe = qx.bom.Iframe.create({ src: qx.util.ResourceManager.getInstance().toUri( qx.core.Environment.get("qx.blankpage") ) }); iframe.style.visibility = "hidden"; iframe.style.position = "absolute"; iframe.style.left = "-1000px"; iframe.style.top = "-1000px"; return iframe; }, /** * Waits for the IFrame being loaded. Once the IFrame is loaded * the callback is called with the provided context. * * @param callback {Function} This function will be called once the iframe is loaded * @param context {Object?window} The context for the callback. * @param retry {Integer} number of tries to initialize the iframe */ __waitForIFrame(callback, context, retry) { if (typeof retry === "undefined") { retry = 0; } if ( !this.__iframe.contentWindow || !this.__iframe.contentWindow.document ) { if (retry > 20) { throw new Error("can't initialize iframe"); } qx.event.Timer.once( function () { this.__waitForIFrame(callback, context, ++retry); }, this, 10 ); return; } this.__iframeReady = true; callback.call(context || window); } }, destruct() { this.__iframe = null; if (this.__writeStateTimner) { this.__writeStateTimner.dispose(); this.__writeStateTimner = null; } qx.event.Idle.getInstance().removeListener( "interval", this.__onHashChange, this ); } });