UNPKG

@qooxdoo/framework

Version:

The JS Framework for Coders

327 lines (265 loc) 7.98 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 : function() { this.base(arguments); this.__initTimer(); }, members : { __iframe : null, __iframeReady : false, __writeStateTimner : null, __dontApplyState : null, __locationState : null, // overridden _setInitialState : function() { this.base(arguments); this.__locationState = this._getHash(); }, //overridden _setHash : function(value) { this.base(arguments, value); this.__locationState = this._encode(value); }, //overridden addToHistory : function(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 : function(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 : function(state) { this.__dontApplyState = true; this.setState(state); this.__dontApplyState = false; }, //overridden _applyState : function(value, old) { if (this.__dontApplyState){ return; } this._writeState(value); }, /** * Get state from the iframe * * @return {String} current state of the browser history */ _readState : function() { 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 : function(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 : function() { if (this.__writeStateTimner){ this.__writeStateTimner.stop(); this.__writeStateTimner.dispose(); } }, /** * Initialize the polling timer */ __initTimer : function() { this.__initIframe(function () { qx.event.Idle.getInstance().addListener("interval", this.__onHashChange, this); }); }, /** * Hash change listener. * * @param e {qx.event.type.Event} event instance */ __onHashChange : function(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 : function (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 : function (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 : function(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 : function () { 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 : function(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 : function() { this.__iframe = null; if (this.__writeStateTimner){ this.__writeStateTimner.dispose(); this.__writeStateTimner = null; } qx.event.Idle.getInstance().removeListener("interval", this.__onHashChange, this); } });