@qooxdoo/framework
Version:
The JS Framework for Coders
408 lines (325 loc) • 10.9 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)
======================================================================
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
----------------------------------------------------------------------
http://developer.yahoo.com/yui/license.html
Copyright (c) 2009, Yahoo! Inc.
All rights reserved.
Redistribution and use of this software in source and binary forms,
with or without modification, are permitted provided that the
following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name of Yahoo! Inc. nor the names of its contributors
may be used to endorse or promote products derived from this
software without specific prior written permission of Yahoo! Inc.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
OF THE POSSIBILITY OF SUCH DAMAGE.
************************************************************************ */
/* ************************************************************************
************************************************************************ */
/**
* 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).
*
* This class is an abstract template class. Concrete implementations have to
* provide implementations for the {@link #_readState} and {@link #_writeState}
* methods.
*
* 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).
*
* @asset(qx/static/blank.html)
*/
qx.Class.define("qx.bom.History",
{
extend : qx.core.Object,
type : "abstract",
/*
*****************************************************************************
CONSTRUCTOR
*****************************************************************************
*/
construct : function()
{
this.base(arguments);
this._baseUrl = window.location.href.split('#')[0] + '#';
this._titles = {};
this._setInitialState();
},
/*
*****************************************************************************
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.Data"
},
/*
*****************************************************************************
STATICS
*****************************************************************************
*/
statics :
{
/**
* @type {Boolean} Whether the browser supports the 'hashchange' event natively.
*/
SUPPORTS_HASH_CHANGE_EVENT : qx.core.Environment.get("event.hashchange"),
/**
* Get the singleton instance of the history manager.
*
* @return {History}
*/
getInstance : function()
{
var runsInIframe = !(window == window.top);
if (!this.$$instance)
{
// in iframe + IE9
if (runsInIframe
&& qx.core.Environment.get("browser.documentmode") == 9
) {
this.$$instance = new qx.bom.HashHistory();
}
// in iframe + IE<9
else if (runsInIframe
&& qx.core.Environment.get("engine.name") == "mshtml"
&& qx.core.Environment.get("browser.documentmode") < 9
) {
this.$$instance = new qx.bom.IframeHistory();
}
// browser with hashChange event
else if (this.SUPPORTS_HASH_CHANGE_EVENT) {
this.$$instance = new qx.bom.NativeHistory();
}
// IE without hashChange event
else if ((qx.core.Environment.get("engine.name") == "mshtml")) {
this.$$instance = new qx.bom.IframeHistory();
}
// fallback
else {
this.$$instance = new qx.bom.NativeHistory();
}
}
return this.$$instance;
}
},
/*
*****************************************************************************
PROPERTIES
*****************************************************************************
*/
properties :
{
/**
* Property holding the current title
*/
title :
{
check : "String",
event : "changeTitle",
nullable : true,
apply : "_applyTitle"
},
/**
* Property holding the current state of the history.
*/
state :
{
check : "String",
event : "changeState",
nullable : true,
apply: "_applyState"
}
},
/*
*****************************************************************************
MEMBERS
*****************************************************************************
*/
members :
{
_titles : null,
// property apply
_applyState : function(value, old)
{
this._writeState(value);
},
/**
* Populates the 'state' property with the initial state value
*/
_setInitialState : function() {
this.setState(this._readState());
},
/**
* Encodes the state value into a format suitable as fragment identifier.
*
* @param value {String} The string to encode
* @return {String} The encoded string
*/
_encode : function (value)
{
if (qx.lang.Type.isString(value)) {
return encodeURIComponent(value);
}
return "";
},
/**
* Decodes a fragment identifier into a string
*
* @param value {String} The fragment identifier
* @return {String} The decoded fragment identifier
*/
_decode : function (value)
{
if (qx.lang.Type.isString(value)) {
return decodeURIComponent(value);
}
return "";
},
// property apply
_applyTitle : function (title)
{
if (title != null) {
document.title = title || "";
}
},
/**
* Adds an entry to the browser history.
*
* @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 (!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._writeState(state);
}
},
/**
* Navigates back in the browser history.
* Simulates a back button click.
*/
navigateBack : function() {
qx.event.Timer.once(function() {history.back();}, this, 100);
},
/**
* Navigates forward in the browser history.
* Simulates a forward button click.
*/
navigateForward : function() {
qx.event.Timer.once(function() {history.forward();}, this, 100);
},
/**
* Called on changes to the history using the browser buttons.
*
* @param state {String} new state of the history
*/
_onHistoryLoad : function(state)
{
this.setState(state);
this.fireDataEvent("request", state);
if (this._titles[state] != null) {
this.setTitle(this._titles[state]);
}
},
/**
* Browser dependent function to read the current state of the history
*
* @return {String} current state of the browser history
*/
_readState : function() {
throw new Error("Abstract method call");
},
/**
* Save a state into the browser history.
*
* @param state {String} state to save
*/
_writeState : function(state) {
throw new Error("Abstract method call");
},
/**
* Sets the fragment identifier of the window URL
*
* @param value {String} the fragment identifier
*/
_setHash : function (value)
{
var url = this._baseUrl + (value || "");
var loc = window.location;
if (url != loc.href) {
loc.href = url;
}
},
/**
* Returns the fragment identifier of the top window URL. For gecko browsers we
* have to use a regular expression to avoid encoding problems.
*
* @return {String} the fragment identifier
*/
_getHash : function()
{
var hash = /#(.*)$/.exec(window.location.href);
return hash && hash[1] ? hash[1] : "";
}
}
});