Карточка записи¶
В карточку записи могут быть добавлены следующие дополнительные точки:
Новая вкладка в карточке записи¶
Рисунок 2 - Новая вкладка в карточке
Точка расширения UEDataCardTabItem предназначена для добавления вкладок в карточке записи.
Текущие вкладки атрибутов, связей и т.д. привязаны к DataCardStore
в резолвере. Если вы наследуете от AbstractCardStore
, вам необходимо самостоятельно подготовить содержимое вкладки.
Пример UEDataCardTabItem:
type DataCardTabItemProps<T extends INamespaceDataRecord> = {
dataCardStore: AbstractCardStore<T>;
}
export type UEDataCardTabItem<T extends INamespaceDataRecord> = UeModuleBase & {
default: {
component: ComponentType<DataCardTabItemProps<T>>; // Компонент, отображающий содержимое вкладки
meta: {
tab: TabItem; // Описание вкладки (отображаемое имя, ключ и т.д.)
position: 'left' | 'right'; // Возможность добавления вкладок как в общий список, так и в правую часть карточки
};
resolver: (dataCardStore: AbstractCardStore<T>) => boolean; // Функция, определяющая, в каком случае показывать кнопку (в зависимости от состояния карточки)
};
}
Пример реализации:
export const relationGraphUE: UEDataCardTabItem<DataRecord> = {
'default': {
type: UEList.DataCardTabItem,
moduleId: 'relationGraph',
active: true,
system: false,
component: RelationGraphTabItem, // Компонент, отображающий граф связей
meta: {
tab: {
key: 'relationGraph',
tab: i18n.t('module.data-ee>ue>relationGraph>tabLabel'),
order: 30
},
position: 'left'
},
resolver: (dataCardStore: AbstractCardStore<DataRecord>) => {
return dataCardStore instanceof DataCardStore &&
MetaTypeGuards.isEntity(dataCardStore.metaRecordStore.getMetaEntity()) && Boolean(dataCardStore.etalonId) &&
dataCardStore.draftStore?.draftId === undefined;
}
}
};
Новый контент на правой боковой панели¶
Рисунок 3 - Правая боковая панель
Точка расширения UEDataCardSidePanelItem предназначена для отображения элементов управления в правой части карточки записи.
Периоды действия, кластеры, задачи - это пользовательские точки расширения типа UEDataCardSidePanelItem
.
В текущей реализации они не привязаны к типу хранилища и будут доступны для всех возможных записей. Это поведение может быть изменено в соответствии с требованиями проекта.
Пример UEDataCardSidePanelItem:
type DataCardSidePanelItemProps<T extends INamespaceDataRecord> = {
dataCardStore: AbstractCardStore<T>;
}
export type UEDataCardSidePanelItem<T extends INamespaceDataRecord> = UeModuleBase & {
default: {
component: ComponentType<DataCardSidePanelItemProps<T>>; // Компонент, отображающий содержимое панели
meta: {
order: number; // Последовательность панелей
};
resolver: (dataCardStore: AbstractCardStore<T>) => boolean; // Функция, определяющая, в каком случае показывать кнопку (в зависимости от состояния карточки)
};
}
Пример реализации:
export const clusterWidgetUE: UEDataCardSidePanelItem<any> = {
'default': {
type: UEList.DataCardSidePanelItem,
moduleId: 'dataRecordClusters',
active: true,
system: false,
component: ClustersWidget, // Компонент панели
resolver: (dataCardStore: AbstractCardStore<any>) => {
return Boolean(dataCardStore.etalonId); // Отображать только при наличии EtalonId записи
},
meta: {
order: 20
}
}
};
Новая кнопка в шапке карточки записи¶
Рисунок 4 - Кнопки в шапке карточки записи
Точка расширения UEDataCardButton предназначена для добавления кнопок в шапку карточки записи.
Принимает AbstractCardStore в резолвере, так что определенные элементы могут быть добавлены/исключены с помощью реализации определенного класса хранилища.
Пример реализации: история записей, которая отображается только в опубликованных записях.
ueModuleManager.addModule('DataCardButton',
{
moduleId: 'cardButton',
active: true,
system: false,
component: ButtonComponent,
resolver: (dataCardStore) => true,
meta: {
label: 'Header button',
intent: INTENT.INFO
}
}
);
interface IProps {
dataCardStore: DataCardStore;
}
export class ButtonComponent extends React.Component<IProps> {
clickHandler () {
return axios.fetch(...)
}
override render () {
const {
label,
intent,
dataCardStore
} = this.props;
return (
<Button
isRound={true}
isGhost={true}
intent={intent}
onClick={this.clickHandler}
>
{label}
</Button>
);
}
}
Отображение атрибута в карточке записи¶
Рисунок 5 - Отображение простых атрибутов и атрибутов массива
Точка расширения UEAttributePreview отвечает за отображение атрибутов в карточке записи (Рисунок 4).
Описание UEAttributePreview:
import {INamespaceMetaModel, UeModuleBase} from '@unidata/core-app';
import {ComponentType} from 'react';
import {AbstractAttribute, UPathMetaStore} from '@unidata/meta';
import {AbstractModel} from '@unidata/core';
import {ArrayAttributeStore} from '../../page/dataview_light/dataviewer/card/attribute/store/ArrayAttributeStore';
import {SimpleAttributeStore} from '../../page/dataview_light/dataviewer/card/attribute/store/SimpleAttributeStore';
import {AbstractDataEntityStore} from '../../page/dataview_light/store/dataEntity/AbstractDataEntityStore';
type AttributePreviewProps = {
attributeStore: SimpleAttributeStore | ArrayAttributeStore;
dataEntityStore: AbstractDataEntityStore;
metaEntityStore: UPathMetaStore<INamespaceMetaModel>;
}
export type UEAttributePreview = UeModuleBase & {
default: {
component: ComponentType<AttributePreviewProps>; // компонент для рендеринга атрибута
meta: {
name: string;
displayName: () => string;
};
resolver: (attribute: AbstractAttribute, model: AbstractModel) => boolean;
};
}
Отображение кастомного атрибута в карточке записи¶
Точка расширения UERenderAttributeOnDataCard предназначена для кастомного отображения атрибута в карточке записи.
Описание UERenderAttributeOnDataCard:
type RenderDataAttributeProps = {
attributeStore: SimpleAttributeStore | ArrayAttributeStore;
dataEntityStore: AbstractRecordEntityStore;
metaEntityStore: UPathMetaStore<IMetaModel>;
}
type Resolver = (attribute: IMetaAbstractAttribute, model: AbstractModel) => boolean;
type Meta = {
name: string; // Ключ, который используется для настройки вида атрибута в метамодели.
previewOptions?: string [];
displayName: () => string;
additionalProps?: {type?: string};
};
export type UERenderAttributeOnDataCard = UeModuleBase<Resolver, Meta> & {
component: ComponentType<RenderDataAttributeProps>;
}
Пример реализации:
type UERenderAttributeOnDataCard = UniverseUE.IUeMeta['RenderAttributeOnDataCard'];
// Описание UE
const simpleType: UERenderAttributeOnDataCard = {
moduleId: 'customStringAttribute',
active: true,
system: false,
component: MyCustomAttribute, // Компонент логики
resolver: (attribute: AbstractAttribute, model: AbstractModel) => {
return MetaTypeGuards.isAbstractSimpleAttribute(attribute) &&
attribute.typeCategory === AttributeTypeCategory.simpleDataType &&
attribute.simpleDataType.getValue() === SIMPLE_DATA_TYPE.STRING;
},
meta: {
name: 'default',
displayName: () => i18n.t('module.record>dataview>defaultView'),
additionalProps: {type: 'string'}
}
};
Пример файла с компонентом MyCustomAttribute:
import * as React from 'react';
import {observer} from 'mobx-react';
import {computed} from 'mobx';
import {Input} from '@universe-platform/uikit';
import {i18n} from '@universe-platform/sdk';
import {IMetaModel, UPathMetaStore, AbstractRecordEntityStore, SimpleAttributeStore} from '@universe-platform/sdk-mdm
interface IProps {
attributeStore: SimpleAttributeStore;
dataEntityStore: AbstractRecordEntityStore;
metaEntityStore: UPathMetaStore<IMetaModel>;
type: 'string' | 'number' | 'integer';
}
@observer
export class MyCustomAttribute extends React.Component<IProps> {
get store () {
return this.props.attributeStore;
}
get inputMode () {
if (this.props.type === 'integer') {
return 'numeric';
} else if (this.props.type === 'number') {
return 'decimal';
}
return 'text';
}
@computed
get attribute () {
return this.props.attributeStore.getDataAttribute();
}
onTextChange = (e: React.ChangeEvent<HTMLInputElement>) => {
let value = e.target.value;
this.setAttributeValue(value);
};
onNumberChange = (valueArg: number | undefined) => {
const value = valueArg === undefined ? null : valueArg;
this.setAttributeValue(value);
};
setAttributeValue (value: string | number | null) {
this.props.attributeStore.setAttributeValue(value);
}
getPlaceholder (readOnly: boolean, type: string = 'text') {
if (readOnly) {
return i18n.t('module.record>dataview>valueUnset');
}
if (type === 'number') {
return i18n.t('module.record>dataview>enterNumber');
} else if (type === 'integer') {
return i18n.t('module.record>dataview>enterInteger');
} else {
return i18n.t('module.record>dataview>enterText');
}
}
private renderNumberInput () {
const value = Number.parseFloat(this.attribute.value.getValue()) || undefined;
const errorMessage = i18n.t(this.attribute.getErrorMessage('value'));
const readOnly = this.store.getReadOnly() || !this.store.getIsEditMode();
const placeholder = this.getPlaceholder(readOnly, this.props.type);
return (
<Input.Number
value={value}
onChange={this.onNumberChange}
hasError={Boolean(errorMessage)}
errorMessage={errorMessage || undefined}
isInteger={this.inputMode === 'numeric'}
inputMode={this.inputMode}
autoFocus={true}
allowClear={true}
placeholder={placeholder}
onBlur={this.store.setEditModeOff}
/>
);
}
private renderTextInput () {
const errorMessage = i18n.t(this.attribute.getErrorMessage('value'));
const readOnly = this.store.getReadOnly() || !this.store.getIsEditMode();
const placeholder = this.getPlaceholder(readOnly, this.props.type);
return (
<Input
value={this.attribute.value.getValue()}
onChange={this.onTextChange}
hasError={Boolean(errorMessage)}
allowClear={true}
errorMessage={errorMessage || undefined}
inputMode={this.inputMode}
autoFocus={true}
placeholder={placeholder}
onBlur={this.store.setEditModeOff}
/>
);
}
override render () {
if (this.props.type === 'number' || this.props.type === 'integer') {
return this.renderNumberInput();
}
return this.renderTextInput();
}
}
Пример настройки UE для кастомного представления атрибута определенного типа (например String):
type UERenderAttributeOnDataCard = UniverseUE.IUeMeta['RenderAttributeOnDataCard'];
// Описание UE
const simpleType: UERenderAttributeOnDataCard = {
moduleId: 'customStringAttribute',
active: true,
system: false,
component: MyCustomAttribute, // Компонент с логикой
resolver: (attribute: AbstractAttribute, model: AbstractModel) => {
return MetaTypeGuards.isAbstractSimpleAttribute(attribute) &&
attribute.typeCategory === AttributeTypeCategory.simpleDataType &&
attribute.simpleDataType.getValue() === SIMPLE_DATA_TYPE.STRING;
},
meta: {
name: 'some_custom_view', // Произвольный ключ, который будет использоваться в поле `вид` в метамодели в настройке атрибута наряду с displayName
displayName: () => 'Название представления этого атрибута',
additionalProps: {type: 'string'}
}
};
Принцип выбора атрибутов из UE для отрисовки в карточке:
Если в модели данных выбрать вид атрибута, то в его custom_properties в поле
DATACARD_ATTRIBUTE_TYPE
запишется значение из поляname
разделаmeta
настроек UE атрибута. Таким образом, это значение участвует при фильтрации нужного представления (см. ниже).
// const metaAttribute: AbstractAttribute;
// const store: SimpleAttributeStore | ArrayAttributeStore;
// ...
const typeCategory = metaAttribute.typeCategory;
const customProperty = metaAttribute.getCustomProperty(AttributeCustomPropertyEnum.DATACARD_ATTRIBUTE_TYPE);
let view = 'default';
if (customProperty) {
view = customProperty.value.getValue();
}
if (!typeCategory) {
return null;
}
const ueAttributes = ueModuleManager.getResolvedModulesByType('RenderAttributeOnDataCard', [
this.metaAttribute,
this.metaEntityStore.getMetaEntity()
]);
let ueAttribute = ueAttributes.find((module) => {
return module.meta.name === view;
}) ||
ueAttributes.find((module) => {
return module.meta.name === 'default';
});
Отображение секции комплексного атрибута¶
Точка расширения UEComplexAttributeSection отвечает за отображение секции с комплексными атрибутами.
Описание UEComplexAttributeSection:
type Props = {
allowChangeView: boolean; // Указывает, разрешено ли изменять вид на таблицу
allowModalForNested?: boolean;
dataPath: string;
metaPath: string;
isNavigable: boolean;
title: string;
attrLabelWidth?: number;
attrLabelPosition?: LabelPosition | string;
dataEntityStore: AbstractRecordEntityStore;
metaEntityStore: UPathMetaStore<IMetaModel>;
navigableItemsStore?: NavigableItemsStore;
dataCardStore: AbstractCardStore<IRecordEntity>;
}
type Resolver = (group: RecordCardGroupLayoutItem, dataCardStore: AbstractCardStore<IRecordEntity>) => boolean;
export type UEComplexAttributeSection = UeModuleBase<Resolver, {}> & {
component: ComponentType<Props>;
}
Пример использования UEComplexAttributeSection:
ueModuleManager.addModule('ComplexAttributeSection', {
moduleId: 'mdmComplexAttributes',
active: true,
system: false,
component: ComplexAttributeSection, // Компонент, отображающий комплексные атрибуты в карточке
resolver: (group: RecordCardGroupLayoutItem, dataCardStore: AbstractCardStore<IRecordEntity>) => {
return group.isComplex;
},
meta: {
order: 10
}
});
Отображение элемента перед атрибутом¶
Точка расширения UEDataViewElementPrefix отвечает за отображение доп. информации перед атрибутами в карточке атрибутами. Используется для подсветки атрибутов с ошибками.
Описание UEDataViewElementPrefix:
type DataAttributePrefixProps = {
metaEntity: IMetaModel;
path: string;
parentPath?: string;
type: RecordViewElementType;
cmpRef: React.RefObject<React.Component>;
navigableItemsStore?: NavigableItemsStore;
}
type Meta = {
order: number;
};
export type UEDataViewElementPrefix = UeModuleBase<DefaultUeResolver, Meta> & {
component: ComponentType<DataAttributePrefixProps>;
}
Пример использования UEDataViewElementPrefix:
ueModuleManager.addModule('DataViewElementPrefix', {
moduleId: 'dqErrorIndicator',
active: true,
system: false,
resolver: () => true,
meta: {
order: 1
},
component: DqErrorIndicatorContainer // Компонент, отображающий индикацию ошибок
});
Отображение полей ввода значений атрибутов¶
Рисунок 6 - Пример подсветки поля атрибута
Точка расширения AttributeViewProperties отвечает за стиль отображения полей ввода у атрибутов.
Резолвер имеет следующие параметры:
dataPath: string,
metaPath: string,
dataEntityStore: AbstractRecordEntityStore,
metaEntityStore: UPathMetaStore<IMetaModel>
Параметры отображения описываются посредством поля meta
и имеют следующий тип:
type DataAttributeViewParams = {
isHighlighted?: boolean;
}
Для передачи стиля отображения в компоненты атрибутов используется контекст DataAttributeViewContext
. Данный контекст экспортируется из модуля @universe-platform/record
.
Пример использования контекста в компоненте:
export class HighlightedAttribute extends React.Component<Props> {
static override contextType = DataAttributeViewContext;
declare context: React.ContextType<typeof DataAttributeViewContext>;
override render () {
return (
<Input
value={this.props.value}
onChange={this.props.onChange}
isHighlighted={this.context.isHighlighted}
/>
);
}
}
Пример использования точки расширения:
function resolver (
dataPath: string,
metaPath: string,
dataEntityStore: AbstractRecordEntityStore,
metaEntityStore: UPathMetaStore<IMetaModel>
) {
const attrNameToHighlight = 'attributeToHighlight';
if (dataPath === attrNameToHighlight && metaPath === attrNameToHighlight) {
return true;
}
return false;
}
ueModuleManager.addModule('AttributeViewProperties', {
moduleId: 'highlightAttribute',
active: true,
system: false,
resolver: resolver,
meta: {
isHighlighted: true
}
});
Отображение тега в шапке карточки записи¶
Точка расширения UEDataCardTag предназначена для добавления тега в шапку карточки записи.
Описание UEDataCardTag:
type DataCardTagProps<T extends IRecordEntity> = {
dataCardStore: AbstractCardStore<T>;
}
export type UEDataCardTag<T extends IRecordEntity> = UeModuleBase & {
default: {
component: ComponentType<DataCardTagProps<T>>;
meta: {
order: number;
};
resolver: (dataCardStore: AbstractCardStore<T>) => boolean;
};
}
Пример UEDataCardTag: отображение тега черновика записи в процессе согласования.
interface IProps {
dataCardStore: AbstractCardStore<any>;
}
@observer
class TagDraftIsDelayed extends React.PureComponent<IProps> {
override render () {
const draft = this.props.dataCardStore.draftStore?.selectedDraft;
const state = draft?.state.selectedItem;
return (
<Tag intent={INTENT.INFO} key={'draftTag'}>
<Tooltip overlay={state?.description.getValue()}>
<span>
{draft?.state.displayValue}
</span>
</Tooltip>
</Tag>
);
}
}
export const dataCardTagUe: UEDataCardTag<IRecordEntity> = {
'default': {
type: UEList.DataCardTag,
moduleId: 'TagDraftDelayedByWorkflow',
active: true,
system: false,
component: TagDraftIsDelayed,
resolver: (dataCardStore: AbstractCardStore<IRecordEntity>) => {
return dataCardStore.draftStore?.selectedDraft?.state.getValue() === 'DELAYED_BY_WORKFLOW';
},
meta: {
order: 0
}
}
};
Метод, вызываемый после публикации черновика записи¶
Точка расширения UEAfterPublishRecord позволяет вызывать функции после публикации черновика записи.
Описание UEAfterPublishRecord:
export type UEAfterPublishRecord<T extends IRecordEntity> = UeModuleBase & {
default: {
fn: (dataCardStore: AbstractCardStore<T>) => void;
resolver: (dataCardStore: AbstractCardStore<T>) => boolean;
};
}
Пример UEAfterPublishRecord: дополнительное сообщение, если настроен процесс согласования при публикации записи.
function afterPublish () {
Dialog.showMessage(i18n.t('module.workflow>dataCardAfterPublish>message'));
}
export const afterPublishRecordUe: UEAfterPublishRecord<any> = {
'default': {
type: UEList.AfterPublishRecord,
moduleId: 'afterPublishWorkflow',
active: true,
system: false,
fn: afterPublish,
resolver: (store: AbstractCardStore<any>) => {
return store.draftStore?.selectedDraft?.state.getValue() === 'DELAYED_BY_WORKFLOW';
},
meta: {}
}
};
Метод, вызываемый после публикации c ошибкой¶
Точка расширения UEAfterPublishRecordFailure позволяет вызывать функции после публикации черновика записи с ошибкой.
Описание UEAfterPublishRecordFailure:
export type UEAfterPublishRecordFailure<T extends IRecordEntity> = UeModuleBase & {
default: {
fn: (dataCardStore: AbstractCardStore<T>, errors: ServerDetailsError[]) => void;
resolver: (dataCardStore: AbstractCardStore<T>, errors: ServerDetailsError[]) => boolean;
};
}
Добавление дополнительного стора на карточку записи¶
Точка расширения UERecordCardInnerStore используется для добавления дополнительной логики в карточку записи.
Созданный стор вызывается на все глобальные действия с карточкой: получение данных (процессинг) перед сохранением (для добавления данных в запрос).
Описание UERecordCardInnerStore:
type Resolver = (dataCardStore: AbstractCardStore<IRecordEntity>) => boolean;
export type UERecordCardInnerStore = UeModuleBase<Resolver, {}> & {
fn: (dataCardStore: AbstractCardStore<IRecordEntity>) => IInnerRecordCardStore;
};
Описание интерфейса IInnerRecordCardStore:
export interface IInnerRecordCardStore {
/**
* Функция инициализации внутреннего стора. Вызывается один раз при создании стора карточки записи.
*
* Здесь могут быть любые методы инициализации стора. Например, загрузка данных.
* Если ничего инициализировать не нужно, можно вернуть пустое обещание.
*/
init (): Promise<void>;
/**
* Флаг указывающий, что внутренний стор имеет измененные данные
*
* У родительского стора (DataCardStore) есть аналогичный флаг isDirty, который собирает это состояние со
* всех своих внутренних сторов. Если хотя бы у одного из внутренних сторов isDirty равно true, то значит
* isDirty всей карточки равно true.
*
* Этот флаг нужен, например, для блокировки кнопки "Сохранить". Если нет необходимости использовать эту
* функциональность, то флаг можно выставить в false
*/
isDirty: boolean;
/**
* Ключ таба карточки записи, к которому относится данный стор.
* Этот таб будет подсвечен в случае ошибок валидации.
*
* Свойство необходимо для подсветки валидационных ошибок. tabKey это тот таб в котором
* должны располагаться поля с валидационными ошибками. Свойство отвечает только за подсветку таба.
* В случае если в сторе не предполагаются валидационные ошибки, то можно указать пустую строку
*/
tabKey: string;
/**
* Ключ для помещения payload в запросе
*
* По умолчанию для получения/изменения данных записи используется atomic-запрос
* Это составной запрос в котором может быть указано несколько различных ключей с полезной нагрузкой
*
* Если нет необходимости дополнять запрос дополнительными аргументами, то в качестве значения можно
* указать пустую строку.
*/
payloadKey: string;
/**
* Payload который передается в запрос получения данных
*
* Подробнее см. описание payloadKey
*/
payloadForGet?: Object;
/**
* Payload который передается в запрос сохранения
*
* Подробнее см. описание payloadKey
*/
payloadForSave?: Object;
/**
* Payload который передается в запрос восстановления данных
*
* Подробнее см. описание payloadKey
*/
payloadForRestore?: Object;
/**
* Метод обработки данных из ответа на запрос получения данных
*
* Если дополнительная обработка полученных данных из atomic-запроса не требуется,
* то тело метода можно оставить пустым
*/
processAtomic (data: object, operationType: AtomicOperationType): void;
/**
* Метод валидации данных во внутреннем сторе. Вызывается перед публикацией записи
*
* Если будут возвращены ошибки валидации, то публикация прервется
*/
validate (): IValidationResult;
/**
* Метод обогащения / изменения данных сущностей во внутреннем сторе.
*/
enrich?(enrichedEntities: IEnrichedRecordEntity[]): void;
}
Пример использования UERecordCardInnerStore:
class CustomCardInnerStore implements IInnerRecordCardStore {
// Если нет необходимости дополнять запрос дополнительными аргументами,
// можно указать пустой объект
payloadForGet = {};
// Если нет необходимости дополнять запрос дополнительными аргументами,
// можно указать пустой объект
payloadForRestore = {};
// Если нет необходимости дополнять запрос дополнительными аргументами,
// можно указать пустой объект
payloadForSave = {};
/**
* Пример данных хранящихся в сторе. В данном случе это простой счетчик
* и функция increase для его увеличения
*/
@observable
counter = 0;
@action
increase () {
this.counter = this.counter + 1;
}
init () {
return Promise.resolve();
}
get isDirty () {
return false;
}
get payloadKey () {
// Если нет необходимости дополнять запрос дополнительными аргументами,
// то в качестве значения можно указать пустую строку.
return '';
}
processAtomic (data: object, operationType: AtomicOperationType) {
// Если дополнительная обработка полученных данных из запроса /data/atomic/ не требуется,
// то тело метода можно оставить пустым
}
get tabKey () {
// В случае если в сторе не предполагаются валидационные ошибки, то можно указать пустую строку
return '';
}
validate () {
return new Map();
}
}
/**
* Инициализация innerStore
*/
ueModuleManager.addModule('RecordCardInnerStore', {
moduleId: 'customCardInnerStore',
active: true,
system: false,
resolver: () => true,
meta: {},
fn: (cardStore: DataCardStore) => new CustomCardInnerStore()
});
Пример получения UERecordCardInnerStore внутри другого UE:
Стор AbstractCardStore
предоставляет доступ к innerStore
с помощью метода getInnerStore(moduleId)
.
Получение innerStore
показано на примере UE DataCardMenuItem:
ueModuleManager.addModule('DataCardMenuItem', {
moduleId: 'customCardMenuItem',
active: true,
system: false,
component: observer(class CustomCardMenuItem extends React.Component<DataCardMenuProps> {
override render () {
const innerStore = this.props.dataCardStore.getInnerStore('customCardInnerStore') as CustomCardInnerStore;
return (
<DropDown.Item
onClick={() => {
innerStore.increase();
}}
>
{`increment (${innerStore.counter})`}
</DropDown.Item>
);
}
}),
resolver: () => true,
meta: {
menuGroupId: 'delete'
}
});
Клонирование записи¶
Точка расширения UECloneRecord предназначена для добавления фрагмента к запросу на клонирование записи, а также для вывода в окне настроек клонирования интерфейса (для изменения передаваемых во фрагменте параметров).
Используемые типы:
interface ICloneInnerStore<T extends keyof UniverseUE.ICloneAtomicPayload> {
/*
* Возвращаемый фрагмент запроса на клонирование и ключ, под которым он будет добавлен в запрос, должны быть
* глобально объявлены в пространстве имен UniverseUE
*/
payloadContent: {
key: T;
content: UniverseUE.ICloneAtomicPayload[T];
};
/*
* Возвращает true, если нужно запретить подтверждение клонирования записи
*/
readonly hasErrors: boolean;
}
/*
* Передаваемые в компонент параметров пропсы
*/
interface ICloneRecordParametersProps {
store: CloneStore;
}
type UECloneRecordResolver = (cloneStore: CloneStore) => boolean;
type UECloneRecordMeta = {
/*
* @param key - передается meta.key текущего User Exit
*/
getStore: (cloneStore: CloneStore, key: string) => ICloneInnerStore;
key: string;
};
export type UECloneRecord = UeModuleBase<UECloneRecordResolver, UECloneRecordMeta> & {
component: ComponentType<ICloneRecordParametersProps>;
};
Пример UECloneRecord:
const CLONE_RECORD_ADDITIONAL_PAYLOAD_KEY = 'clone_record_additional_fragment_v1' as const;
interface ICloneRecordAdditionalPayload {
cleanUnique: boolean;
}
declare global {
namespace UniverseUE {
export interface ICloneAtomicPayload {
[CLONE_RECORD_ADDITIONAL_PAYLOAD_KEY]: ICloneRecordAdditionalPayload;
}
}
}
class CloneRecordAdditionalFragmentStore implements ICloneInnerStore {
@observable
cleanUnique: boolean;
@action
setCleanUnique (cleanUnique: boolean) {
this.cleanUnique = cleanUnique;
}
get payloadContent () {
return {
key: CLONE_RECORD_ADDITIONAL_PAYLOAD_KEY,
content: {
cleanUnique: this.cleanUnique
}
}
}
get hasErrors () {
return false;
}
}
@observer
class CloneRecordUserExit extends React.Component<ICloneRecordParametersProps> {
get store () {
return this.props.store.getStore(CLONE_RECORD_ADDITIONAL_PAYLOAD_KEY) as CloneRecordAdditionalFragmentStore;
}
override render () {
const {store} = this;
return (
<CardPanel
internal={true}
title={'Дополнительные параметры клонирования'}>
<Field.Checkbox
label={'Очистить уникальные значения?'}
defaultChecked={store.cleanUnique}
onChange={(name, value) => store.setCleanUnique(value)}
/>
</CardPanel>
);
}
}
ueModuleManager.addModule('CloneRecord', {
active: true,
component: CloneRecordUserExit,
meta: {
key: CLONE_RECORD_ADDITIONAL_PAYLOAD_KEY,
getStore: () => {
return new CloneRecordAdditionalFragmentStore();
}
},
moduleId: CLONE_RECORD_ADDITIONAL_PAYLOAD_KEY,
resolver: (cloneStore: CloneStore) => {
const dataRecordInnerStore = cloneStore.dataCardStore.getInnerStore('data-record-additional-store');
return dataRecordInnerStore !== undefined;
},
system: false
});
Рисунок 7 - Модальное окно клонирования записи
Расширенный поиск атрибута "Ссылка на справочник"¶
Точка расширения LookupExtendedSearchEnabled используется для активации и деактивации расширенного поиска по атрибутам типа "Ссылка на справочник".
User exit влияет на отображение расширенного поиска в карточке записи (кнопка "Расширенный поиск" при выборе значения атрибута) и в окне поиска записей реестра.
Для того чтобы активировать расширенный поиск, необходимо добавить этот модуль в проект. Если модуль уже добавлен, а от расширенного поиска необходимо отказаться, то модуль нужно отключить.
По аналогии был создан User exit HierarchyLookupExtendedSearchEnabled для иерархических атрибутов типа "Ссылка на справочник".
Описание LookupExtendedSearchEnabled:
import {DefaultUeResolver, UeModuleBase} from '@universe-platform/user-exit';
export type UEHierarchyLookupExtendedSearchEnabled = UeModuleBase<DefaultUeResolver, {}>