Маршрутизация

Эта статья содержит общее описание механизма маршрутизации в Wasaby Framework. Из статьи вы получите ответы на такие вопросы, как:

Настройка маршрутизации для конкретных приложений СБИС, например online.sbis.ru, может иметь свои особенности. Поэтому перед началом работы рекомендуется обратить внимание на связанные с темой статьи:

Термины и определения

Маршрутизация

Процесс определения маршрута внутри приложения в зависимости от запроса. В процессе маршрутизации разработчик определяет шаблон URL и связывает его со своим кодом. Если возникает необходимость изменения конкретного URL, то следует просто поменять шаблон, тогда код по-прежнему будет работать отлично, и не понадобится менять какую-либо логику.

Механизм маршрутизации решает задачи:

  • построения на сервере (рендеринга);
  • SPA (позволяет переходить на клиенте со страницы на страницу без запроса на сервер).

SPA (Single Page Application)

Это web-приложение, компоненты которого загружаются единожды на одной странице, а контент подгружается по необходимости.

Маршрутизация в SPA-системах используется для загрузки определенных частей веб-приложения.

Корневой контрол

По URL возможно вычислить корневой контрол, который построит HTML-верстку.

В качестве примера рассмотрим:

https://online.sbis.ru/Tasks/FromMe

Чтобы вычислить корневой контрол, необходимо:

  1. Выбрать первое слово в пути, определяющее раздел, в котором находится корневой компонент;
  2. Через символ "/" добавить Index.js.
  3. В итоге получим корневой контрол — Tasks/Index.js, в шаблоне которого находится Controls/Application, который построит HTML-верстку страницы.

Параметр FromMe в данном примере показывает, какая вкладка будет отображаться. Он используется в процессе построения верстки.

Переключение корневого контрола

При переключении с одной ссылки на другую может переключиться и весь раздел.

Пример. В веб-приложении СБИС переключаемся из вкладки "Задачи" на вкладку "Контакты". На клиенте посредством VDOM-библиотеки будет вычислен другой корневой контрол, который полностью заменит собой старый и отстроит от него новую верстку.

/Tasks/FromMe -> /Contacts/

В итоге Tasks/Index.js поменяется на Contacts/Index.js.

Ключевые контролы

Инфраструктуру маршрутизации обеспечивают контролы:

  • Router/router:Reference — по параметрам вычисляет новую ссылку и, посредством опции, передает ее в собственный контент.
  • Router/router:Route — согласно своим настройкам из URL-адреса получает параметры и передает их в собственный контент.

Контролы изоморфны, т.е. используются как на сервере, так и на клиенте.

Маска и синтаксис её задания

Для работы с параметрами контролов используется маска — это строка с параметрами, синтаксически выделяемыми двоеточием, единственным образом определяющая подстроку в URL. Если маска начинается с символа "/", значит нам необходимо определить URL-параметр, который располагается в начале пути (абсолютная маска).

Например, маска /doc/:guid для

online.sbis.ru/doc/c065

вычислит guid = c065, а для

online.sbis.ru/tasks/doc/c065

не вычислит guid.

Если мы зададим маску без символа "/", она определит любой параметр, даже если он находится в середине пути.

Например, маска doc/:guid, для

online.sbis.ru/doc/c065

вычислит guid = c065. Для

online.sbis.ru/tasks/doc/c065

вычислит guid = c065;

Контрол Router/router:Reference

Router/router:Reference — по параметрам вычисляет новую ссылку и, посредством опции, передает ее в собственный контент. Для добавления параметров в URL-адрес используется опция state.

Пример. Опция state хранит маску с интересующим нас параметром. Опция href является контентной опцией внутри шаблона Router/router:Reference. В результате, к текущей ссылке добавится destination/ и название страны, которое определит Router/router:Reference.

<Router.router:Reference state="destination/:country" country="{{ country }}">
    <a href="{{ content.href }}">Go to {{ country }}</a>
</Router.router:Reference>

Контрол Router/router:Route

Router/router:Route — согласно своим настройкам из URL-адреса получает параметры и передает их в собственный контент. Для вычисления параметров используется опция mask. Значение этого параметра извлекается из URL-адреса при его изменении и передается внутри Router/router:Route с тем же именем.

Пример 1. Для online.sbis.ru/task/123/task/456 нельзя задать маску task/:taskId, которая из URL выберет 456. Это связано с ограничениями, которые мы задали для маски. При наличии в URL двух одинаковых параметров всегда будет выбираться только первый.

Чтобы выбирать 456, нужно в URL-адресе изменить имена параметров и сделать их уникальными. Например, вот так:

online.sbis.ru/task/123/task1/456

В этом случае можно выбрать 456 при помощи маски task1/:taskid.

Пример 2. В примере ниже, если URL содержит подстроку destination/Russia, пользователь увидит Selected destination: Russia. Если URL не соответствует маске destination/, то country будет неопределенным.

<Router.router:Route mask="destination/:country">
    <p>Selected destination: {{ content.country }}</p>
</Router.router:Route>

Router/router:Route поддерживает два типа масок: стандартный параметр (path) и параметр запроса (query):

Маска параметров в path — "paramName/:paramValue".
URLПараметр, определенный маской
/paramName/valueOnevalueOne
/paramName/value/Twovalue
/paramName/value?num=threevalue
/paramName/value#Fourvalue
Маска параметров в query — "paramName=:paramValue".
URLПараметр, определенный маской
/page?paramName=valueOnevalueOne
/page?paramName=value&two=truevalue
/page?paramName=value#threevalue

History

Синглтон Router/router:History позволяет определить состояние истории браузера, если это необходимо.

