import {
get,
set
} from 'ember-metal';
import { Object as EmberObject } from 'ember-runtime';
import EmberLocation from './api';
/**
@module ember
@submodule ember-routing
*/
let popstateFired = false;
/**
Ember.HistoryLocation implements the location API using the browser's
history.pushState API.
@class HistoryLocation
@namespace Ember
@extends Ember.Object
@private
*/
export default EmberObject.extend({
implementation: 'history',
init() {
this._super(...arguments);
let base = document.querySelector('base');
let baseURL = base ? base.getAttribute('href') : '';
set(this, 'baseURL', baseURL);
set(this, 'location', get(this, 'location') || window.location);
this._popstateHandler = undefined;
},
/**
Used to set state on first call to setURL
@private
@method initState
*/
initState() {
let history = get(this, 'history') || window.history;
set(this, 'history', history);
if (history && 'state' in history) {
this.supportsHistory = true;
}
this.replaceState(this.formatURL(this.getURL()));
},
/**
Will be pre-pended to path upon state change
@property rootURL
@default '/'
@private
*/
rootURL: '/',
/**
Returns the current `location.pathname` without `rootURL` or `baseURL`
@private
@method getURL
@return url {String}
*/
getURL() {
let location = get(this, 'location');
let path = location.pathname;
let rootURL = get(this, 'rootURL');
let baseURL = get(this, 'baseURL');
// remove trailing slashes if they exists
rootURL = rootURL.replace(/\/$/, '');
baseURL = baseURL.replace(/\/$/, '');
// remove baseURL and rootURL from start of path
let url = path
.replace(new RegExp('^' + baseURL + '(?=/|$)'), '')
.replace(new RegExp('^' + rootURL + '(?=/|$)'), '');
let search = location.search || '';
url += search;
url += this.getHash();
return url;
},
/**
Uses `history.pushState` to update the url without a page reload.
@private
@method setURL
@param path {String}
*/
setURL(path) {
let state = this.getState();
path = this.formatURL(path);
if (!state || state.path !== path) {
this.pushState(path);
}
},
/**
Uses `history.replaceState` to update the url without a page reload
or history modification.
@private
@method replaceURL
@param path {String}
*/
replaceURL(path) {
let state = this.getState();
path = this.formatURL(path);
if (!state || state.path !== path) {
this.replaceState(path);
}
},
/**
Get the current `history.state`. Checks for if a polyfill is
required and if so fetches this._historyState. The state returned
from getState may be null if an iframe has changed a window's
history.
@private
@method getState
@return state {Object}
*/
getState() {
if (this.supportsHistory) {
return get(this, 'history').state;
}
return this._historyState;
},
/**
Pushes a new state.
@private
@method pushState
@param path {String}
*/
pushState(path) {
let state = { path: path };
get(this, 'history').pushState(state, null, path);
this._historyState = state;
// used for webkit workaround
this._previousURL = this.getURL();
},
/**
Replaces the current state.
@private
@method replaceState
@param path {String}
*/
replaceState(path) {
let state = { path: path };
get(this, 'history').replaceState(state, null, path);
this._historyState = state;
// used for webkit workaround
this._previousURL = this.getURL();
},
/**
Register a callback to be invoked whenever the browser
history changes, including using forward and back buttons.
@private
@method onUpdateURL
@param callback {Function}
*/
onUpdateURL(callback) {
this._removeEventListener();
this._popstateHandler = () => {
// Ignore initial page load popstate event in Chrome
if (!popstateFired) {
popstateFired = true;
if (this.getURL() === this._previousURL) { return; }
}
callback(this.getURL());
};
window.addEventListener('popstate', this._popstateHandler);
},
/**
Used when using `{{action}}` helper. The url is always appended to the rootURL.
@private
@method formatURL
@param url {String}
@return formatted url {String}
*/
formatURL(url) {
let rootURL = get(this, 'rootURL');
let baseURL = get(this, 'baseURL');
if (url !== '') {
// remove trailing slashes if they exists
rootURL = rootURL.replace(/\/$/, '');
baseURL = baseURL.replace(/\/$/, '');
} else if (baseURL.match(/^\//) && rootURL.match(/^\//)) {
// if baseURL and rootURL both start with a slash
// ... remove trailing slash from baseURL if it exists
baseURL = baseURL.replace(/\/$/, '');
}
return baseURL + rootURL + url;
},
/**
Cleans up the HistoryLocation event listener.
@private
@method willDestroy
*/
willDestroy() {
this._removeEventListener();
},
/**
@private
Returns normalized location.hash
@method getHash
*/
getHash: EmberLocation._getHash,
_removeEventListener() {
if (this._popstateHandler) {
window.removeEventListener('popstate', this._popstateHandler);
}
}
});