Управление данными для страниц и окон

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

На схеме показано, как конфигурация данных через фабрики преобразуется в контекст, получаемый контролами.

Соответствие фабрики данных и компонента задается через опцию 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
            }
        }
    }       
}