diff --git a/lib/application.js b/lib/application.js
index 27796cd..59e4e59 100755
--- a/lib/application.js
+++ b/lib/application.js
@@ -26,7 +26,8 @@ export default class Application {
constructor() {
this.routers = [];
- this.DOMLoading = false;
+ this.isDOMLoaded = false;
+ this.isDOMReady = false;
this.settings = new Settings();
}
@@ -76,33 +77,40 @@ export default class Application {
*/
listen(callback) {
- document.onreadystatechange = () => {
- const uri = window.location.pathname + window.location.search;
- const method = 'GET';
- const request = {method, uri};
- const response = {status: 200, statusText: 'OK'};
+ window.onbeforeunload = () => {
+ this._callMiddlewareExited();
+ };
- // gathers all routes impacted by the current browser location
- const currentRoutes = this._routes(uri, method);
+ window.onpopstate = (event) => {
+ if (event.state) {
+ const {response, request} = event.state;
+ const currentRoutes = this._routes(request.uri, request.method);
- // listen dom events
- if (document.readyState === 'loading') {
- this.DOMLoading = true;
this._callMiddlewareEntered(currentRoutes, request);
- } else if (document.readyState === 'interactive') {
- if (!this.DOMLoading) {
+ this._callMiddlewareUpdated(currentRoutes, request, response);
+ }
+ };
+
+ document.onreadystatechange = () => {
+ const request = {method: 'GET', uri: window.location.pathname + window.location.search};
+ const response = {status: 200, statusText: 'OK'};
+ const currentRoutes = this._routes();
+ // DOM state
+ if (document.readyState === 'loading' && !this.isDOMLoaded) {
+ this.isDOMLoaded = true;
+ this._callMiddlewareEntered(currentRoutes, request);
+ } else if (document.readyState === 'interactive' && !this.isDOMReady) {
+ if (!this.isDOMLoaded) {
+ this.isDOMLoaded = true;
this._callMiddlewareEntered(currentRoutes, request);
}
+ this.isDOMReady = true;
this._callMiddlewareUpdated(currentRoutes, request, response);
if (callback) {
callback(request, response);
}
}
};
-
- window.addEventListener('beforeunload', () => {
- this._callMiddlewareExited();
- });
}
@@ -183,12 +191,13 @@ export default class Application {
* @private
*/
- _routes(uri, method) {
+ _routes(uri=window.location.pathname + window.location.search, method='GET') {
const currentRoutes = [];
for (const router of this.routers) {
const routes = router.routes(uri, method);
currentRoutes.push(...routes);
}
+
return currentRoutes;
}
@@ -297,13 +306,12 @@ export default class Application {
/**
- * Make an ajax request.
+ * Make an ajax request. Manage History#pushState if history object set.
*
* @private
*/
- _fetch({method, uri, headers, data}, resolve, reject) {
-
+ _fetch({method, uri, headers, data, history}, resolve, reject) {
const httpMethodTransformer = this.get(`http ${method} transformer`);
if (httpMethodTransformer) {
uri = httpMethodTransformer.uri ? httpMethodTransformer.uri({uri, headers, data}) : uri;
@@ -323,6 +331,13 @@ export default class Application {
// invokes http request
this.settings.get('http requester').fetch({method, uri, headers, data},
(request, response) => {
+ if (history) {
+ if (history.state) {
+ response.historyState = history.state;
+ }
+ history.state = {request, response};
+ window.history.pushState(history.state, history.title, history.uri);
+ }
this._callMiddlewareUpdated(currentRoutes, request, response);
if (resolve) {
resolve(request, response);
@@ -403,21 +418,24 @@ HTTP_METHODS.reduce((reqProto, method) => {
*
* // HTTP GET method
* httpGet('/route1');
+ *
+ * // HTTP GET method
* httpGet({uri: '/route1', data: {'p1': 'val1'});
* // uri invoked => /route1?p1=val1
*
- * // HTTP POST method
- * httpPost('/user');
- * ...
+ * // HTTP GET method with browser history management
+ * httpGet({uri: '/api/users', history: {state: {foo: "bar"}, title: 'users page', uri: '/view/users'});
*
- * @param {String|Object} uri or object containing uri, http headers, data
+ * Samples above can be applied on other HTTP methods.
+ *
+ * @param {String|Object} uri or object containing uri, http headers, data, history
* @param {Function} success callback
* @param {Function} failure callback
* @public
*/
const httpMethodName = 'http'+method.charAt(0).toUpperCase() + method.slice(1).toLowerCase();
reqProto[httpMethodName] = function(request, resolve, reject) {
- let {uri, headers, data} = request;
+ let {uri, headers, data, history} = request;
if (!uri) {
uri = request;
}
@@ -425,7 +443,8 @@ HTTP_METHODS.reduce((reqProto, method) => {
uri,
method,
headers,
- data
+ data,
+ history
}, resolve, reject);
};
diff --git a/test/application-test.js b/test/application-test.js
index 5de87f4..70c3838 100755
--- a/test/application-test.js
+++ b/test/application-test.js
@@ -1,6 +1,7 @@
/*eslint-env mocha*/
import chai, {assert} from 'chai';
import sinon from 'sinon';
+//import jsdom from 'jsdom';
import frontexpress from '../lib/frontexpress';
import Requester from '../lib/requester';
@@ -25,18 +26,108 @@ describe('Application', () => {
});
});
- describe('listen method', () => {
- let eventFn = {};
+ // // JSDOM cannot manage pushState/onpopstate/window.location
+ // see https://github.com/tmpvar/jsdom/issues/1565
+ //
+ // describe('listen method with JSDOM', () => {
+ // before(() => {
+ // // Init DOM with a fake document
+ // //