Перейти к содержанию

Роутинг

В платформе существуют два основных типа роутинга: для карточек /card/:id и для рабочих мест /view/:id. Платформа позволяет переопределять существующий ройтинг и задавать новый.

Создание нового роута

Для примера создадим роут /custom_route, при переходе на который пользователь должен увидеть название роута в центре экрана.

Для начала нужно описать новый контроллер со стороны сервера:

namespace Tessa.Extensions.Server.Web.Services { [AllowAnonymous] public sealed class CustomRouteController : Controller { #region Constructors

public CustomRouteController(IClientViewProvider viewProvider) { this.viewProvider = NotNullOrThrow(viewProvider); }

#endregion

#region Fields

private readonly IClientViewProvider viewProvider;

#endregion

#region Controller Methods

[Route("custom_route"), SessionMethod] public Task<IActionResult> GetCustomRoute(CancellationToken cancellationToken = default) { // при переходе по маршруту просто отдаем базовую html страницу со всеми скриптами return this.viewProvider.GetIndexViewAsync(this, cancellationToken: cancellationToken); }

#endregion } }

Для возможности редиректа на окно логина нужно добавить следующий код:

namespace Tessa.Extensions.Server.Web.Services { public class CustomRoutePathParser : ClientPathParser { [WebRegistrator] public sealed class WebRegistrator : WebRegistratorBase { public override void RegisterServices() => this.Services.AddSingleton<IClientPathParser, CustomRoutePathParser>(); }

public CustomRoutePathParser(IWebPathParser webPathParser) : base(webPathParser) { this.RedirectableToLoginPrefixes.Add("/custom_route"); } } }

Note

Для простоты класс регистратора WebRegistrator добавлен внутрь класса CustomRoutePathParser, т.к. он регистрирует единственную зависимость. Его возможно указать и как обычный класс в пространстве имён (по аналогии с классами Registrator).

Со стороны клиента нужно создать новый тип вкладки, связанный с роутом:

export class CustomWorkspace extends DefaultWorkspaceModelBase { //#region static

// вкладка всегда будет в одном экземпляре, поэтому заранее создаем для неё id readonly WorkspaceId = Guid.newGuid();

//#endregion

//#region ctor

constructor() { super(CustomWorkspace.WorkspaceId); }

//#endregion

//#region props

override get workspaceName(): string { return 'Custom'; }

//#endregion

//#region methods

override getRoute(): RouteData { // путь, который устаналивается при переходе на вкладку return { path: '/custom_route' }; }

//#endregion }

Далее опишем компонент, который будет отображаться на экране при переходе по роуту:

const Container = styled.div` display: flex; justify-content: center; align-content: center; font-size: 80px; `;

export const CustomRouteComponent: RouteComponent = ({ route }) => { // из компонента можно получить доступ до объекта роута console.log(route);

return ( <div className="dashboard-wrapper"> <Container>Custom Route</Container> </div> ); };

Далее нужно создать объект с описанием нового роута, который свяжет вместе все ранее созданные элементы, и зарегистрировать его в платформе:

// customRoute.ts @injectable() export class CustomRoute extends Route { override get path(): string { return '/custom_route'; }

override get component(): RouteComponent { return CustomRouteComponent; }

override async transit(): Promise<void> { // если вкладка ранее уже была открыта, то просто переходим на неё. в противном случае создаем вкладку. const workspace = this.workspaceStorage.tryGetWorkspace(CustomWorkspace.WorkspaceId); if (!workspace) { this.workspaceStorage.addWorkspace(new CustomWorkspace()); }

await this.workspaceStorage.activateWorkspace(CustomWorkspace.WorkspaceId, false); } }

// registrator.ts export const SolutionRegistrator: ExtensionRegistrator = { async registerTypes(container) { container.bind(IRoute$).to(CustomRoute).inSingletonScope(); } };

Чтобы перейти на новый роут, можно:

  • добавить в адресную строку браузера https://%server_name%/custom_route;
  • из кода вызвать Application.instance.history.push('/custom_route').

Переопределение существующего маршурта

Для примера переопределим роут для карточек “Автомобиль”, чтобы была возможность открывать карточки этого типа по роуту /car/:car_name.

Для начала нужно описать новый контроллер со стороны сервера:

namespace Tessa.Extensions.Server.Web.Services { [Route("car"), AllowAnonymous] public sealed class CarController : Controller { #region Constructors

public CarController(IClientViewProvider viewProvider) { this.viewProvider = NotNullOrThrow(viewProvider); }

#endregion

#region Fields

private readonly IClientViewProvider viewProvider;

#endregion

#region Controller Methods

[HttpGet("{name}"), SessionMethod] public Task<IActionResult> GetCar([FromRoute] string name, CancellationToken cancellationToken = default) { // получаем ID карточки по названию автомобиля, указанного в карточке // в дальнейшем этот ID можно будет использовать при переходе var cardID = await this.GetCarCardIDByName(name); return this.viewProvider.GetIndexViewAsync( this, async () => { var storage = new Dictionary<string, object?>(); storage["CardID"] = cardID; return storage; }, cancellationToken); }

[HttpGet("api/car/{name}"), SessionMethod, Produces(MediaTypeNames.Text.Plain)] [ProducesResponseType(StatusCodes.Status200OK)] public async Task<string> GetCard( [FromRoute] string name, CancellationToken cancellationToken = default) { var cardID = await this.GetCarCardIDByName(name); return cardID.ToString(); }

private async Task<Guid> GetCarCardIDByName(string name) { // получаем нужную карточку. например через DbScope // ... }

#endregion } }

Для возможности редиректа на окно логина нужно добавить следующий код:

namespace Tessa.Extensions.Server.Web.Services { public class CarRoutePathParser : ClientPathParser { [WebRegistrator] public sealed class WebRegistrator : WebRegistratorBase { public override void RegisterServices() => this.Services.AddSingleton<IClientPathParser, CarRoutePathParser>(); }

public CarRoutePathParser(IWebPathParser webPathParser) : base(webPathParser) { this.RedirectableToLoginPrefixes.Add("/car/"); } } }

Note

Для простоты класс регистратора WebRegistrator добавлен внутрь класса CarRoutePathParser, т.к. он регистрирует единственную зависимость. Его возможно указать и как обычный класс в пространстве имён (по аналогии с классами Registrator).

Нужно создать объект с описанием нового роута для карточки и зарегистрировать его:

@injectable() export class CarRoute extends CardRoute { constructor(@inject(IApiClient$) private readonly _apiClient: IApiClient) { super(); }

override get path(): string { // через `:` можно указывать параметры в роута, которые будут доступны для получения через `.getParams()` return '/car/:name'; }

override async canTransit(opt: RouteTransitionOptions): Promise<boolean> { const { name } = opt.getParams<{ name: string }>(); return name != null; }

override async transit(opt: RouteTransitionOptions): Promise<void> { // достаем название автомобиля из параметров роута const { name } = opt.getParams<{ name: string }>();

// актуальный ID карточки может быть получен заранее и добавлен в стейт роута let cardId = opt.location.state?.cardId; // если в стейте пусто и это стартовый роут (был открыт через адресную строку браузера), то // пытаемся получить ID карточки через глобальные данные роута, которые были заданы на сервере в CarController.GetCar if (!cardId && opt.initial) { cardId = opt.getGlobalRouteData()?.['CardID']; } // если ID найти не удалось, то для открытия карточки получаем правильный ID с сервера if (!cardId) { cardId = await this._apiClient.get(`/car/api/card/${encodeURIComponent(name)}`).text(); }

// открываем карточку как обычно await openCard({ cardId, needDispatch: false }); } }

// registrator.ts export const SolutionRegistrator: ExtensionRegistrator = { async registerTypes(container) { container.bind(IRoute$).to(CarRoute).inSingletonScope(); } };

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

@extension({ name: 'CarRouteUIExtension' }) export class CarRouteUIExtension extends CardUIExtension { override initializing(context: ICardUIExtensionContext): void { const { card, uiContext } = context; const cardName = card.sections.tryGet('TEST_CarMainInfo')?.fields.tryGet('Name');

if (cardName && uiContext.cardEditor) { uiContext.cardEditor.routeOverride = () => ({ path: `/car/${encodeURIComponent(cardName)}`, state: { cardId: card.id // сразу сохраняем id карточки, чтобы не приходилось вызывать лишний раз сервер при переходах } }); } } }

// registrator.ts export const SolutionRegistrator: ExtensionRegistrator = { async registerExtensions(container) { container.registerExtension({ extension: CarRouteUIExtension, stage: ExtensionStage.AfterPlatform, when: whenCardTypeIdIs('d0006e40-a342-4797-8d77-6501c4b7c4ac') }); } };

Каждый раз при открытии/обновлении карточки дефолтный путь card/:id будет заменяться на car/:car_name.

Back to top