diff --git a/doc/ns.history.md b/doc/ns.history.md
new file mode 100644
index 00000000..1119261c
--- /dev/null
+++ b/doc/ns.history.md
@@ -0,0 +1,33 @@
+# ns.History
+
+В noscript для смены URL в адресной строке используется HTML5 History API, который не поддерживается [в IE раньше 10](http://caniuse.com/#feat=history).
+
+## Polyfill для IE
+
+В качестве полифилла можно использовать [devote/HTML5-History-API](https://github.com/devote/HTML5-History-API). Скрипт предоставляет стандартизированное API и будет использовать смену хеш-фрагмента URL для навигации.
+
+ /notes/141 -> /#/notes/141
+
+Кроме подключения самого скрипта на страницу нужно проделать небольшую работу:
+
+1. Организовать редирект до старта приложения:
+
+```js
+// Тут может произойти смена URL и перезагрузка, поэтому какие-нибудь
+// модели до редиректа запрашивать бессмысленно.
+window.history.redirect();
+
+ns.init();
+```
+
+2. Переопределить вычисление текущего URL приложения:
+
+```js
+var history = window.history;
+
+if (history.emulate) {
+ ns.page.getCurrentUrl = function() {
+ return history.location.pathname + history.location.search;
+ };
+}
+```
diff --git a/src/ns.action.js b/src/ns.action.js
index 9a9506c2..869a0916 100644
--- a/src/ns.action.js
+++ b/src/ns.action.js
@@ -198,8 +198,8 @@ ns.action._process = function(e) {
return true;
}
- // если host ссылки не равен нашему хосту
- if (target.host != window.location.host) {
+ // если hostname ссылки не равен нашему хосту
+ if (target.hostname != window.location.hostname) {
return true;
}
diff --git a/src/ns.history.js b/src/ns.history.js
index 1ac3c5a1..0a3d2b39 100644
--- a/src/ns.history.js
+++ b/src/ns.history.js
@@ -1,104 +1,44 @@
+(function() {
+
/**
* Объект для работы с историей и адресной строкой браузера.
- *
- * Методы:
- *
- * `ns.history.pushState(url)`
- * Меняет URL в адресной строке, запоминая его в истории браузера.
- * `ns.history.replaceState(url)`
- * Меняет URL в адресной строке, заменяя текущую запись в истории браузера.
- * `ns.history.adapt()`
- * Адаптирует хешовые URL для браузеров, поддерживающих HTML5 History API,
- * выполняя трансформации типа `/#/message/123/` в `/message/123/`.
- * `ns.history.legacy`
- * {Boolean}
- * Флаг использования HTML5 History API или отката на `hashchange`.
+ * @namespace
*/
-ns.history = (function(window, ns) {
-
- var history = window.history;
- var loc = window.location;
-
- var legacy = !(history.pushState && history.replaceState);
-
- // API для браузеров с поддержкой HTML5 History API.
- if (!legacy) {
- return {
- pushState: function(url, title) {
- history.pushState(null, title || ns.page.title(url), url);
- },
- replaceState: function(url, title) {
- history.replaceState(null, title || ns.page.title(url), url);
- },
-
- // Редирект с хешового урла на его полноценный аналог.
- adapt: function() {
- var hash = loc.hash.substr(1);
-
- if (hash.length) {
-
- var hashRoute = ns.router(hash);
- if (hashRoute.page !== ns.R.NOT_FOUND && hashRoute.page !== ns.R.REDIRECT) {
-
- // TODO: При добавлении способа задания корневого урла заменить
- // слеш на нужное свойство/метод.
- if (loc.pathname === '/' || ns.router(loc.pathname).page === ns.R.NOT_FOUND) {
- // Если в хеше уже есть какие-то query параметры,
- // текущие надо к ним прибавить.
- var search = hash.indexOf('?') !== -1 ? loc.search.replace(/^\?/, '&') : loc.search;
- this.replaceState(hash + search);
- }
- }
- }
+ns.history = {};
- window.addEventListener('popstate', function(e) {
- // прибиваем событие, чтобы не дергалась адресная строка
- e.preventDefault();
- e.stopPropagation();
+ns.history.init = function() {
+ $(window).on('popstate', function(e) {
+ // прибиваем событие, чтобы не дергалась адресная строка
+ e.preventDefault();
+ e.stopPropagation();
- ns.page.go('', true);
- }, false);
+ ns.history.onpopstate(e);
+ });
+};
- // Здесь `ns.page.go` можно не вызывать, потому что после `ns.init`
- // `ns.page.go` все равно вызывается.
- },
- legacy: false
- };
-
- // Откат на `hashchange`.
- } else {
- // Редирект с текущего полноценного урла на его хешовый аналог.
- //
- // TODO: При добавлении способа задания корневого урла заменить
- // слеш на нужное свойство/метод.
- if ((loc.pathname + loc.search) !== '/') {
- return loc.replace('/#' + loc.pathname + loc.search);
- }
-
- // Заставляет `hashchange` игнорировать смену URL через вызов legacy
- // `pushState` или `replaceState`.
- var ignore = false;
+ns.history.pushState = function(url, title) {
+ if (isFunction(window.history.pushState)) {
+ window.history.pushState(null, title || ns.page.title(url), url);
+ }
+};
- window.addEventListener('hashchange', function() {
- if (!ignore) {
- ns.page.go();
- }
+ns.history.replaceState = function(url, title) {
+ if (isFunction(window.history.replaceState)) {
+ window.history.replaceState(null, title || ns.page.title(url), url);
+ }
+};
- ignore = false;
- }, false);
+ns.history.onpopstate = function() {
+ ns.page.go('', true);
+};
- return {
- pushState: function(url) {
- ignore = true;
- loc.hash = url;
- },
- replaceState: function(url) {
- ignore = true;
- loc.replace(loc.pathname + loc.search + '#' + url);
- },
- adapt: no.nop,
- legacy: true
- };
- }
+/**
+ * Проверяет, является ли переданный объект функцией.
+ * @param {Function} fn
+ * @return {Boolean}
+ */
+function isFunction(fn) {
+ return 'function' == typeof fn;
+}
-}(window, ns));
+})();
diff --git a/src/ns.js b/src/ns.js
index 0e57b7be..1ff37f6e 100644
--- a/src/ns.js
+++ b/src/ns.js
@@ -112,9 +112,8 @@ ns.tmpl = function(json, mode, module) {
ns.init = function() {
ns.action.init();
ns.router.init();
+ ns.history.init();
ns.initMainView();
-
- ns.history.adapt();
};
/**
diff --git a/src/ns.page.js b/src/ns.page.js
index 9793bd49..64baf92d 100644
--- a/src/ns.page.js
+++ b/src/ns.page.js
@@ -18,7 +18,7 @@ ns.page._lastUrl = '';
/**
* Осуществляем переход по ссылке.
- * @param {String} [url=location.pathname + location.search]
+ * @param {String} [url=ns.page.getCurrentUrl()]
* @param {Boolean} [preventAddingToHistory=false] Не добавлять урл в историю браузера.
* @return {no.Promise}
*/
@@ -29,9 +29,7 @@ ns.page.go = function(url, preventAddingToHistory) {
return no.Promise.rejected('transaction');
}
- var loc = window.location;
-
- url = url || (ns.history.legacy ? loc.hash.substr(1) : (loc.pathname + loc.search));
+ url = url || ns.page.getCurrentUrl();
// возможность заблокировать переход
if (!ns.page.block.check(url)) {
@@ -165,6 +163,13 @@ ns.page.getDefaultUrl = function() {
return ns.router.url('/');
};
+/**
+ * Calculates current application url, fed as default value for `ns.page.go`.
+ */
+ns.page.getCurrentUrl = function() {
+ return window.location.pathname + window.location.search;
+};
+
/**
* Object to work with application history.
* @namespace
diff --git a/test/index.html b/test/index.html
index a107ce5d..45b2194b 100644
--- a/test/index.html
+++ b/test/index.html
@@ -39,17 +39,17 @@
+
-
-
+
+
-
@@ -69,7 +69,6 @@
-
diff --git a/test/spec/ns.history.js b/test/spec/ns.history.js
deleted file mode 100644
index 2a96278f..00000000
--- a/test/spec/ns.history.js
+++ /dev/null
@@ -1,143 +0,0 @@
-describe('ns.history', function() {
-
- // PhantomJS не умеет работать с URL
- if (/PhantomJS\//.test(navigator.userAgent)) {
- return;
- }
-
- var original = window.location.pathname + window.location.search;
-
- afterEach(function() {
- window.history.replaceState(null, 'test', original);
- });
-
- describe('API', function() {
-
- it('ns.history should be defined', function() {
- expect(ns.history).to.be.an('object');
- });
-
- it('ns.history.pushState should be defined', function() {
- expect(ns.history.pushState).to.be.a('function');
- });
-
- it('ns.history.replaceState should be defined', function() {
- expect(ns.history.replaceState).to.be.a('function');
- });
-
- it('ns.history.adapt should be defined', function() {
- expect(ns.history.adapt).to.be.a('function');
- });
-
- it('ns.history.legacy should have a specific value', function() {
- expect(ns.history.legacy).to.be.a('boolean');
- });
-
- });
-
- describe('behavior in modern browsers', function() {
-
- var url = '/some/address/with%20spaces/';
-
- it('ns.history.legacy should be false', function() {
- expect(ns.history.legacy).to.be(false);
- });
-
- it(!!window['phantom'] + ' 121212ns.history.pushState should change the address bar URL to the one passed in', function() {
- ns.history.pushState(url);
-
- expect(window.location.pathname + window.location.search).to.be(url);
- });
-
- it('ns.history.pushState should not trigger popstate event', function(done) {
- var triggered = false;
- var onpopstate = function() { triggered = true; };
-
- window.addEventListener('popstate', onpopstate, false);
-
- ns.history.pushState(url);
-
- setTimeout(function() {
- expect(triggered).to.be(false);
- window.removeEventListener('popstate', onpopstate);
- done();
- }, 0);
- });
-
- });
-
- describe('adapting hashed URLs to their original versions', function() {
-
- beforeEach(function() {
- ns.router.routes = {
- route: {
- '/message/{message-id:int}': 'layout',
- '/photo/{photo-id:int}': 'layout',
- '/': 'layout'
- }
- };
- ns.router.init();
-
- // заменяем ns.page.go на пустую функцию, нам там нечего проверять
- sinon.stub(ns.page, 'go', no.nop);
- });
-
- afterEach(function() {
- ns.page.go.restore();
- });
-
- it('should happen when the hash matches any of the defined routes', function(done) {
-
- window.history.pushState(null, 'test', '/#/message/123/');
-
- ns.history.adapt();
-
- setTimeout(function() {
- expect(window.location.pathname + window.location.search).to.be('/message/123/');
- done();
- }, 0);
-
- });
-
- it('should keep the query params', function(done) {
-
- window.history.pushState(null, 'test', '/?debug=true&append=%25a%25#/message/123/?param=1');
-
- ns.history.adapt();
-
- setTimeout(function() {
- expect(window.location.pathname + window.location.search).to.be('/message/123/?param=1&debug=true&append=%25a%25');
- done();
- }, 0);
-
- });
-
- it('should not happen when the hash is a random non-route string', function(done) {
-
- window.history.pushState(null, 'test', '/?debug=true#top');
-
- ns.history.adapt();
-
- setTimeout(function() {
- expect(window.location.pathname + window.location.search + window.location.hash).to.be('/?debug=true#top');
- done();
- }, 0);
-
- });
-
- it('should not happen when both pathname and hash match routes', function(done) {
-
- window.history.pushState(null, 'test', '/message/123/#/photo/321/');
-
- ns.history.adapt();
-
- setTimeout(function() {
- expect(window.location.pathname + window.location.search).to.be('/message/123/');
- done();
- }, 0);
-
- });
-
- });
-
-});