Цель
Для чего понадобилось создавать этот роутер:
- понадобилось API для переходов между страницами JavaScript-приложения
- переходы должны сохраняться в истории браузера. Т.е. пользователь может использовать стандартные кнопки навигации
Страницы в JavaScript-приложении довольно условные, потому что URL не меняется, а изменяется только его хэш. Т.е. мы переходим со страницы http://localhost#product/list на страницу http://localhost#product/details/1234 при клике на продукт с Id равной 1234.
API для работы с роутером
Регистрация роутера
1: ApplicationRouter.initRouter();
Регистрация путей
1: router.registerRouter('product/list', function() {
2: // действия, которые можно сделать при переходе на список продуктов
3: }, 'Список продуктов');
4:
5: router.registerRouter('product/details/{id}', function(id) {
6: // id - выбирается из конкретного URL и передается в функцию
7: }, 'Детали продукта');
Пути можно регистрировать по конкретной строке и по маске с Id. Ограничением является присутствие в маске только {id}, причем в конце URL. В любом случае, этих двух сценариев использования нам полностью хватает для реализации проекта. При желании роутер можно расширить, чтобы он понимал любое количество заданных масок и передавал их в функцию, которая вызывается при переходе по заданному пути.
Редирект
1: Router.redirect('product/list');
2:
3: Router.redirect('product/details/1234');
Реализация
В основе реализации лежит компонент Ext.History. Мы наследуем наш роутер от него, чтобы использовать перехват события change. Живой пример обработки события компонентом Ext.History можно посмотреть на сайте ExtJS в примерах.
Сам код:
1: /**
2: * Handle URL navigation functionality
3: *
4: * @class ApplicationRouter
5: * @extends Ext.History
6: * @singletone
7: */
8: ApplicationRouter = (function() {
9: var redirectedTo = '';
10: var routerList = new Array();
11:
12: var setLink = function(link, title) {
13: if (!Ext.isEmpty(title))
14: {
15: document.title = title.replace('→', '»');
16: }
17:
18: top.location.hash = "#" + link;
19: };
20:
21: var saveUrl = function() {
22: redirectedTo = document.location.hash.substring(1);
23: };
24:
25: /**
26: * @param {String}
27: * path
28: */
29: var getPageLink = function(path) {
30: var result = "";
31: if (path.indexOf('?') != -1) {
32: result = path.substr(0, path.indexOf('?'));
33: } else {
34: result = path;
35: }
36: return result;
37: };
38:
39: /**
40: * @param {String}
41: * route Путь, который содержит ID
42: * @param {String}
43: * pathMask Маска, по которой выбирается ID
44: * @return {Int} Возвращает найденную ID или 0
45: */
46: var getIdFromRoute = function(route, pathMask) {
47: if (pathMask.indexOf('{id}') == -1 || route == null)
48: return 0;
49:
50: var pathRegex = pathMask.replace('{id}', '(\\d*)');
51: var match = route.match(pathRegex);
52:
53: if (match == null || match.length < 2 || match[1] == null)
54: return 0;
55:
56: return parseInt(match[1]);
57: };
58:
59: /**
60: * @param {String}
61: * route Путь, который проверяем
62: * @param {String}
63: * pathMask Маска, которая проверяет путь
64: * @return {Boolean} True, если путь подходит маске; иначе, false
65: */
66: var isRouteAcceptPath = function(url, pathMask) {
67: var page = getPageLink(url);
68: if (page == pathMask)
69: return true;
70: return getIdFromRoute(page, pathMask) > 0;
71: }
72:
73: return {
74: /**
75: * Call init before user router
76: *
77: * @public
78: */
79: initRouter : function(onReady, scope) {
80: ApplicationRouter.init();
81:
82: var me = this;
83: this.on('change', function(token) {
84: if (!Ext.isEmpty(token) && token != redirectedTo) {
85: saveUrl();
86: me.redirect(token);
87: }
88: });
89: },
90:
91: /**
92: * Register router for specifiered path mask.
93: *
94: *
95: * @public
96: * @param {String}
97: * pathMask - Mask for redirec path
98: * @param {Function}
99: * responseAction - If user redirect to page for pathMask, router should execute specifiered action
100: * @param {String}
101: * pageTitle - Title for page
102: */
103: registerRouter : function(pathMask, responseAction, pageTitle) {
104: routerList.push({
105: pathMask : pathMask.toLowerCase(),
106: title : pageTitle,
107: responseAction : responseAction
108: });
109: },
110:
111: /**
112: * Redirect application to path
113: *
114: * @public
115: * @param {String}
116: * path - Path to redirect
117: * @example
118: * ApplicationRouter.redirect("my-url");
119: */
120: redirect : function(url) {
121: var router = null;
122: for (var i = 0; i < routerList.length; i++) {
123: router = routerList[i];
124: if (isRouteAcceptPath(url, router.pathMask)) {
125: this.doRedirect(url, router);
126: return;
127: }
128: }
129: router = routerList[0];
130: this.doRedirect(router.pathMask, router);
131: },
132:
133: /**
134: * @private
135: */
136: doRedirect : function(path, router) {
137: redirectedTo = path;
138: setLink(path, router.title);
139:
140: var id = getIdFromRoute(path, router.pathMask);
141: router.responseAction(id > 0 ? id : null);
142:
143: this.fireEvent('redirect', path, router.title);
144: }
145: };
146: })();
147:
148: Ext.apply(ApplicationRouter, Ext.History);
Заголовок неточный. Может сложиться впечатление что "роутер" - аппаратное сетевое средство.
ОтветитьУдалить@Сергей Звездин
ОтветитьУдалитьВсе, кто дочитали до этого комментария, знайте, что речь идет о роутере для URL в веб-страницах ;)
Уф, а я тоже подумал, что какой-то гик сэмулировал Cisco на JavaScript-е :)))
ОтветитьУдалитьКстати, а почему внутри класса везде var myVar вместо this.myVar ? По религиозным соображением, или просто чтоб помедленнее работало?
"почему внутри класса везде var myVar вместо this.myVar"
ОтветитьУдалитьДаже не знаю как ответить =). Почитай про области видимости переменных в JavaScript и попробуй вместо var написать this. На самом деле вопрос из серии "я познаю мир".
А я в свою очередь рекомендую тебе почитать о разнице в накладных расходах между созданием свойств и методов в прототипе и в конструкторе. А вопрос был из серии "надо ли писать на джаваскрипте как на шарпе".
ОтветитьУдалитьХа-ха-ха
ОтветитьУдалитьЧувак, не позорься.
obj = (function(){
var internalVar;
return {
publicVar: 'i am public',
publicFunc: function(){
this.publicVar;
internalVar;
}
}
})();
obj.publicFunc();
obj.publicVar();
Вот это стандартный прием в работе с JavaScript. Так что расслабься.
Да, да, да... Александр, приношу свои извинения! Мне следовало внимательнее задачу читать и код смотреть. Посыпаю голову пеплом, что не увидел синглтон в постановке задачи и замыкание в коде. Действительно, опозорился.
ОтветитьУдалитьОй, пристыдил тебя, мне аж неловко стало =)
ОтветитьУдалитьJavaScript вообще удивительный язык. Когда я начал им плотно пользоваться год назад, то плевался во все стороны. Как сказал его создатель: "JavaScript не имеет отношения к Java и скриптовым языком не является", так что с названием ему изначально не свезло =) А как лодку назовешь, так она и поплывет ;)
Соглашусь, что хаки, типа приведенного синглтона распознаются на глаз уж очень не просто.
Ай, да так мне и надо! Глянул код с середины по диагонали. Что имеется return внутренней функции - не заметил. Хоть бы увидел синглтон в шапке, он бы точно на мысль о замыкании навел - так нет же, полез холиварить, что свойства объекта экономнее юзать... Ээх!
ОтветитьУдалить