@qooxdoo/framework
Version:
The JS Framework for Coders
327 lines (265 loc) • 7.98 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 : 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);
}
});