Дополнительно¶
Темы¶
Легкий клиент поддерживает файлы с настройками тем толстого клиента. Темы можно найти в папке 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);
Установка собственного средства предпросмотра¶
В Лёгком клиенте предоставляется возможность установки собственного средства предпросмотра.
Разработка собственного средства содержит следующие этапы:
- Создайте собственную вью-модель средства предпросмотра PreviewerViewModelBase. Обратитесь к документации интерфейса IPreviewerViewModel для более детальной информации.
- Реализуйте собственный компонент отображения с использованием библиотеки React, который в свойствах (пропсах) будет принимать разработанную вью-модель и содержать UI на основе состояния вью-модели.
- Свяжите разработанные вью-модель и компонент. Для этого создайте ApplicationExtension расширение и в методе initialize обратитесь к ComponentRegistry.
- Теперь осталось установить разработанное средство предпросмотра. Создайте FilePreviewExtension расширение, добавив в метод resolve логику установки собственного средства. Метод resolve в первым аргументом содержит контекст IFilePreviewResolveExtensionContext, в котором содержится фабрика previewerFactory, задача которой определить нужное средство предпросмотра при открытии файла. Фабрику previewerFactory можно переопределить или расширить своей логикой. Обратитесь к документации previewerFactory для более детальной информации.
В папке examples содержится папка examplePreviewer, которая демонстрирует средство предпросмотра для текстовых файлов с форматом exampleTxt. Изучите исходный код представленного примера.
Оформление констант и хелперов¶
- Оформление констант и хелперов через экспорт из модуля не рекомендуется, т.к. эти константы и хелперные методы будут лежать в глобальной области видимости.
- Вариант оформления через
static class
также не рекомендуется, т.к. при доступе к такому классу будут доступны лишние методы (apply
,call
и т.д.)
- Можно использовать реэкспорт из модуля:
// 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();
-
Также можно использовать
enum
для констант:export enum MyConsts { Const1 = '1', Const2 = 2 }
-
Можно использовать
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
, можно расширить уже существующий:
- В модуле
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();