@qooxdoo/framework
Version:
The JS Framework for Coders
322 lines (277 loc) • 7.97 kB
JavaScript
/* ************************************************************************
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
);
}
});