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

Дополнительно

Темы

Легкий клиент поддерживает файлы с настройками тем толстого клиента. Темы можно найти в папке themes в корневой папке. При необходимости в эту папку можно добавить кастомные темы. Они автоматически подхватятся сервером после перезапуска.

Фон

Легкий клиент также поддерживает дополнительные фоны, которые может выбирать пользователь. Для этого нужно добавить соответствующие файлы в папку ./wwwroot/images/wallpapers, и после добавить иконки для выбора в папку ./wwwroot/images/wallpapers/icons. Также, чтобы у пользователя появилась возможность выбирать дополнительный фон, необходимо добавить название файла с фоном в подходящие темы (свойство TessaWallpapers).

Консоль разработчика

Чтобы открыть консоль разработчика - нажмите [F12] На вкладке “Console” выводятся все ошибки во время выполнения js кода

На вкладке “Network” выводятся все запросы к серверу. При нажатии на запрос будут выводиться данные ответа.

Настройка сотрудников, создаваемых при входе для ADFS

Web-клиент поддерживает аутентификацию через ADFS, основанную на стандарте SAML 2.0. Настройка ADFS описана в руководстве по установке.

В настройке AddNewUserToRoles указывается список идентификаторов ролей, в которые сотрудник, создаваемый при логине при наличии флага CreateUserAfterAuthenticationIfNotExists:true, будет сразу же добавлен до загрузки рабочего места. По умолчанию прописан идентификатор роли “Все сотрудники”.

Вы также можете написать серверное расширение, которое добавляет сотрудника в другие роли или выполняет другие настройки. Приведённое расширение есть в сборке Tessa.Extensions.Default.Server.

/// <summary> /// Расширение добавляет сотрудников, которые были созданы при adfs аутентификации в ЛК, в роль "Все сотрудники". /// </summary> public sealed class ADFSAuthenticationPersonalRoleStoreExtension: CardStoreExtension { private static readonly Guid AllUsersRoleID = Guid.Parse("7FF52DC0-FF6A-4C9D-BA25-B562C370004D"); // Все сотрудники

private readonly IRoleRepository roleRepository;

public ADFSAuthenticationPersonalRoleStoreExtension(IRoleRepository roleRepository) { this.roleRepository = roleRepository; }

public override void BeforeCommitTransaction(ICardStoreExtensionContext context) { if (!context.Request.IsADFSAuthenticationResponseExists()) { return; }

var card = context.Request.Card; var section = card.Sections["PersonalRoles"]; var userID = card.ID; var userName = section.Fields.Get<string>("FullName");

var roleUserRecord = new RoleUserRecord { ID = AllUsersRoleID, RowID = Guid.NewGuid(), IsDeputy = false, RoleType = RoleType.Dynamic, UserID = userID, UserName = userName };

this.roleRepository.Insert(roleUserRecord); } }

// регистрация в классе Registrator, метод RegisterExtensions

extensionContainer .RegisterExtension<ICardStoreExtension, ADFSAuthenticationPersonalRoleStoreExtension>(x => x .WithOrder(ExtensionStage.AfterPlatform, 1) .WithSingleton() .WhenCardTypes(RoleHelper.PersonalRoleTypeID)) ;

Отслеживание изменений свойств контролов и изменения массивов

Многие свойства контролов являются observable. Изменения этих свойств можно отслеживать через вызовы функции MobX. Например свойство selectedRow у контрола таблицы:

import { reaction } from 'mobx'; ... const disposer = reaction(() => grid.selectedRow, row => console.log(row)); ... disposer(); // необходимо вызывать когда реакция больше не нужна (например в finalized в CardUIExtension)

Похожий пример можно найти в расширении 9_additionalTableButtonUIExtension.ts в папке examples.

Свойство rows у грида тоже является observable и тут тоже можно применить reaction, но реакция может вызываться даже когда значения в массиве не поменялись, т.к. reaction по умолчанию сравнивает значения по ссылке. Чтобы этого избежать нужно переопределить функцию сравнения. Пример:

