Path: blob/trunk/third_party/closure/goog/history/html5history.js
2868 views
// Copyright 2010 The Closure Library Authors. All Rights Reserved.1//2// Licensed under the Apache License, Version 2.0 (the "License");3// you may not use this file except in compliance with the License.4// You may obtain a copy of the License at5//6// http://www.apache.org/licenses/LICENSE-2.07//8// Unless required by applicable law or agreed to in writing, software9// distributed under the License is distributed on an "AS-IS" BASIS,10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.11// See the License for the specific language governing permissions and12// limitations under the License.1314/**15* @fileoverview HTML5 based history implementation, compatible with16* goog.History.17*18* TODO(user): There should really be a history interface and multiple19* implementations.20*21*/222324goog.provide('goog.history.Html5History');25goog.provide('goog.history.Html5History.TokenTransformer');2627goog.require('goog.asserts');28goog.require('goog.events');29goog.require('goog.events.EventTarget');30goog.require('goog.events.EventType');31goog.require('goog.history.Event');32333435/**36* An implementation compatible with goog.History that uses the HTML537* history APIs.38*39* @param {Window=} opt_win The window to listen/dispatch history events on.40* @param {goog.history.Html5History.TokenTransformer=} opt_transformer41* The token transformer that is used to create URL from the token42* when storing token without using hash fragment.43* @constructor44* @extends {goog.events.EventTarget}45* @final46*/47goog.history.Html5History = function(opt_win, opt_transformer) {48goog.events.EventTarget.call(this);49goog.asserts.assert(50goog.history.Html5History.isSupported(opt_win),51'HTML5 history is not supported.');5253/**54* The window object to use for history tokens. Typically the top window.55* @type {Window}56* @private57*/58this.window_ = opt_win || window;5960/**61* The token transformer that is used to create URL from the token62* when storing token without using hash fragment.63* @type {goog.history.Html5History.TokenTransformer}64* @private65*/66this.transformer_ = opt_transformer || null;6768/**69* The fragment of the last navigation. Used to eliminate duplicate/redundant70* NAVIGATE events when a POPSTATE and HASHCHANGE event are triggered for the71* same navigation (e.g., back button click).72* @private {?string}73*/74this.lastFragment_ = null;7576goog.events.listen(77this.window_, goog.events.EventType.POPSTATE, this.onHistoryEvent_, false,78this);79goog.events.listen(80this.window_, goog.events.EventType.HASHCHANGE, this.onHistoryEvent_,81false, this);82};83goog.inherits(goog.history.Html5History, goog.events.EventTarget);848586/**87* Returns whether Html5History is supported.88* @param {Window=} opt_win Optional window to check.89* @return {boolean} Whether html5 history is supported.90*/91goog.history.Html5History.isSupported = function(opt_win) {92var win = opt_win || window;93return !!(win.history && win.history.pushState);94};959697/**98* Status of when the object is active and dispatching events.99* @type {boolean}100* @private101*/102goog.history.Html5History.prototype.enabled_ = false;103104105/**106* Whether to use the fragment to store the token, defaults to true.107* @type {boolean}108* @private109*/110goog.history.Html5History.prototype.useFragment_ = true;111112113/**114* If useFragment is false the path will be used, the path prefix will be115* prepended to all tokens. Defaults to '/'.116* @type {string}117* @private118*/119goog.history.Html5History.prototype.pathPrefix_ = '/';120121122/**123* Starts or stops the History. When enabled, the History object124* will immediately fire an event for the current location. The caller can set125* up event listeners between the call to the constructor and the call to126* setEnabled.127*128* @param {boolean} enable Whether to enable history.129*/130goog.history.Html5History.prototype.setEnabled = function(enable) {131if (enable == this.enabled_) {132return;133}134135this.enabled_ = enable;136137if (enable) {138this.dispatchEvent(new goog.history.Event(this.getToken(), false));139}140};141142143/**144* Returns the current token.145* @return {string} The current token.146*/147goog.history.Html5History.prototype.getToken = function() {148if (this.useFragment_) {149return goog.asserts.assertString(this.getFragment_());150} else {151return this.transformer_ ?152this.transformer_.retrieveToken(153this.pathPrefix_, this.window_.location) :154this.window_.location.pathname.substr(this.pathPrefix_.length);155}156};157158159/**160* Sets the history state.161* @param {string} token The history state identifier.162* @param {string=} opt_title Optional title to associate with history entry.163*/164goog.history.Html5History.prototype.setToken = function(token, opt_title) {165if (token == this.getToken()) {166return;167}168169// Per externs/gecko_dom.js document.title can be null.170this.window_.history.pushState(171null, opt_title || this.window_.document.title || '',172this.getUrl_(token));173this.dispatchEvent(new goog.history.Event(token, false));174};175176177/**178* Replaces the current history state without affecting the rest of the history179* stack.180* @param {string} token The history state identifier.181* @param {string=} opt_title Optional title to associate with history entry.182*/183goog.history.Html5History.prototype.replaceToken = function(token, opt_title) {184// Per externs/gecko_dom.js document.title can be null.185this.window_.history.replaceState(186null, opt_title || this.window_.document.title || '',187this.getUrl_(token));188this.dispatchEvent(new goog.history.Event(token, false));189};190191192/** @override */193goog.history.Html5History.prototype.disposeInternal = function() {194goog.events.unlisten(195this.window_, goog.events.EventType.POPSTATE, this.onHistoryEvent_, false,196this);197if (this.useFragment_) {198goog.events.unlisten(199this.window_, goog.events.EventType.HASHCHANGE, this.onHistoryEvent_,200false, this);201}202};203204205/**206* Sets whether to use the fragment to store tokens.207* @param {boolean} useFragment Whether to use the fragment.208*/209goog.history.Html5History.prototype.setUseFragment = function(useFragment) {210if (this.useFragment_ != useFragment) {211if (useFragment) {212goog.events.listen(213this.window_, goog.events.EventType.HASHCHANGE, this.onHistoryEvent_,214false, this);215} else {216goog.events.unlisten(217this.window_, goog.events.EventType.HASHCHANGE, this.onHistoryEvent_,218false, this);219}220this.useFragment_ = useFragment;221}222};223224225/**226* Sets the path prefix to use if storing tokens in the path. The path227* prefix should start and end with slash.228* @param {string} pathPrefix Sets the path prefix.229*/230goog.history.Html5History.prototype.setPathPrefix = function(pathPrefix) {231this.pathPrefix_ = pathPrefix;232};233234235/**236* Gets the path prefix.237* @return {string} The path prefix.238*/239goog.history.Html5History.prototype.getPathPrefix = function() {240return this.pathPrefix_;241};242243244/**245* Gets the current hash fragment, if useFragment_ is enabled.246* @return {?string} The hash fragment.247* @private248*/249goog.history.Html5History.prototype.getFragment_ = function() {250if (this.useFragment_) {251var loc = this.window_.location.href;252var index = loc.indexOf('#');253return index < 0 ? '' : loc.substring(index + 1);254} else {255return null;256}257};258259260/**261* Gets the URL to set when calling history.pushState262* @param {string} token The history token.263* @return {string} The URL.264* @private265*/266goog.history.Html5History.prototype.getUrl_ = function(token) {267if (this.useFragment_) {268return '#' + token;269} else {270return this.transformer_ ?271this.transformer_.createUrl(272token, this.pathPrefix_, this.window_.location) :273this.pathPrefix_ + token + this.window_.location.search;274}275};276277278/**279* Handles history events dispatched by the browser.280* @param {goog.events.BrowserEvent} e The browser event object.281* @private282*/283goog.history.Html5History.prototype.onHistoryEvent_ = function(e) {284if (this.enabled_) {285var fragment = this.getFragment_();286// Only fire NAVIGATE event if it's POPSTATE or if the fragment has changed287// without a POPSTATE event. The latter is an indication the browser doesn't288// support POPSTATE, and the event is a HASHCHANGE instead.289if (e.type == goog.events.EventType.POPSTATE ||290fragment != this.lastFragment_) {291this.lastFragment_ = fragment;292this.dispatchEvent(new goog.history.Event(this.getToken(), true));293}294}295};296297298299/**300* A token transformer that can create a URL from a history301* token. This is used by {@code goog.history.Html5History} to create302* URL when storing token without the hash fragment.303*304* Given a {@code window.location} object containing the location305* created by {@code createUrl}, the token transformer allows306* retrieval of the token back via {@code retrieveToken}.307*308* @interface309*/310goog.history.Html5History.TokenTransformer = function() {};311312313/**314* Retrieves a history token given the path prefix and315* {@code window.location} object.316*317* @param {string} pathPrefix The path prefix to use when storing token318* in a path; always begin with a slash.319* @param {Location} location The {@code window.location} object.320* Treat this object as read-only.321* @return {string} token The history token.322*/323goog.history.Html5History.TokenTransformer.prototype.retrieveToken = function(324pathPrefix, location) {};325326327/**328* Creates a URL to be pushed into HTML5 history stack when storing329* token without using hash fragment.330*331* @param {string} token The history token.332* @param {string} pathPrefix The path prefix to use when storing token333* in a path; always begin with a slash.334* @param {Location} location The {@code window.location} object.335* Treat this object as read-only.336* @return {string} url The complete URL string from path onwards337* (without {@code protocol://host:port} part); must begin with a338* slash.339*/340goog.history.Html5History.TokenTransformer.prototype.createUrl = function(341token, pathPrefix, location) {};342343344