Содержание

basis.router

Модуль 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

В качестве значения для 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

Для 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'

checkUrl()

Функция позволяет немедленно перепроверить значение location.hash, не дожидаясь события или срабатывания таймера.

debug

Свойство 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.