Модуль basis.router
позволяет отслеживать изменения location
и задавать реакцию на эти изменения.
В настоящий момент модуль работает только со значением хеша адреса (location.hash
). Поддержка History API
планируется в будущих версиях.
Модуль отслеживает изменения location.hash
, используя событие hashchanged
, либо каждые 50ms
, используя таймер, если событие не поддерживается браузером. При этом запоминается предущее известное значение для location.hash
, и если между проверками произойдет несколько изменений, то "реакции" будут применены только к последнему значению. Если несколько изменений в итоге приводят к тому же значению, что было при предыдущей проверке, "реакций" применено не будет.
По умолчанию состояние location.hash
не проверяется, и изменения не отслеживаются. Для того чтобы модуль начал отслеживать изменения, необходимо вызвать функцию basis.router.start()
. Последующие вызовы этой функции не имеют эффекта, если изменения уже отслеживаются. Чтобы модуль прекратил отслеживать изменения, необходимо вызвать функцию basis.router.stop()
. Когда изменения начинают отслеживаться, срабатывают уже добавленные "реакции", которые удовлетворяют текущему значению location.hash
.
Реакция – это набор функций, которые задаются для определенного значения location.hash
и выполняются при определенных условиях (событиях).
Чтобы добавить "реакцию" для некоторого значения location.hash
, используется функция basis.router.add
, а чтобы удалить – функция basis.router.remove
. При этом функции basis.router.remove
должны передаваться те же значения, что и для функции basis.router.add
. Добавлять и удалять "реакции" можно в любой момент, независимо от того, отслеживаются ли изменения location.hash
или нет. При этом если отслеживаются изменения и добавляемая "реакция" удовлетворяет условиям, то она тут же срабатывает.
Для функции basis.router.add
(как и для basis.router.remove
) задаются три параметра:
path
– строка или регулярное выражение, описывающее значениеlocation.hash
;callbacks
– набор функций, выполняемых при наступлении определенных событий;context
– опциональный параметр, задающий контекст для функций (значениеthis
).
В качестве значения для path
может быть задана строка или регулярное выражение.
Если задана строка, то она трансформируется в регулярное выражение по следующим правилам:
- подстроки вида
:\w+
(например,:foo
) преобразуются в([^/]+)
, что значит один и более символ, не являющийся слешем (/
); - подстроки вида
*name
(например,*rest
) преобразуются в(.*?)
, то есть произвольное количество символов; - подстроки, обрамленные в скобки, являются опциональными (преобразование
(expr)
→(?:expr)?
);- поддерживается вложенность, например,
foo(/bar(/baz))
; - внутри скобок можно использовать
|
для описания альтернатив, например,foo(/bar|/baz)
; - если у открывающей скобки нет пары, то она считается обычным символом;
- поддерживается вложенность, например,
- обратный слеш (
\
) перед символом отменяет его специальное значение, при этом обратный слеш не попадает в результат; например,\:foo
не будет преобразовано (будет /^:foo$/);- стоит помнить, что в строках обратный слеш тоже экранирует символы, поэтому вместо одного
\
нужно указывать пару, например,'\\:foo'
;
- стоит помнить, что в строках обратный слеш тоже экранирует символы, поэтому вместо одного
- символы, которые имеют специальное значение в регулярных выражениях, экранируются;
- к полученному результату перед преобразованием в регулярное выражение в начало добавляется
^
, а в конец$
– таким образом, регулярное значение применяется ко всему значению (ко всей строке).
Преобразование подстрок вида
:foo
,*bar
и(baz)
аналогично поведениюBackbone.Router
, но вBackbone
не поддерживается вложенность скобок, вертикальная черта в скобках и экранирование символов.
Все значения в скобках в регулярном выражении становятся значениями параметров для match
-функций "реакции". Другими словами, делается примерно следующее:
fn.apply(context, location.hash.match(/^foo\/(\d+)(\/bar|baz)$/).slice(1));
Рассмотрим несколько примеров. Допустим, для path
задана строка 'books/:kind/page:page'
. Такое значение сработает для #books/sci-fi/page5
, а в match
-функции будут переданы значения 'sci-fi'
и '5'
.
Путь 'foo/*rest'
сработает для #foo/bar/baz
, передав в функцию значение 'bar/baz'
.
А путь 'docs/:section(/:subsection)'
сработает для #docs/faq
и для #docs/faq/installing
, передав в функцию 'faq'
в первом случае, а во втором – значения 'faq'
и 'installing'
.
Слеши в конце пути считаются частью пути, поэтому 'docs'
и 'docs/'
будут считаться разными путями. Чтобы указать реакцию для обоих случаев, необходимо использовать скобки, например 'docs(/)'
.
В простых случаях описанных правил оказывается досточно. Но в сложных случаях или когда возможностей оказывается недостаточно, оказывается проще описать непосредственно регулярное выражение.
Для одного пути можно задавать произвольное количество "реакций". Например, разные модули могут добавлять свои реакции на один и тот же путь.
Для callbacks
задается объект, где ключ – это название события, а значение – функция, которая должна быть выполнена при наступлении этого события. Доступны следующие события:
enter
- значениеlocation.hash
начало удовлетворятьpath
(похоже наmouseenter
);match
– срабатывает при каждом измененииlocation.hash
, если значение удовлетворяетpath
;leave
- значениеlocation.hash
перестало удовлетворятьpath
(похоже наmouseleave
).
Все функции являются необязательными. match
-функциям передаются параметры (подстроки из location.hash
), если в path
есть значения в скобках. Для enter
- и leave
-функций параметры не передаются.
При изменении location.hash
проверяется, какие "реакции" удовлетворяют новому значению. После чего выполняются функции в следующем порядке:
- для "реакций", которые матчились при предыдущей проверке, но перестали матчиться, выполняются их
leave
-функции; - для "реакций", которые не матчились при предыдущей проверке, но начали матчится при текущей, выполняются их
enter
-функции; - для всех реакций, которые удовлетворяют текущему значению
location.hash
, выполняются ихmatch
-функции.
// location.hash = 'foo/123'
var router = basis.require('basis.router');
router.start();
router.add('foo/:id', {
enter: function(){ console.log('foo enter'); },
match: function(id){ console.log('foo match', id); },
leave: function(){ console.log('foo leave'); }
});
// > 'foo enter'
// > 'foo match' '123'
router.add('bar/:baz(/:etc(/))', {
enter: function(){ console.log('bar enter'); },
match: function(a, b){ console.log('bar match', a, b); },
leave: function(){ console.log('bar leave'); }
});
router.navigate('bar/456');
// > 'foo leave'
// > 'bar enter'
// > 'bar match' '456' undefined
router.navigate('bar/456/example');
// > 'bar match' '456' 'example'
router.navigate('bar/456/example/');
// > 'bar match' '456' 'example'
В качестве значения для callbacks
может быть задана функция, а не объект, тогда считается, что задана match
-функция.
var router = basis.require('basis.router');
router.add('foo', function(){
// ..
});
// эквивалентно
router.add('foo', {
match: function(){
// ..
}
});
Функция меняет текущее значение location.hash
на переданное (value
). При этом если передан параметр replace
и его значение приводится к true
, то предыдущее значение location.hash
не сохраняется в истории (используется метод location.replace()
).
Переданное значение никак не трансформируется и задается location.hash
, как есть. При этом новое значение немедленно проверяется, и для него применяются "реакции".
var router = basis.require('basis.router');
router.navigate('foo');
console.log(location.hash);
// > '#foo'
router.navigate('bar');
console.log(location.hash);
// > '#bar'
history.back();
console.log(location.hash);
// > '#foo'
router.navigate('one'); // добавит #one в историю
router.navigate('two', true); // перезапишет #one на #two
console.log(location.hash);
// > '#two'
history.back();
console.log(location.hash);
// > '#foo'
Функция позволяет немедленно перепроверить значение location.hash
, не дожидаясь события или срабатывания таймера.
Свойство debug
с заданным значением true
позволяет получать отладочную информацию об изменении location.hash
и примененных реакциях.
// location.hash == '#foo'
var router = basis.require('basis.router');
router.debug = true;
router.start();
// > basis.router started
// > basis.router: hash changed to "foo"
// <no matches>
router.add('foo', function(){});
// > basis.router: add handler for route `asd3`
// Object {type: "match", path: "foo", cb: Object, route: Object, args: Array[0]}
До версии
1.4
изменять значение флага необходимо иначе: либоbasis.router.debug = value
, либоbasis.namespace('basis.router').debug = value
.