Управление данными для страниц и окон
Управление состоянием страницы происходит через контекст, который позволяет отделить слой данных от слоя верстки.

На схеме показано, как конфигурация данных через фабрики преобразуется в контекст, получаемый контролами.
Соответствие фабрики данных и компонента задается через опцию storeId. Подробнее о связывании контролов читайте здесь. Благодаря такой связи больше не требуется использовать компонент Controls/Browser.
Кроме того, есть часть опций, которые так или иначе используются для получения данных. Это такие опции как: source
, items
, filter
, selectedKeys
, excludedKeys
, sorting
, navigation
, expandedItems
, root
, collapsedGroups
.
Их не требуется указывать на компонентах. При использовании опции storeId данные опции будут проигнорированы.
Конфигурация данных
Определите набор фабрик данных для вашей страницы. Он задается типом TDataConfigs.
- В качестве ключей фабрик используются уникальные строковые значения, которые в дальнейшем будут применяться для конфигурирования компонентов через опцию storeId.
- В качестве элемента набора фабрик данных используется объект с интерфейсом Controls-DataEnv/IDataConfig.
Минимальная конфигурация включает в себя указание следующих полей:
- dataFactoryName - имя модуля, в котором описывается код фабрики данных.
- dataFactoryArguments - настройки, которые будут переданы в код фабрики данных.
- dependencies - массив имен фабрик данных, которые обязательно надо вызвать ДО текущей описываемой фабрики.
Пример:
{
document: {
dataFactoryName: 'MyModule/Factory',
dataFactoryArguments: {
id: someId,
hasAttachment: false
}
},
nomList: {
dataFactoryName: 'Controls/dataFactory:List',
dataFactoryArguments: {
source: someSource,
keyProperty: someKeyProperty
},
dependencies: ['document']
}
}
Модуль, указанный в dataFactoryName, должен экспортировать три поля:
- loadData - функция, которая отвечает непосредственно за запрос данных.
- slice - конструктор объекта, который будет положен в контекст страницы.
- getDataFactoryArguments - функция, которая возвращает конфигурацию для фабрики данных.
Где:getDataFactoryArguments( config: IDataConfig['dataFactoryArguments'], dependenciesResults: Object): IMyDataFactoryArguments
- config - то, что указано в аргументах фабрики.
- dependenciesResults - результаты загрузки фабрик, указанных в dependencies. Нужно использовать в случае, когда аргументы фабрики формируются динамически, в зависимости от других фабрик данных.
Функцию getDataFactoryArguments необходимо использовать в случаях, когда в качестве loadData используется платформенная функция загрузки или slice. Эта функция позволит подготовить параметры для них.
Кроме того, если используется своя функция загрузки и слайс, данный метод позволит избежать дублирования при построении конфигурации для них.
Плохо:
import {List} from 'Controls/dataFactory';
export {
loadData(dataFactoryParams, dependenciesResults): Promise {
dataFactoryParams.filter.document = dependenciesResults.document;
return List.loadData(dataFactoryParams);
}
slice: List.slice
};
Хорошо:
import {List} from 'Controls/dataFactory';
export {
getDataFactoryArguments(dataFactoryParams, dependenciesResults): IMyFactoryArgs {
dataFactoryParams.filter.document = dependenciesResults.document;
return dataFactoryParams;
}
loadData(dataFactoryParams, dependenciesResults): Promise {
return List.loadData(dataFactoryParams);
}
slice: List.slice
};
Рассмотрим подробнее каждое из этих полей.
Загрузка данных
За загрузку данных отвечает поле loadData из фабрики данных, которое имеет тип функции, возвращающей Promise
.
В качестве первого аргумента в функцию будут переданы параметры фабрики данных, указанные в dataFactoryArguments.
В качестве второго аргумента - результаты работы функций загрузки из фабрик данных, указанных в dependencies. Они представляют собой объект, ключами которого являются имена фабрик данных из dependencies
, а значениями - результаты работы их функций загрузки.
А в качестве третьего аргумента приходит Router - класс, предоставляющий API для работы с роутингом.
Пример простой функции загрузки в рамках фабрики данных:
// MyModule/Factory.ts
import {Slice} from 'MyModule/dataSlice';
import {SbisService} from 'Types/source';
export {
loadData(dataFactoryParams, dependenciesResult, Router): Promise {
return SbisService.read(dataFactoryParams.id)
},
slice: Slice
}
Стандартная фабрика работы со списками
Для работы со списочными данными доступен механизм Controls/dataFactory:List, который содержит функционал загрузки и работы списков, включая механизм фильтрации, навигации, поиска и другие. Его можно использовать как напрямую, указывая в dataFactoryName, так и вызывая его функцию загрузки внутри своей и создавая расширение загрузки данных для списка.
В следующем примере часть результата функции загрузки, указанной в dependencies, передается в фильтр списочного загрузчика.
import {List, ListSlice, IListLoadResult} from 'Controls/dataFactory';
export {
getDataFactoryArguments(dataFactoryParams, dependenciesResults): IMyFactoryArgs {
dataFactoryParams.filter.document = dependenciesResults.document;
return dataFactoryParams;
}
loadData(dataFactoryParams, dependenciesResults): Promise<IListLoadResult> {
return List.loadData(dataFactoryParams);
}
slice: ListSlice
};
Обратите внимание, что все возвращаемые из функций загрузки данные будут сериализованы и переданы на клиент, поэтому результатом может быть либо какой-то из стандартных типов JavaScript
, либо Record/Recordset
.
Получение данных в компонентах
Провайдер контекста
Чтобы воспользоваться загруженными данными при создании компонентов, необходимо использовать Controls/context:ContextOptionsProvider.
Если вы используете механизм построения PageX и одну из стандартных раскладок, то данный компонент уже будет встроен и данные будут переданы ему автоматически.
В противном случае компонент необходимо разместить самостоятельно и сконфигурировать,указав поля, которые описаны выше.
Структура объекта, лежащего в контексте, будет соответствовать структуре конфигурации данных TDataConfigs.
Например, для такой конфигурации:
{
document: {
dataFactoryName: 'MyModule/Factory',
dataFactoryArguments: {
id: someId,
hasAttachment: false
}
},
nomList: {
dataFactoryName: 'Controls/dataFactory:List',
dataFactoryArguments {
source: someSource,
keyProperty: someKeyProperty
},
dependencies: ['document']
}
}
В контексте будет находиться такой объект:
{
// Slice, созданный в EDO/document:Reader
document: docSliceInstance,
// Slice, созданный в Controls/dataFactory:List
nomList: listSliceInstance
}
Объекты, расположенные в контексте данных, носят название Slice ("кусок данных").
Получение контекста в компонентах на tsx
Для того, чтобы получить доступ к контексту данных в своем компоненте на tsx
, необходимо использовать стандартный хук useContext для типа контекста Controls-DataEnv/context:DataContext.
import React from 'react';
import {DataContext} from 'Controls-DataEnv/context';
export function MyComponent(props): JSX.Element {
const dataContext = React.useContext(DataContext);
const slice = dataContext['nomList'];
console.log(slice.filter);
slice.setState();
slice.reload();
}
Получение контекста в компонентах на wml
Если вы работаете с wml, то для получения контекста необходимо использовать HOC
. Он добавляет к остальным опциям _dataOptionsValue.
import {Control, IControlOptions, TemplateFunction} from 'UI/Base';
import controlTemplate= require('wm!MyModule/MyControl/Template');
import {connectToDataContext, IContextValue} from 'Controls/context';
interface IOptions extends IControlOptions {
_dataOptionsValue: IContextValue;
}
class MyControl extends Control<IControlOptions> {
protected _template: TemplateFunction = controlTemplate;
protected _slice: any;
protected _beforeMount(options) {
this._slice = options._dataOptionsValue.myStoreId;
console.log(this._slice.filter);
this._slice.setState({
prop1: 'value',
prop2: 'value'
});
this._slice.reload();
}
}
export default connectToDataContext(MyControl);
Произвольный слайс
Чтобы реализовать произвольный слайс, необходимо унаследоваться от базового Controls-DataEnv/slice и определить метод _initState, в котором вернуть объект с начальным состоянием. В метод initState() приходят два аргумента:
- Результат выполнения метода loadData() для соответствующей фабрики данных;
- Параметры фабрики данных, указанные в dataFactoryArguments. Если задана функция
getDataFactoryArguments
, то в аргумент придет результат ее работы.
Пример произвольного слайса. В указанном примере предполагается, что функция загрузки вернула Record
.
import {Slice} from 'Controls-DataEnv/slice';
export default class documentSlice extends Slice{
protected _initState(loadResult, dataFactoryParams) {
return {
id: loadResult.getKey();
record: loadResult;
}
}
}
Без дополнительных действий у слайса доступен метод setState(). Его вызов с указанием в аргументе полей, которые необходимо изменить, приведет к обновлению состояния и перерисовке контролов, работающих с данным слайсом.
Пример вызова setState:
import documentSlice from 'MyModule/Slice';
someFunction() {
documentSlice.setState({record: someNewRecord});
//вызывается перерисовка компонента, в котором написано useContext
}
Если при изменении одного из полей в состоянии надо пересчитать другие состояния, то необходимо переопределить метод _beforeApplyState. Метод setState переопределять не рекомендуется, т.к. это может вызвать проблемы с перерисовками контролов.
import {Slice} from 'Controls-DataEnv/slice';
export default class documentSlice extends Slice{
protected _initState(loadResult, dataFactoryParams) {
return {
recordKey: loadResult.getKey();
record: loadResult;
}
}
protected _beforeApplyState(nextState) {
// когда устанавливают новый рекорд, меняем в состоянии ключ
if (this.state.record !== nextState.record) {
nextState.recordKey = nextState.record.getKey();
}
}
}
Стандартный слайс для работы со списками
Controls.dataFactory:List.slice - доступный из коробки механизм для работы со списочными данными, который содержит функционал работы с фильтрами, поиском, выделением, возможностью перезапроса списочных данных и т.д. Его можно использовать как напрямую, указывая его в поле Slice фабрики данных, так и наследуясь от него и дополняя необходимую логику.
В следующем часть результата функции загрузки, указанной в dependencies, передается в фильтр списочного загрузчика.
import {List} from 'Controls/dataFactory';
export default class NomListSlice extends List.slice {
protected _initState(loadResult, dataFactoryParams) {
const listState = super._initState(loadResult, dataFactoryParams);
//добавляем мегаважное поле в состояние
listState.myCustomProp = '123';
return listState
}
protected _beforeApplyState(nextState) {
// когда меняется вид отображения списка на таблицу в фильтр надо добавить мегаважное поле
if (nextState.viewMode === 'table') {
nextState.filter = {
...nextState.filter,
someParam: nextState.myCustomProp
}
}
}
}