255 lines
5.5 KiB
JavaScript
255 lines
5.5 KiB
JavaScript
/**
|
|
@module Discourse
|
|
*/
|
|
|
|
var get = Ember.get, set = Ember.set;
|
|
var popstateFired = false;
|
|
var supportsHistoryState = window.history && 'state' in window.history;
|
|
|
|
// Thanks: https://gist.github.com/kares/956897
|
|
var re = /([^&=]+)=?([^&]*)/g;
|
|
var decode = function(str) {
|
|
return decodeURIComponent(str.replace(/\+/g, ' '));
|
|
};
|
|
$.parseParams = function(query) {
|
|
var params = {}, e;
|
|
if (query) {
|
|
if (query.substr(0, 1) === '?') {
|
|
query = query.substr(1);
|
|
}
|
|
|
|
while (e = re.exec(query)) {
|
|
var k = decode(e[1]);
|
|
var v = decode(e[2]);
|
|
if (params[k] !== undefined) {
|
|
if (!$.isArray(params[k])) {
|
|
params[k] = [params[k]];
|
|
}
|
|
params[k].push(v);
|
|
} else {
|
|
params[k] = v;
|
|
}
|
|
}
|
|
}
|
|
return params;
|
|
};
|
|
|
|
|
|
/**
|
|
`Ember.DiscourseLocation` implements the location API using the browser's
|
|
`history.pushState` API.
|
|
|
|
@class DiscourseLocation
|
|
@namespace Discourse
|
|
@extends Ember.Object
|
|
*/
|
|
Ember.DiscourseLocation = Ember.Object.extend({
|
|
|
|
init: function() {
|
|
set(this, 'location', get(this, 'location') || window.location);
|
|
this.initState();
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
Used to set state on first call to setURL
|
|
|
|
@method initState
|
|
*/
|
|
initState: function() {
|
|
|
|
var location = this.get('location');
|
|
if (location && location.search) {
|
|
this.set('queryParams', $.parseParams(location.search));
|
|
}
|
|
|
|
set(this, 'history', get(this, 'history') || window.history);
|
|
this.replaceState(this.formatURL(this.getURL()));
|
|
},
|
|
|
|
/**
|
|
Will be pre-pended to path upon state change
|
|
|
|
@property rootURL
|
|
@default '/'
|
|
*/
|
|
rootURL: '/',
|
|
|
|
/**
|
|
@private
|
|
|
|
Returns the current `location.pathname` without rootURL
|
|
|
|
@method getURL
|
|
*/
|
|
getURL: function() {
|
|
var rootURL = (Discourse.BaseUri === undefined ? "/" : Discourse.BaseUri),
|
|
url = get(this, 'location').pathname;
|
|
|
|
rootURL = rootURL.replace(/\/$/, '');
|
|
url = url.replace(rootURL, '');
|
|
|
|
return url;
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
Uses `history.pushState` to update the url without a page reload.
|
|
|
|
@method setURL
|
|
@param path {String}
|
|
*/
|
|
setURL: function(path) {
|
|
var state = this.getState();
|
|
path = this.formatURL(path);
|
|
|
|
if (state && state.path !== path) {
|
|
this.pushState(path);
|
|
}
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
Uses `history.replaceState` to update the url without a page reload
|
|
or history modification.
|
|
|
|
@method replaceURL
|
|
@param path {String}
|
|
*/
|
|
replaceURL: function(path) {
|
|
var state = this.getState();
|
|
path = this.formatURL(path);
|
|
|
|
if (state && state.path !== path) {
|
|
this.replaceState(path);
|
|
}
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
Get the current `history.state`
|
|
Polyfill checks for native browser support and falls back to retrieving
|
|
from a private _historyState variable
|
|
|
|
@method getState
|
|
*/
|
|
getState: function() {
|
|
return supportsHistoryState ? get(this, 'history').state : this._historyState;
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
Pushes a new state
|
|
|
|
@method pushState
|
|
@param path {String}
|
|
*/
|
|
pushState: function(path) {
|
|
var state = { path: path };
|
|
|
|
// store state if browser doesn't support `history.state`
|
|
if (!supportsHistoryState) {
|
|
this._historyState = state;
|
|
} else {
|
|
get(this, 'history').pushState(state, null, path);
|
|
}
|
|
|
|
// used for webkit workaround
|
|
this._previousURL = this.getURL();
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
Replaces the current state
|
|
|
|
@method replaceState
|
|
@param path {String}
|
|
*/
|
|
replaceState: function(path) {
|
|
var state = { path: path };
|
|
|
|
// store state if browser doesn't support `history.state`
|
|
if (!supportsHistoryState) {
|
|
this._historyState = state;
|
|
} else {
|
|
get(this, 'history').replaceState(state, null, path);
|
|
}
|
|
|
|
// used for webkit workaround
|
|
this._previousURL = this.getURL();
|
|
},
|
|
|
|
|
|
queryParamsString: function() {
|
|
var params = this.get('queryParams');
|
|
if (Em.isEmpty(params) || Em.isEmpty(Object.keys(params))) {
|
|
return "";
|
|
} else {
|
|
return "?" + decodeURIComponent($.param(params, true));
|
|
}
|
|
}.property('queryParams'),
|
|
|
|
// When our query params change, update the URL
|
|
queryParamsStringChanged: function() {
|
|
var loc = this;
|
|
Em.run.next(function () {
|
|
loc.replaceState(loc.formatURL(loc.getURL()));
|
|
});
|
|
}.observes('queryParamsString'),
|
|
|
|
/**
|
|
@private
|
|
|
|
Register a callback to be invoked whenever the browser
|
|
history changes, including using forward and back buttons.
|
|
|
|
@method onUpdateURL
|
|
@param callback {Function}
|
|
*/
|
|
onUpdateURL: function(callback) {
|
|
var guid = Ember.guidFor(this),
|
|
self = this;
|
|
|
|
Ember.$(window).on('popstate.ember-location-'+guid, function(e) {
|
|
// Ignore initial page load popstate event in Chrome
|
|
if (!popstateFired) {
|
|
popstateFired = true;
|
|
if (self.getURL() === self._previousURL) { return; }
|
|
}
|
|
callback(self.getURL());
|
|
});
|
|
},
|
|
|
|
/**
|
|
@private
|
|
|
|
Used when using `{{action}}` helper. The url is always appended to the rootURL.
|
|
|
|
@method formatURL
|
|
@param url {String}
|
|
*/
|
|
formatURL: function(url) {
|
|
var rootURL = get(this, 'rootURL');
|
|
|
|
if (url !== '') {
|
|
rootURL = rootURL.replace(/\/$/, '');
|
|
}
|
|
|
|
return rootURL + url + this.get('queryParamsString');
|
|
},
|
|
|
|
willDestroy: function() {
|
|
var guid = Ember.guidFor(this);
|
|
|
|
Ember.$(window).off('popstate.ember-location-'+guid);
|
|
}
|
|
|
|
});
|
|
|
|
Ember.Location.registerImplementation('discourse_location', Ember.DiscourseLocation);
|