import { reaction } from 'mobx'; ... const disposer = reaction( () => grid.rows, rows => console.log(rows), { equals: (a: ReadonlyArray<GridRowViewModel>, b: ReadonlyArray<GridRowViewModel>): boolean => { // ... сравниваем два массива } } ); ... disposer();

Есть другой способ: если есть возможность работать с табличной секцией карточки, то лучше использовать её. Для этого необходимо использовать CardRowsListener. Пример его использования есть в 3_tableSectionChangedUIExtension.ts в папке examples.

import { CardRowsListener } from 'tessa/cards'; ... const card = grid.cardModel.card; const cardSection = card.sections.get('MyTableSection')!; const rows = cardSection.rows; const listener = new CardRowsListener(); listener.rowInserted.add(() => console.log(grid.rows)); listener.rowDeleted.add(() => console.log(grid.rows)); listener.start(rows); ... listener.stop(); // необходимо вызывать когда listener больше не нужен (например в finalized в CardUIExtension)

Focus контролов при открытии карточки или формы

Метод focus() контролов карточки вызывает нативный метод focus() элемента. Поэтому для правильной работы метода, необходимо чтобы контрол уже был отрендерен в момент вызова. Поэтому если необходимо вызвать focus() при открытии карточки, то нужно вызывать его в CardUIExtension.contextInitialized. В этом методе расширения карточка уже будет отрисована и все контролы будут иметь ссылки на свои html элементы.

public contextInitialized(context: ICardUIExtensionContext) { const textBox = context.model.controls.get('MyTextBox') as TextBoxViewModel; textBox.focus(); }

Если необходимо вызвать при открытии формы контрола таблицы, то нужно вызывать его в GridViewModel.rowInitialized.

table.rowInitialized.add(e => { const textBox = e.cardModel.controls.get('MyTextBox') as GridViewModel; textBox.focus(); });

Работа с decimal полями

Поля карточки типа Decimal хранятся в web-клиенте в виде строки. Для проведения операций над этими полями можно воспользоваться сторонним пакетом decimal.js.. Он позволяет работать со значениями в привычном виде без потери точности.

Установка пакета:

npm i --save decimal.js

Пример:

import {Decimal} from 'decimal.js'; ... const amountField = section.fields.getField('Amount'); if (!amountField || !isDecimalField(amountField)) { return; } let amount = new Decimal(getTypedFieldValue(amountField)); amount = amount.plus(100.54); section.fields.set('Amount', amount.toString(), DotNetType.Decimal);

Установка собственного средства предпросмотра

В Лёгком клиенте предоставляется возможность установки собственного средства предпросмотра.

Разработка собственного средства содержит следующие этапы:

  1. Создайте собственную вью-модель средства предпросмотра PreviewerViewModelBase. Обратитесь к документации интерфейса IPreviewerViewModel для более детальной информации.
  2. Реализуйте собственный компонент отображения с использованием библиотеки React, который в свойствах (пропсах) будет принимать разработанную вью-модель и содержать UI на основе состояния вью-модели.
  3. Свяжите разработанные вью-модель и компонент. Для этого создайте ApplicationExtension расширение и в методе initialize обратитесь к ComponentRegistry.
  4. Теперь осталось установить разработанное средство предпросмотра. Создайте FilePreviewExtension расширение, добавив в метод resolve логику установки собственного средства. Метод resolve в первым аргументом содержит контекст IFilePreviewResolveExtensionContext, в котором содержится фабрика previewerFactory, задача которой определить нужное средство предпросмотра при открытии файла. Фабрику previewerFactory можно переопределить или расширить своей логикой. Обратитесь к документации previewerFactory для более детальной информации.

В папке examples содержится папка examplePreviewer, которая демонстрирует средство предпросмотра для текстовых файлов с форматом exampleTxt. Изучите исходный код представленного примера.

Оформление констант и хелперов

  • Оформление констант и хелперов через экспорт из модуля не рекомендуется, т.к. эти константы и хелперные методы будут лежать в глобальной области видимости.
  • Вариант оформления через static class также не рекомендуется, т.к. при доступе к такому классу будут доступны лишние методы (apply, call и т.д.)
  1. Можно использовать реэкспорт из модуля:

    // internal/myHelper.ts export const Const1 = '1'; export const Const2 = 2;

    export function myHelperMethod() {}

    // myHelper.ts export * as MyHelper from './internal/myHelper'

    // app.ts import { MyHelper } from './myHelper'; MyHelper.myHelperMethod();

  2. Также можно использовать enum для констант:

    export enum MyConsts { Const1 = '1', Const2 = 2 }

  3. Можно использовать namespace, как самый универсальный вариант:

    // myHelper.ts export namespace MyHelper { export const Const1 = '1'; export const Const2 = 2;

    export function myHelperMethod() {} }

    // app.ts import { MyHelper } from './myHelper'; MyHelper.myHelperMethod();

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

Новые хелперы в проектном коде часто могут быть связаны с уже существующими хелперами в платформе. Например StorageHelper. Для того чтобы не делать отдельный хелпер решения SolutionStorageHelper, можно расширить уже существующий:

  1. В модуле src/solution/apiExtensions.ts добавить следующий код:

    // apiExtensions.ts import { StorageHelper } from '@tessa/core';

    declare module '@tessa/core' { namespace StorageHelper { export function mySolutionMethod(): void; } }

    StorageHelper.mySolutionMethod = function mySolutionMethod(): void { // ... };

После этого при использовании StorageHelper в остальном коде новый метод будет доступен:

// someModule.ts import { StorageHelper } from '@tessa/core'; StorageHelper.mySolutionMethod();

Back to top