blob: 52323e3a888222165d28df31f7221ec5bb39124b [file] [log] [blame]
/*******************************************************************************
* Copyright: 2004, 2012 1&1 Internet AG, Germany, http://www.1und1.de,
* and EclipseSource
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v1.0 which accompanies this
* distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* 1&1 Internet AG and others - original API and implementation
* EclipseSource - adaptation for the Eclipse Rich Ajax Platform
*
*
* This class contains code based on the following work:
*
* * Yahoo! UI Library
* http://developer.yahoo.com/yui
* Version 2.2.0
*
* Copyright:
* (c) 2007, Yahoo! Inc.
*
* License:
* BSD: http://developer.yahoo.com/yui/license.txt
*
******************************************************************************/
/**
* A helper for using the browser history in JavaScript Applications without
* reloading the main page.
*
* Adds entries to the browser history and fires a "request" event when one of
* the entries was requested by the user (e.g. by clicking on the back button).
*
* Browser history support is currently available for Internet Explorer 6/7,
* Firefox, Opera 9 and WebKit. Safari 2 and older are not yet supported.
*
* This module is based on the ideas behind the YUI Browser History Manager
* by Julien Lecomte (Yahoo), which is described at
* http://yuiblog.com/blog/2007/02/21/browser-history-manager/. The Yahoo
* implementation can be found at http://developer.yahoo.com/yui/history.
* The original code is licensed under a BSD license
* (http://developer.yahoo.com/yui/license.txt).
*/
qx.Class.define("rwt.client.History",
{
type : "singleton",
extend : qx.core.Target,
/*
*****************************************************************************
CONSTRUCTOR
*****************************************************************************
*/
/**
* @signature function()
*/
construct : rwt.util.Variant.select("qx.client",
{
"mshtml" : function()
{
this.base(arguments);
this._hasNavigationListener = false;
this._iframe = document.createElement("iframe");
this._iframe.style.visibility = "hidden";
this._iframe.style.position = "absolute";
this._iframe.style.left = "-1000px";
this._iframe.style.top = "-1000px";
/*
* 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
*/
var src = rwt.remote.Server.RESOURCE_PATH + "static/html/blank.html";
this._iframe.src = src;
document.body.appendChild(this._iframe);
this._titles = {};
this._state = decodeURIComponent(this.__getHash());
this._locationState = decodeURIComponent(this.__getHash());
this.__waitForIFrame(function()
{
this.__storeState(this._state);
this.__startTimer();
}, this);
},
"default" : function()
{
this.base(arguments);
this._hasNavigationListener = false;
this._titles = {};
this._state = this.__getState();
this.__startTimer();
}
}),
/*
*****************************************************************************
EVENTS
*****************************************************************************
*/
events: {
/**
* Fired when the user moved in the history. The data property of the event
* holds the state, which was passed to {@link #addToHistory}.
*/
"request" : "qx.event.type.DataEvent"
},
/*
*****************************************************************************
PROPERTIES
*****************************************************************************
*/
properties :
{
/**
* Interval for the timer, which periodically checks the browser history state
* in milliseconds.
*/
timeoutInterval :
{
check: "Number",
init : 100,
apply : "_applyTimeoutInterval"
}
},
/*
*****************************************************************************
MEMBERS
*****************************************************************************
*/
members :
{
/**
* Adds an entry to the browser history.
*
* @type member
* @param state {String} a string representing the state of the
* application. This command will be delivered in the data property of
* the "request" event.
* @param newTitle {String ? null} the page title to set after the history entry
* is done. This title should represent the new state of the application.
*/
addToHistory : function( state, newTitle ) {
if( newTitle != null ) {
document.title = newTitle;
}
this._titles[ state ] = document.title;
if( state != this._state ) {
// RAP [if] Prevent the event dispatch
this._state = state;
this.__storeState( state );
}
},
/**
* Get the current state of the browser history.
*
* @return {String} The current state
*/
getState : function() {
return this._state;
},
/**
* Navigates back in the browser history.
* Simulates a back button click.
*/
navigateBack : function() {
rwt.client.Timer.once(function() {history.back();}, 0);
},
/**
* Navigates forward in the browser history.
* Simulates a forward button click.
*/
navigateForward : function() {
rwt.client.Timer.once(function() {history.forward();}, 0);
},
/**
* Apply the interval of the timer.
*
* @type member
* @param newInterval {Integer} new timeout interval
*/
_applyTimeoutInterval : function(value) {
this._timer.setInterval(value);
},
/**
* called on changes to the history using the browser buttons
*
* @param state {String} new state of the history
*/
__onHistoryLoad : function(state) {
this._state = state;
this.createDispatchDataEvent("request", state);
if (this._titles[state] != null) {
document.title = this._titles[state];
}
},
/**
* Starts the timer polling for updates to the history IFrame on IE
* or the fragment identifier on other browsers.
*/
__startTimer : function() {
this._timer = new rwt.client.Timer(this.getTimeoutInterval());
this._timer.addEventListener( "interval", function( e ) {
var newHash = this.__getState();
// RAP [if] Ignore all non application states
//if (newHash != this._state) {
if( newHash != this._state && this._titles[ newHash ] != null ) {
this.__onHistoryLoad(newHash);
}
}, this );
if( this._hasNavigationListener ) {
this._timer.start();
}
},
/**
* Returns the fragment identifier of the top window URL
*
* @return {String} the fragment identifier
*/
__getHash : function()
{
// RAP [if] Fix for bug 295816
//var href = top.location.href;
var href = window.location.href;
var idx = href.indexOf( "#" );
return idx >= 0 ? href.substring(idx+1) : "";
},
/**
* Browser dependent function to read the current state of the history
*
* @return {String} current state of the browser history
*/
__getState : rwt.util.Variant.select("qx.client",
{
"mshtml" : function()
{
// the location only changes if the user manually changes the fragment
// identifier.
var locationState = decodeURIComponent(this.__getHash());
if (locationState != this._locationState)
{
this._locationState = locationState;
this.__storeState(locationState);
return locationState;
}
var doc = this._iframe.contentWindow.document;
var elem = doc.getElementById("state");
var iframeState = elem ? decodeURIComponent(elem.innerText) : "";
return iframeState;
},
"default" : function() {
return decodeURIComponent(this.__getHash());
}
}),
/**
* Save a state into the browser history.
*
* @param state {String} state to save
* @return {Boolean} Whether the state could be saved. This function may
* fail on the Internet Explorer if the hidden IFrame is not yet fully
* loaded.
*/
__storeState : rwt.util.Variant.select("qx.client",
{
"mshtml" : function( state ) {
var html = '<html><body><div id="state">' + encodeURIComponent(state) + '</div></body></html>';
try {
var doc = this._iframe.contentWindow.document;
doc.open();
doc.write(html);
doc.close();
} catch( ex ) {
return false;
}
return true;
},
"default" : function( state ) {
// RAP [if] Fix for bug 295816
//top.location.hash = "#" + encodeURIComponent(state);
window.location.hash = "#" + encodeURIComponent(state);
return true;
}
}),
/**
* 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.
*/
__waitForIFrame : rwt.util.Variant.select("qx.client",
{
"mshtml" : function(callback, context)
{
if ( !this._iframe.contentWindow || !this._iframe.contentWindow.document ) {
// Check again in 10 msec...
rwt.client.Timer.once(function() {
this.__waitForIFrame(callback, context);
}, this, 10);
return;
}
callback.call(context || window);
},
"default" : null
}),
setHasNavigationListener : function( value ) {
this._hasNavigationListener = value;
if( value ) {
this.addEventListener( "request", this._historyNavigated, this );
if( this._timer ) {
this._timer.start();
}
} else {
this.removeEventListener( "request", this._historyNavigated, this );
if( this._timer ) {
this._timer.stop();
}
}
},
_historyNavigated : function( event ) {
var entryId = event.getData();
var server = rwt.remote.Server.getInstance();
server.getServerObject( this ).notify( "historyNavigated", {
"entryId" : entryId
} );
}
},
/*
*****************************************************************************
DESTRUCTOR
*****************************************************************************
*/
destruct : function()
{
this._timer.stop();
this._disposeObjects("_timer");
this._disposeFields("_iframe", "_titles");
}
});