МетодОписание
getPrevState()Определить предыдущее состояние.
getNextState()Определить следующее состояние.
getCurrentState()Определить текущее состояние.

События маршрутизации

У Router/router:Route есть события enter и leave. Они позволяют значительно упростить управление открытием и закрытием панелей. Событие enter происходит, когда текущий URL-адрес соответствует маске этого маршрута, а leave — когда текущий URL-адрес больше не соответствует указанной маске.

Пример. Рассмотрим код, который приведён ниже. При переходе с URL-адреса /home на /page/search/My-query произойдёт событие enter, т.к. в URL появится параметр My-query. При переходе с /page/search/My-query на about, произойдёт событие leave, т.к. в URL параметр My-query пропадет.

<Router.router:Route mask="search/:query">...</Router.router:Route>

Использование событий для маршрутизации всплывающих окон

События можно использовать для выполнения пользовательских действий при изменении URL-адреса, например для открытия всплывающих окон.

Пример. Рассмотрим код, который приведён ниже.

<Router.router:Reference mask="alert/:popupInfo" popupInfo="{{ _productId }}">
    <a href="{{ content.href }}">Open popup</a>
</Router.router:Reference> 
<Router.router:Route mask="alert/:popupInfo" on:enter="showPopup()" on:leave="closePopup()"/>

При клике по крестику всплывающего окна из URL-адреса будет удалён параметр, который задан в маске. Далее произойдёт событие leave, которое закроет всплывающее окно. При этом все состояния контрола будут сохранены. Таким образом, можно легко управлять открытием и закрытием всплывающих окон.

При клике на контрол Router/router:Reference в URL-адрес будет добавлен параметр, который указан в маске. В примере этим параметром является идентификатор всплывающего окна. Произойдёт событие enter, по которому showPopup() покажет всплывающее окно с соответствующим идентификатором.

Рекурсивное открытие всплывающих окон

В некоторых случаях требуется из окна открыть точно такое же окно, но с другими параметрами.

Например, в веб-приложение СБИС такое можно встретить в разделе "Задачи", где необходимо из задачи открыть другую задачу, затем еще одну и т.д.

Чтобы это работало, всплывающие окна должны использовать разные маски URL, потому что они не должны открываться и закрываться одновременно. Однако у каждого окна задачи одинаковый шаблон, маски не будут уникальными, поэтому трудно определить к какой именно маске относится конкретный параметр.

Для решения этой задачи нужно создать опцию depth, которая сделает маску уникальной по "глубине". Например, вот так:

<!-- inside of the popup template -->
<Router.router:Route
    mask="alert/{{_options.depth + 1}}/:popupInfo"
    on:enter="openPopupRecursive()"
    on:leave="closePopupRecursive()" />
     
<Router.router:Reference
    mask="alert/{{_options.depth + 1}}/:popupInfo"
    popupInfo="{{ _productId }}">
     
    <a href="{{ content.href }}">Open a new popup</a>
</Router.router:Reference>

Таким образом, в первой задаче к маске добавится 0, во второй задаче отрисуются новые Router/router:Reference и Router/router:Route, а глубина уже будет равна 1. Так каждый раз в URL к маскам будут добавляться параметры "глубины" и, соответственно, будет открываться новая панель.

Программный переход

Для данного механизма, также, существует возможность не использовать Router/router:Reference, а переходить по ссылкам программно.

Чтобы осуществить программный переход, запросим его вручную:

var MaskResolver = require('Router/router').MaskResolver;
 
// Вычисляем новый адрес, в котором будет tab/:tab
var newUrl = MaskResolver.calculateHref('tab/:tab', { tab: tabName });
 
var Controller = require('Router/router').Controller;
 
// Переходим на новый адрес
Controller.navigate({ state: newUrl });

Библиотека MaskResolver обеспечивает работу с масками.

Настройка перенаправления ссылок (редирект)

Механизм перенаправления ссылок позволяет изменить URL с помощью json-файла.

Например, есть ссылка online.sbis.ru/doc/c065922e, в которой требуется произвести замену {"doc":"EDO3/Route/Dialog"}. Тогда получим путь online.sbis.ru/EDO3/Route/Dialog/c065922e.

Т.к. маска работает по замененному пути, она должна соответствовать формату Dialog/:guid. Корневой контрол будет вычислен, также, по замененному пути — EDO3/Index.js.

Подобные замены следует хранить в файле router.json:

{
    "/" : "OnlineSbisRu",
    "/Contractors/" : "Contractor",
    "/Tasks" : "OnlineSbisRu/Tasks",
    "doc" : "EDO3/Route/Dialog",
    "fastvdisk": "DOCVIEW3/fastvdisk"
}

Создать этот файл можно в любом каталоге интерфейсного модуля, поскольку билдер при сборке проекта соберет все router.json в один файл и положит его в корень проекта. Этот файл в последствии будет использоваться для механизма маршрутизации.

Добавление адресов в файл router.json должно быть согласовано с ответственным за приложение. Для online.sbis.ru такое согласование должно быть с Новиковым Д.В., и за добавление адреса без согласования будут выписываться ошибки.

Для того чтобы привести ссылку в адресной строке к красивому виду, используется параметр href.

<Router.router:Reference state="page/:pageType" pageType="register" href="/signup">
    <a href="{{ content.href }}">Sign up</a>
</Router.router:Reference>

В данном примере мы перейдем по ссылке page/register, маски отработают, и вычислится корневой компонент, также, по ссылке /page/register. Однако, благодаря параметру href, в адресной строке отобразится /signup.

Запуск демо

Чтобы запустить демонстрационный сервер, выполните следующие команды:

npm install
npm run build
node app

Перейдите по адресу https://github.com/saby/Router в своем браузере, чтобы увидеть демонстрационную страницу.