Маршрутизация
Эта статья содержит общее описание механизма маршрутизации в Wasaby Framework. Из статьи вы получите ответы на такие вопросы, как:
- какие контролы обеспечивают инфраструктуру маршрутизации;
- что такое маска подстроки в URL и как с ней работать;
- как программно переходить по ссылкам;
- как настроить красивые адреса (ЧПУ или человекопонятные URL);
- как настроить сопоставление маршрутов;
Настройка маршрутизации для конкретных приложений СБИС, например online.sbis.ru, может иметь свои особенности. Поэтому перед началом работы рекомендуется обратить внимание на связанные с темой статьи:
- Использование маршрутизации Wasaby
- SEO controls
- Создание веб-страниц в приложении online.sbis.ru
- Правила составления «хороших» URL в приложении online.sbis.ru
Термины и определения
Маршрутизация
Процесс определения маршрута внутри приложения в зависимости от запроса. В процессе маршрутизации разработчик определяет шаблон URL и связывает его со своим кодом. Если возникает необходимость изменения конкретного URL, то следует просто поменять шаблон, тогда код по-прежнему будет работать отлично, и не понадобится менять какую-либо логику.
Механизм маршрутизации решает задачи:
- построения на сервере (рендеринга);
- SPA (позволяет переходить на клиенте со страницы на страницу без запроса на сервер).
SPA (Single Page Application)
Это web-приложение, компоненты которого загружаются единожды на одной странице, а контент подгружается по необходимости.
Маршрутизация в SPA-системах используется для загрузки определенных частей веб-приложения.
Корневой контрол
По URL-адресу возможно вычислить корневой контрол, который построит HTML-верстку.
В качестве примера рассмотрим:
https://online.sbis.ru/Tasks/FromMe
Чтобы вычислить корневой контрол, необходимо:
- Выбрать первое слово в пути, определяющее раздел, в котором находится корневой компонент;
- Через символ "/" добавить
Index.js
. - В итоге получим корневой контрол —
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-адресе.
Например, маска /doc/:guid для
online.sbis.ru/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
):
URL | Параметр, определенный маской |
---|---|
/paramName/valueOne | valueOne |
/paramName/value/Two | value |
/paramName/value?num=three | value |
/paramName/value#Four | value |
URL | Параметр, определенный маской |
---|---|
/page?paramName=valueOne | valueOne |
/page?paramName=value&two=true | value |
/page?paramName=value#three | value |
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() покажет всплывающее окно с соответствующим идентификатором.
Программный переход
Для данного механизма, также, существует возможность не использовать 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 обеспечивает работу с масками.
Важная особенность в использовании метода Controller.navigate. На вход ожидается объект с полями state и href. Если не передавать поле href, то оно будет автоматически вычислено из файла маршрутов router.json. href это то, что будет показано в адресной строке браузера.
Программный переход в браузере
Если есть необходимость осуществить программный переход в прикладном контроле, то необходимо убедиться, что был зарегистрирован хотя бы один "маршрут". При использовании контрола Router/router:Route
это происходит автоматически в хуке жизненного цикла _beforeMount(). Поэтому вызовы методов Controller.navigate/Controller.replaceState необходимо делать после _beforeMount()
, например в хуке жизненного цикла _afterMount(). Это связано с тем, что при вызове _beforeMount()
прикладного контрола еще не будет вызван _beforeMount()
контрола Router/router:Route
, т.е. не будет зарегистрирован "маршрут".
Пример:
// MyView.ts
import * as Control from 'Core/Control';
import { Controller } from 'Router/router';
export default class extends Control {
_beforeMount(cfg: any): void {
// ...
}
_afterMount(): void {
// вызов Controller.navigate/Controller.replaceState
}
// ...
}
<!-- MyView.wml -->
<Router.router:Route mask="/mask/:maskId">
<ws:content>
<!-- ... -->
</ws:content>
</Router.router:Route>
Программный переход на сервисе представления
При необходимости выполнить переход на новый URL-адрес на сервисе представления нужно пользоваться методом:
process.domain.res.redirect('/new/path');
Это позволит выполнить переход на новый URL-адрес без лишней загрузки страницы.
Пример. Задача определения города посетителя сайта. По умолчанию нам не известен город. Можно на сервисе представления вычислить город. Выполнить переход на новый URL-адрес типа /city/Yaroslavl
.
// MyView.ts
import * as Control from 'Core/Control';
import { constants } from 'Env/Env';
export default class extends Control {
_beforeMount(cfg: any): void {
if (constants.isServerSide) {
data = MaskResolver.calculateUrlParams('city/:city');
if (!data?.city && process?.domain?.res) {
process.domain.res.redirect('/city/Yaroslavl');
return;
}
}
}
// ...
}
<!-- MyView.wml -->
<Router.router:Route mask="/city/:city">
<ws:content>
Ваш город: {{ content.city }}
</ws:content>
</Router.router:Route>
Настройка сопоставления маршрутов
Механизм сопоставления маршрутов позволяет изменить 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 такое согласование должно быть с Новиковым Д.В., и за добавление адреса без согласования будут выписываться ошибки.
Более подробно с настройкой перенаправлений в router.json
можно ознакомиться в отдельной статье.
Настройка "красивых" ссылок в адресной строке
Для того чтобы привести ссылку в адресной строке к красивому виду, используется параметр 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
.
маска в href
В параметре href, также, можно использовать маску, соблюдая все правила использования масок.
Запуск демо
Чтобы запустить демонстрационный сервер, скачайте исходный код и выполните следующие команды:
npm install
npm run build
npm start
Перейдите по адресу http://localhost:777/ в своем браузере, чтобы увидеть демонстрационную страницу.