import { convertType, DataType, toString } from "./metadata/DataType.js"; import { ChildDescriptor, FieldDescriptor, Filter, FilterProvider, Sort, SortingProvider, UnitMetadata } from "./metadata/UnitMetadata.js"; import { Action, DataUnitAction, ExecutionContext } from "./state/action/DataUnitAction.js"; import StateManager from "./state/StateManager.js"; import ErrorException from "../exceptions/ErrorException.js"; import WaitingChangeException from "../exceptions/WaitingChangeException.js"; import { StringUtils } from "../utils/StringUtils.js"; import ObjectUtils from "../utils/ObjectUtils.js"; import { LoadDataRequest } from "./loading/LoadDataRequest.js"; import { LoadDataResponse } from "./loading/LoadDataResponse.js"; import { PaginationInfo } from "./loading/PaginationInfo.js"; import { AddedRecordsReducer, getAddedRecords, prepareAddedRecordId, prepareCopiedRecord } from "./state/slice/AddedRecordsSlice.js"; import { ChangesReducer, getChangesToSave, isDirty, hasDirtyRecords } from "./state/slice/ChangesSlice.js"; import { SnapshotReducer, getCurrentRecords, getFieldValue, getModifiedRecords, getSelectedRecord } from "./state/slice/SnapshotSlice.js"; import { getCurrentPage, getLastPage, getPaginationInfo, getCurrentRequest, LoadingControlReducer, hasMorePages, hasPreviousPages } from "./state/slice/LoadingControlSlice.js"; import { getRecords, RecordsReducer } from "./state/slice/RecordsSlice.js"; import { RemovedRecordsReducer } from "./state/slice/RemovedRecordsSlice.js"; import { getSelection, getSelectionInfo, hasNext, hasPrevious, SelectionReducer } from "./state/slice/SelectionSlice.js"; import { getField, getMetadata, UnitMetadataReducer } from "./state/slice/UnitMetadataSlice.js"; import { getBlockingWaitingChanges, getWaitingChangePromisses, getWaitingChanges, isWaiting, WaitingChangesReducer } from "./state/slice/WaitingChangesSlice.js"; import { canRedo, canUndo, HistReducer } from "./state/HistReducer.js"; import { getFormattedValue } from "./formatting/PrettyFormatter.js"; import { v4 as uuid } from "uuid"; import { DataUnitStorage } from "./DataUnitStorage.js"; import { getInvalidFieldMessage, InvalidFieldsReducer } from "./state/slice/InvalidFieldsSlice.js"; import { getLoadingProperties, LoadingPropertiesReducer } from "./state/slice/LoadingProperties.js"; import SortingUtils from "../utils/SortingUtils.js"; import ServiceCanceledException from "../exceptions/ServiceCanceledException.js"; import SilentException from "../exceptions/SilentException.js"; import { DataUnitEventOptions, DUActionInterceptor, HideFieldOptions, LoadDataParams, QuickFilter, Record, SavedRecord, SelectionMode, WaitingChange } from "./interfaces/dataUnit.js"; import { Change } from "./Changes.js"; import { SelectionInfo } from "./SelectionInfo.js"; /*** * `DataUnit`: Atua como uma camada de abstração entre o back-end e a interface do usuário. */ export default class DataUnit { public static CHANGING_PAGE_LOADING_SOURCE = "CHANGING_PAGE_LOADING_SOURCE"; public static ALL_RECORDS_SELECTION_SOURCE = "ALL_RECORDS_SELECTION_SOURCE"; public static DEFAULT_DATAUNIT_NAME = "dataunit"; private _uuid: string; private _name: string; private _observers: Map void>; private _sortingProvider?: SortingProvider; private _filterProviders: Map; private _stateManager: StateManager; private _interceptors: Map; private _pageSize: number; private _childByName = new Map(); private _parentDataUnit: DataUnit | undefined; private _loadingLockers: Promise[]; private _savingLockers: Promise[] = []; private _defaultSorting: Array; private _allowReleaseCallbacks: boolean; private _waitingToReload: boolean = false; private _cancelPagination: boolean = false; private _isMultipleEdition: boolean = false; private _fieldSourceValue: Map = new Map(); public metadataLoader?: (dataUnit: DataUnit) => Promise; public dataLoader?: (dataUnit: DataUnit, request: LoadDataRequest) => Promise; public saveLoader?: (dataUnit: DataUnit, changes: Array) => Promise>; public removeLoader?: (dataUnit: DataUnit, recordIds: Array) => Promise>; public recordLoader?: (dataUnit: DataUnit, recordIds: Array) => Promise>; public allRecordsLoader?: (dataUnit: DataUnit) => Array | undefined; constructor(name: string = DataUnit.DEFAULT_DATAUNIT_NAME, parentDataUnit?: DataUnit) { this._uuid = uuid() this._name = `${name}?uuid=${this._uuid}`; this._pageSize = 0; this._stateManager = new StateManager( [ HistReducer, LoadingPropertiesReducer, UnitMetadataReducer, LoadingControlReducer, RecordsReducer, RemovedRecordsReducer, AddedRecordsReducer, SelectionReducer, ChangesReducer, WaitingChangesReducer, InvalidFieldsReducer, SnapshotReducer ] ); this._observers = new Map(); this._filterProviders = new Map(); this._sortingProvider = undefined; this._defaultSorting = []; this._allowReleaseCallbacks = true; this._interceptors = new Map(); this._parentDataUnit = parentDataUnit; this._parentDataUnit?.subscribe(this.onDataUnitParentEvent); this._loadingLockers = []; DataUnitStorage.put(this); } /** * Desfaz vinculos do DataUnit. Chamado quando o DU não é mais necessário. */ public release() { DataUnitStorage.remove(this._name); if (this._parentDataUnit) { this._parentDataUnit.unsubscribe(this.onDataUnitParentEvent); this._parentDataUnit.removeChildDataunit(this._name); } this.releaseCallbacks(); } /** * Remove callbacks registrados internamente no dataunit, é util * para evitar memory leak em cenários onde o dataunit é reaproveitado em cache. * * Callbacks que são liberados: * - Filters Providers * - Interceptors * - Observers * - Sorting Providers */ public releaseCallbacks() { if (!this._allowReleaseCallbacks) return; this._observers = new Map(); this._filterProviders = new Map(); this._sortingProvider = undefined; this._interceptors = new Map(); } /** * Adiciona uma propriedade transacional que será envida aos * loaders (dataLoader, saveLoader, removeLoader e recordLoader) na chamada. * Essas propriedades serão limpas ao final da execução de cada método. * * @param name - Nome da propriedade * @param value - Valor da propriedade * */ public addGlobalLoaderProp(name: string, value: string): void { this.dispatchAction(Action.LOADING_PROPERTY_ADDED, { [name]: value }); } /** * Retorna as propriedades transacionais adicionados anteriores à chamada. * * @returns - Todas as propriedades desde o final do último loader. */ public getGlobalLoaderProps(): Map { return getLoadingProperties(this._stateManager) || new Map(); } public get dataUnitId(): string { return this._uuid; } /** * Retorna se o dataUnit está com recarregamento pendente. */ public isWaitingToReload(): boolean { return this._waitingToReload; } /** * Define se o dataUnit tem um recarregamento pendente. * @param isWaiting */ public setWaitingToReload(isWaiting: boolean) { this._waitingToReload = isWaiting; } /** * Informa se a paginação deve ser cancelada. */ public get cancelPagination(): boolean { return this._cancelPagination; } /** * Informa se a paginação deve ser cancelada. */ public set cancelPagination(cancelPagination: boolean) { this._cancelPagination = cancelPagination; } /** * Informa se o DataUnit está no modo de edição de múltiplos registros. */ public get isMultipleEdition(): boolean { return this._isMultipleEdition; } /** * Define se o DataUnit está no modo de edição de múltiplos registros. */ public set isMultipleEdition(isMultipleEdition) { this._isMultipleEdition = isMultipleEdition; this.dispatchAction(Action.MULTIPLE_EDITION_CHANGED, { isMultipleEdition }); } /** * * Obtém o nome de identificação do DataUnit (geralmente em formato de URI - Uniform Resource Identifier). * * @returns - Nome de identificação do DataUnit. * */ public get name(): string { return this._name; } /** * * Obtém o valor convertido de acordo com o tipo do campo. * * @param fieldName - Identificador do campo. * @param newValue - Novo valor que será atribuído ao campo pós validação. * * @returns - Novo valor convertido em um tipo valido. * */ private validateAndTypeValue(fieldName: string, newValue: any): any { const descriptor: FieldDescriptor | undefined = this.getField(fieldName); return descriptor ? convertType(descriptor.dataType, newValue) : newValue; } /** * * Trata as Actions do DataUnit Parent * * */ private onDataUnitParentEvent = (action: DataUnitAction) => { switch (action.type) { case Action.SELECTION_CHANGED: case Action.NEXT_SELECTED: case Action.PREVIOUS_SELECTED: case Action.DATA_LOADED: case Action.RECORDS_ADDED: case Action.EDITION_CANCELED: this.clearDataUnit(); const selectedRecord = this._parentDataUnit?.getSelectedRecord(); if (selectedRecord != undefined && !this.isNewRecord(selectedRecord.__record__id__)) { this.loadData(); } break; } } /** * * Obtém chave única para identificação do FilterProvider. * * @param provider - Interface FilterProvider na qual será retornada uma chave correspondente. * * @returns - A chave do provider. * */ private getFielterProviderKey(provider: FilterProvider): string { if (provider.getKey) { return provider.getKey(); } return StringUtils.hashCode(provider.getFilter.toString()); } /** * * Executa o carregamento dos registros. * * @param request - Dados da requisição para carregamento dos registros. * @param executionCtx - Contexto de execução do carregamento dos registros do DataUnit. * @param checkLastFilter - Habilita a verificação da última requisição, evitando carga desnecessária. * * @returns - Registros do DataUnit. * */ private executeLoadData(request: LoadDataRequest, executionCtx?: ExecutionContext, checkLastFilter?: boolean, selectFirstRecord?: boolean): Promise { if (checkLastFilter && this.isSameRequest(request)) { const paginationInfo = this.getPaginationInfo(); if (paginationInfo) { const response: LoadDataResponse = { records: getRecords(this._stateManager), paginationInfo }; if (selectFirstRecord) { this.requestSelectFirst(executionCtx); } return Promise.resolve(response); } } return new Promise(async (resolve, fail) => { if (await this.dispatchAction(Action.LOADING_DATA, request, executionCtx)) { if (this.dataLoader) { this.dataLoader(this, request).then( async response => { await this.dispatchAction( Action.DATA_LOADED, { ...response, keepSelection: request.keepSelection, filters: request.filters, selectFirstRecord }, executionCtx ); await this.dispatchAction(Action.LOADING_PROPERTIES_CLEANED); if (selectFirstRecord) { this.requestSelectFirst(executionCtx); } resolve(response); } ).catch(error => { console.error(error); const { errorCode } = error; this.dispatchAction(Action.LOADING_PROPERTIES_CLEANED); fail(new ErrorException("Erro ao carregar registros", error, errorCode)) }); } } }); } private requestSelectFirst(executionCtx?: ExecutionContext) { window.requestAnimationFrame(() => { this.selectFirst(executionCtx); }) } private isSameRequest(request: LoadDataRequest): boolean { const lastRequest = getCurrentRequest(this._stateManager); return ObjectUtils.objectToString(request) === ObjectUtils.objectToString(lastRequest || {}); } // Loaders /** * * Carrega os metadados do DataUnit. * * @param executionCtx - Contexto de execução do carregamento dos metadados do DataUnit. * * @returns - Metadados carregados. * */ public async loadMetadata(executionCtx?: ExecutionContext): Promise { if (await this.dispatchAction(Action.LOADING_METADATA, undefined, executionCtx)) { return new Promise((resolve, fail) => { if (this.metadataLoader) { this.metadataLoader(this).then( metadata => { this.metadata = metadata resolve(this.metadata); } ).catch(error => { const { errorCode } = error; fail(new ErrorException("Erro ao carregar metadados", error, errorCode)) }); } }); } } public async loadDataWithParams({ source, keepSelection, selectFirstRecord, executionCtx, checkLastFilter, quickFilter }: LoadDataParams): Promise { return await this.loadData(quickFilter, executionCtx, checkLastFilter, source, selectFirstRecord, keepSelection); } /** * * Carrega os registros do DataUnit. * * @param quickFilter - Filtros a serem aplicados. * @param executionCtx - Contexto de execução do carregamento dos registros. * @param checkLastFilter - Habilita a verificação da última requisição, evitando carga desnecessária. * * @returns - Registros requisitados. * */ public async loadData(quickFilter?: QuickFilter, executionCtx?: ExecutionContext, checkLastFilter?: boolean, source?: string, selectFirstRecord?: boolean, keepSelection?: boolean): Promise { await this.processLoadingLockers(); if (this._parentDataUnit) { const parentRecord = this._parentDataUnit.getSelectedRecord(); if (parentRecord == undefined || this._parentDataUnit.isNewRecord(parentRecord.__record__id__)) { if (this.records) { this.clearDataUnit(); } return Promise.resolve({ records: [] }); } } const loadDataRequest = this.getLoadDataRequest(quickFilter, source, keepSelection); return this.executeLoadData(loadDataRequest, executionCtx, checkLastFilter, selectFirstRecord); } /** * * Alterna entre os registros por número de página. * * @param page - Número da página desejada. * @param executionCtx - Contexto de execução do carregamento dos registros do DataUnit. * * @returns - Registros da página desejada. * */ public async gotoPage(page: number, executionCtx?: ExecutionContext): Promise { if (this._pageSize === 0) { return; } let request = getCurrentRequest(this._stateManager); if (!request) { request = { filters: this.getFilters(), sort: this.getSort() } } if (page >= 0 && page <= getLastPage(this._stateManager, this._pageSize)) { return this.executeLoadData({ ...request, limit: this._pageSize, offset: page * this._pageSize, keepSelection: true, source: DataUnit.CHANGING_PAGE_LOADING_SOURCE }, executionCtx); } } /** * * Vai para os registros da página seguinte. * * @param executionCtx - Contexto de execução do carregamento dos registros do DataUnit. * * @returns - Registros da página seguinte. * */ public async nextPage(executionCtx?: ExecutionContext): Promise { return this.gotoPage(getCurrentPage(this._stateManager) + 1, executionCtx); } /** * * Vai para os registros da página anterior. * * @param executionCtx - Contexto de execução do carregamento dos registros do DataUnit. * * @returns - Registros da página anterior. * */ public async previousPage(executionCtx?: ExecutionContext): Promise { return this.gotoPage(getCurrentPage(this._stateManager) - 1, executionCtx); } private getLoadDataRequest(quickFilter?: QuickFilter, source?: string, keepSelection?: boolean): LoadDataRequest { const request: LoadDataRequest = { filters: quickFilter?.filter ? [quickFilter.filter] : this.getFilters(), sort: this.getSort(), parentRecordId: this.getParentRecordId(), source, keepSelection }; if (quickFilter) { request.quickFilter = quickFilter; } if (this._pageSize > 0) { request.limit = this._pageSize request.offset = 0; } return request; } private async notifySavingData(executionCtx?: ExecutionContext): Promise { const notifyPromises: Array> = []; Array.from(this._childByName.values()).forEach( child => notifyPromises.push(child.notifySavingData(executionCtx)) ); if (isDirty(this._stateManager)) { notifyPromises.push(this.dispatchAction(Action.SAVING_DATA, undefined, executionCtx)); } return new Promise(accept => Promise.all(notifyPromises).then(responses => { accept(responses.reduce((previous, current) => previous && current, true)); })); } private getBeforeSavePromisses(): Array | undefined> { let promises = getWaitingChangePromisses(this._stateManager) || []; promises = promises.concat(this._savingLockers); return promises; } /** * * Salva o estado do registro do DataUnit. * * @param executionCtx - Contexto de execução da persistencia do registro do DataUnit. * * @returns - Resposta da solicitação. * */ public async saveData(executionCtx?: ExecutionContext): Promise { const blockingWaitingChanges = getBlockingWaitingChanges(this._stateManager); if (blockingWaitingChanges && blockingWaitingChanges.size > 0) { const entry = blockingWaitingChanges.entries().next().value; if (entry) { const [_, waitingChange] = entry; return Promise.reject(new WaitingChangeException("Aguardando alteração de campo", waitingChange.waitmessage)); } } else { if (this.isDirty()) { if (await this.notifySavingData(executionCtx)) { const promisses = this.getBeforeSavePromisses(); return new Promise((resolve, fail) => { Promise.all(promisses).then(() => { this._savingLockers = []; if (this.saveLoader) { const changes: Array = this.getAllChangesToSave(); this.saveLoader(this, changes).then((records) => { this.dispatchAction(Action.LOADING_PROPERTIES_CLEANED); const recordsByDataUnit = this.getRecordsByDataUnit(records); const dispatchPromisses = []; for (const [ownerDataUnitName, splittedRecords] of recordsByDataUnit) { const ownerDataUnit = DataUnitStorage.get(ownerDataUnitName); const changesByDataUnit = changes.filter(change => change.dataUnit === ownerDataUnitName); if (ownerDataUnit) { dispatchPromisses.push(ownerDataUnit.dispatchAction(Action.DATA_SAVED, { changes: changesByDataUnit, records: splittedRecords }, executionCtx)); } } Promise.all(dispatchPromisses).then(() => resolve()); }).catch(cause => { const { errorCode } = cause; this.dispatchAction(Action.SAVING_ERROR); this.dispatchAction(Action.LOADING_PROPERTIES_CLEANED); if (cause instanceof ServiceCanceledException) { console.debug("Service canceled: " + cause.message); resolve(); } if (cause instanceof SilentException) { fail(cause); return; } fail(new ErrorException("Erro ao salvar alterações", cause, errorCode)); }); } else { this.dispatchAction(Action.LOADING_PROPERTIES_CLEANED); resolve(); } }); }); } } } return Promise.resolve(); } /** * * Retorna as alterações a serem salvas no DataUnit atual. * * * * @returns - Mudanças realizadas no DataUnit atual * */ public buildChangesToSave(): Array { return getChangesToSave(this._name, this._stateManager); } public buildChangesToSaveFromChild(allChanges: Array, dataUnit: DataUnit): void { dataUnit._childByName.forEach((dataUnitChild: DataUnit) => { if (dataUnitChild.isDirty()) { allChanges.push(...getChangesToSave(dataUnitChild.name, dataUnitChild._stateManager)); this.buildChangesToSaveFromChild(allChanges, dataUnitChild); } }); } /** * * Retorna todas as alterações do DataUnit a serem salvas, incluindo no nível Master e Detail. * * * * @returns - Todas as mudanças realizadas no DataUnit, tanto Master quanto Detail; * */ public getAllChangesToSave(): Array { const allChanges: Array = []; allChanges.push(...this.buildChangesToSave()); this.buildChangesToSaveFromChild(allChanges, this); return allChanges; } /** * * Remove o registro selecionado. * * @param buffered - Se será utilizado buffer na solicitação. * @param silent - Define se haverá mensagem de confirmação da remoção * * @returns - ID's dos registros removidos. * */ public async removeSelectedRecords(buffered: boolean = false, silent: boolean = false): Promise> { const selection = this.getSelectionInfo(); if (selection.isAllRecords()) { throw new Error("Exclusão remota não implementada."); } if (!selection.records || !selection.records.length) { return Promise.resolve([]); } const records = selection.records; const recordIds = selection.recordIds as Array; return this.removeRecords(recordIds, records, buffered, undefined, silent); } /** * * Efetua requisição para remoção dos registros. * * @param recordIds - Lista de IDs dos registros que serão removidos. * @param cachedRecords - Dados dos registros que serão removidos. * @param buffered - Se será utilizado buffer na solicitação. * @param executionCtx - Contexto de execução da remoção do registro do DataUnit. * @param silent - Define se haverá mensagem de confirmação da remoção * * @returns - ID's dos registros removidos. * */ public async removeRecords(recordIds: Array, cachedRecords: Array, buffered: boolean = false, executionCtx?: ExecutionContext, silent: boolean = false): Promise> { if (recordIds) { if (buffered || !this.removeLoader) { this.dispatchAction(Action.LOADING_PROPERTIES_CLEANED); this.dispatchAction(Action.RECORDS_REMOVED, { records: recordIds, cachedRecords, buffered: true }, executionCtx); } else { if (await this.dispatchAction(Action.REMOVING_RECORDS, { silent }, executionCtx)) { return new Promise((resolve, fail) => { if (this.removeLoader) { this.removeLoader(this, recordIds).then( removedIds => { const currentRecordsKeys = Array.from(getCurrentRecords(this._stateManager).keys()); const removedIndex: Array = recordIds .map(id => currentRecordsKeys.indexOf(id)) .filter(index => index > -1) .sort((a, b) => a - b); const nextIndex = Math.min(removedIndex.slice(-1)[0] + 1, currentRecordsKeys.length); const selectionAfterRemove = currentRecordsKeys[nextIndex] ? [currentRecordsKeys[nextIndex]] : []; this.dispatchAction(Action.RECORDS_REMOVED, { records: removedIds, cachedRecords, removedIndex, buffered: false, selectionAfterRemove }, executionCtx); this.dispatchAction(Action.LOADING_PROPERTIES_CLEANED); resolve(removedIds); } ).catch(error => { this.dispatchAction(Action.LOADING_PROPERTIES_CLEANED); const { errorCode } = error; fail(new ErrorException("Erro ao remover registros", error, errorCode)) }); } }); } } } return Promise.resolve(recordIds); } // API /** * * Obtém o valor do campo em seu formato/tipo correto a partir de uma string. * * @param fieldName - Nome do campo que terá o tipo identificado para conversão. * @param value - Texto que será convertido de acordo com o tipo identificado no campo. * * @returns - Valor convertido ou ele mesmo. * */ public valueFromString(fieldName: string, value: string): any { const descriptor = this.getField(fieldName); return descriptor ? convertType(descriptor.dataType, value) : value; } /** * * Converte o valor informado para texto de acordo com o tipo do campo informado. * * @param fieldName - Nome do campo utilizado para buscar o tipo de dado com o padrão de conversão para string. * @param value - Valor a ser convertido. * * @returns - Valor informado convertido. * */ public valueToString(fieldName: string, value: any): string { const descriptor = this.getField(fieldName); return toString(descriptor?.dataType, value); } /** * * Formata o valor do campo considerando as informações do descriptor. * Diferente do método "valueToString" que retorna o dado em valor textual, * getFormattedValue retorna uma informação amigável ao usuário, geralmente * usada na interface. * * @param fieldName - Nome do campo utilizado do qual se quer obter valor. * @param value (opcional) - O valor a ser convertido. Caso omitido pega do registro selecionado. * * @returns - Valor formatado. * */ public getFormattedValue(fieldName: string, value?: any): string { const descriptor = this.getField(fieldName); if (value == undefined) { value = this.getFieldValue(fieldName); } else if (typeof value === "string" && descriptor?.dataType != DataType.TEXT) { value = this.valueFromString(fieldName, value); } if (value == undefined) { return ""; } return getFormattedValue(value, descriptor); } /** * * Adiciona um interceptor correspondente a uma ação do DataUnit para fazer um processamento customizado. * * @param interceptor - Interceptor a ser adicionado. * */ public addInterceptor(interceptor: DUActionInterceptor): void { this._interceptors.set(interceptor.interceptAction.toString(), interceptor); } /** * * Remove um interceptor da lista de interceptors. * * @param interceptor - Interceptor a ser removido. * */ public removeInterceptor(interceptor: DUActionInterceptor) { this._interceptors.delete(interceptor.interceptAction.toString()) } /** * * Adiciona um FilterProvider. * * @param provider - FilterProvider que será adicionado. * */ public addFilterProvider(provider: FilterProvider): void { this._filterProviders.set(this.getFielterProviderKey(provider), provider); } /** * * Remove um FilterProvider. * * @param provider - FilterProvider que será removido. * */ public removeFilterProvider(provider: FilterProvider): boolean { return this._filterProviders.delete(this.getFielterProviderKey(provider)); } /** * * Obtém informações de paginação dos registros. * * @returns - Informações da paginação de registros. */ public getPaginationInfo(): PaginationInfo | void { return getPaginationInfo(this._stateManager); } /** * * Obtém informações de paginação dos registros. * * @returns - Informações da paginação de registros. */ public updatePagination(info: PaginationInfo) { return this.dispatchAction(Action.PAGINATION_UPDATED, info, undefined); } /** * * Define a lógica de ordenação dos registros. * * @param provider - Objeto usado para definir a propriedade sortingProvider da instância da classe. * */ public set sortingProvider(provider: SortingProvider) { this._sortingProvider = provider; } /** * * Define a ordenação padrão. * * @param sorting - Ordenação padrão. * */ public set defaultSorting(sorting: Array) { this._defaultSorting = sorting; } /** * * Define a propriedade metadata da instância da classe com um novo valor e chama o método dispatchAction para notificar os observers da aplicação sobre a mudança. * */ public set metadata(md: UnitMetadata) { this.dispatchAction(Action.METADATA_LOADED, md, undefined); } /** * * Obtém os metadados do DataUnit. * */ public get metadata(): UnitMetadata { return getMetadata(this._stateManager); } /** * * Obtém informações da ligação para um DataUnit filho. * * @param name - Nome do DataUnit que se deseja. * * @returns - As informações sobre a ligação solicitada. Pode retornar undefined. * */ public getChildInfo(name: string): ChildDescriptor | undefined { const children = this.metadata?.children; if (children == undefined) { return; } return children.find(child => child.name === name); } /** * * Define a propriedade records da instância da classe com um novo valor e chama o método dispatchAction para notificar os observers da aplicação sobre a mudança, * mantendo a seleção definida anteriormente. * */ public setRecordsKeepingSelection(records: Array): void { const paginationInfo = this.getPaginationInfo(); this.dispatchAction(Action.DATA_LOADED, { records, paginationInfo, keepSelection: true }, undefined); } /** * * Define a propriedade records da instância da classe com um novo valor e chama o método dispatchAction para notificar os observers da aplicação sobre a mudança. * */ public set records(records: Array) { const paginationInfo = this.getPaginationInfo(); this.dispatchAction(Action.DATA_LOADED, { records, paginationInfo }, undefined); } /** * * Obtém todos os registros atuais. * * @returns - Todos os registros atuais. * */ public get records(): Array { const records = getCurrentRecords(this._stateManager); return records ? Array.from(records.values()) : []; } /** * * Define a quantidade de registros que será exibido por página. * * @param size - Quantidade de registros que será exibido por página. * */ public set pageSize(size: number) { this._pageSize = size; } /** * * Obtém a quantidade de registros que está sendo exibido por página. * * @returns - Quantidade de registros exibidos por página. * */ public get pageSize(): number { return this._pageSize; } /** * * Obtém os registros modificados e ainda não salvos no DataUnit. * * @returns - Lista dos registros em edição. * */ public getModifiedRecords(): Array { const modified = getModifiedRecords(this._stateManager); return modified || []; } /** * * Obtém os registros adicionados no DataUnit. * * @returns - Lista dos registros adicionados. * */ public getAddedRecords(): Array { return getAddedRecords(this._stateManager); } /** * * Obtém metadados de um campo específico. * * @param fieldName - Identificador do campo. * * @returns - Metadados do campo informado. */ public getField(fieldName: string): FieldDescriptor | undefined { return getField(this._stateManager, fieldName); } /** * * Adiciona um novo registro. * * @param executionCtx - Contexto de execução da inserção do dado no DataUnit. * */ public async addRecord(executionCtx?: ExecutionContext): Promise { return this.dispatchAction(Action.RECORDS_ADDED, prepareAddedRecordId(this._stateManager, [{}], this.getParentRecordId()), executionCtx); } /** * * Efetua a cópia do registro selecionado. * * @param executionCtx - Contexto de execução da cópia do dado do DataUnit. * */ public copySelected(executionCtx?: ExecutionContext): void { const selectionInfo = this.getSelectionInfo(); if (selectionInfo.isAllRecords()) { throw new Error("Erro interno: Impossível copiar os registros selecionados pois a seleção atual é virtual."); } const selectedRecords = selectionInfo.records; if (selectedRecords.length) { this.dispatchAction(Action.RECORDS_COPIED, prepareCopiedRecord(this._stateManager, selectedRecords, this.getParentRecordId()), executionCtx); } } private getRecordsByDataUnit(records: Array): Map> { const recordsMap = new Map>(); records.forEach(record => { let { __owner__dataunit__name__: ownerDataUnitName } = record; if (!ownerDataUnitName) { ownerDataUnitName = this.name; } let changes = recordsMap.get(ownerDataUnitName); if (!changes) { changes = []; recordsMap.set(ownerDataUnitName, changes); } changes.push(record); }); return recordsMap; } private getParentRecordId() { const parentRecord = this._parentDataUnit?.getSelectedRecord(); return parentRecord?.__record__id__; } /** * * Retorna se a alteração no campo já foi concluída ou se ainda está incompleta. * * @param fieldName - Identificador do campo a ser verificado. * * @returns - Verdadeiro se ainda está pendente. * */ public waitingForChange(fieldName: string): boolean { return isWaiting(this._stateManager, fieldName); } /** * * Obtém valor do campo desejado. * * @param fieldName - Identificador do campo a ser buscado. * * @returns - Valor do campo. * */ public getFieldValue(fieldName: string): any { return getFieldValue(this._stateManager, fieldName); } /** * * Insere valor no campo desejado. * * @param fieldName -Identificador do campo a ser modificado. * @param newValue - Valor a ser inserido no campo. * @param records - Indica quais registros foram afetados pela alteração no valor do campo. * @param options - Configurações do evento * @returns - Promise que será resolvida quando o novo valor for persistido no state. * */ public async setFieldValue(fieldName: string, newValue: any, records?: Array, options?: DataUnitEventOptions): Promise { const noRecordSelected = !this.hasNewRecord() && !this.getSelectedRecord(); const suppressCreateNewRecord = options?.suppressCreateNewRecord; if (noRecordSelected && suppressCreateNewRecord) return Promise.resolve(false); if (noRecordSelected) await this.addRecord(); const typedValue = this.validateAndTypeValue(fieldName, newValue); const currentValue = records?.length === 1 ? (this.records.find(r => r.__record__id__ === records[0])?.[fieldName] ?? this.getFieldValue(fieldName)) : this.getFieldValue(fieldName); if (this.areEquivalentValues(newValue, currentValue, typedValue)) { return Promise.resolve(false); } if (newValue instanceof Promise) { const promise: Promise = new Promise(accept => { newValue.then(resolvedValue => { this.dispatchAction(Action.DATA_RESOLVED, { [fieldName]: resolvedValue, records }, undefined); accept(this.setFieldValue(fieldName, resolvedValue, records, options)); }); }); this._savingLockers.push(promise); return promise; } if (this.isMultipleEdition || currentValue !== typedValue) { const promise = this.dispatchAction(Action.DATA_CHANGED, { [fieldName]: typedValue, records }, undefined, options); this._savingLockers.push(promise); return promise; } return Promise.resolve(false); } private areEquivalentValues(newValue: any, currentValue: any, typedValue: any) { return !(newValue instanceof Promise) && ObjectUtils.hasEquivalentProps(currentValue, typedValue) && !this.isMultipleEdition; } /** * * Marca campos como inválidos. * * @param fieldName - Nome do campo inválido. * @param message - Mensagem descrevendo o motivo da invalidade. * @param recordId - Indica qual registro está com o campo inválido. * */ public setInvalidField(fieldName: string, message: string, recordId: string): void { this.dispatchAction(Action.FIELD_INVALIDATED, { fieldName, message, recordId }, undefined); } /** * * Cancela o saving exibindo os campos invalidos. * * @param filds - Lista dos campos * @param recordId - Indica qual registro está com os campos inválido. * */ public savingCanceled(fields: Array<{ name: string, message: string }>, recordId: string): void { this.dispatchAction(Action.SAVING_CANCELED, { fields, recordId }, undefined); } /** * * Limpa campos inválidos. * * @param recordId - Indica em qual registro o campo não está mais inválido. * @param fieldName - Nome do campo. Caso omitido, todos os campos serão limpos. * */ public clearInvalid(recordId: string, fieldName?: string): void { this.dispatchAction(Action.INVALIDATE_CLEAN, { fieldName, recordId }, undefined); } /** * * Obtém a mensagem de campo inválido para determinado registro. * * @param recordId - Identificador do registro. * @param fieldName - Nome do campo. * */ public getInvalidMessage(recordId: string, fieldName: string): string | undefined { return getInvalidFieldMessage(this._stateManager, fieldName, recordId); } /** * * Inicia alteração no campo. * * @param fieldName - Identificador do campo a ser modificado. * @param waitingChange - Informa que uma mudança irá iniciar. * */ public startChange(fieldName: string, waitingChange: WaitingChange): void { this.dispatchAction(Action.CHANGING_DATA, { [fieldName]: waitingChange }, undefined); } /** * * Cancela o início de uma alteração no campo. * * @param fieldName - Identificador do campo. * */ public cancelWaitingChange(fieldName: string): void { this.dispatchAction(Action.WAITING_CHANGE_CANCELED, { fieldName }, undefined); } /** * Retorna se existe alterações pendentes. * @returns Verdadeiro se existir pendências de modificações. */ public hasWaitingChanges(): boolean { const waitingChanges = getWaitingChanges(this._stateManager); return waitingChanges ? !!waitingChanges.size : false; } /** * * Seleciona o primeiro registro. * * @param executionCtx - Contexto de execução da seleção do registro do DataUnit. * */ public selectFirst(executionCtx?: ExecutionContext): void { if (this.records.length > 0) { this.setSelectionByIndex([0], executionCtx); } } /** * * Seleciona o último registro. * * @param executionCtx - Contexto de execução da seleção do registro do DataUnit. * */ public selectLast(executionCtx?: ExecutionContext): void { if (this.records.length > 0) { this.setSelectionByIndex([this.records.length - 1], executionCtx); } } /** * * Seleciona múltiplos registros por índice. * * @param selection - Índices desejados para a seleção. * @param executionCtx - Contexto de execução da seleção do registro do DataUnit. * */ public setSelectionByIndex(selection: Array, executionCtx?: ExecutionContext): void { this.dispatchAction(Action.SELECTION_CHANGED, { type: "index", selection }, executionCtx); } /** * * Seleciona múltiplos registros por ID ou todos os registros (multipágina) . * * @param selection - IDs para selecionar ou o modo de seleção completo. * @param executionCtx - Contexto de execução da seleção dos registros do DataUnit. * * */ public setSelection(selection: Array | SelectionMode.ALL_RECORDS, executionCtx?: ExecutionContext): Promise { return new Promise(resolve => { this.dispatchAction(Action.SELECTION_CHANGED, { type: "id", selection }, executionCtx) .then(() => resolve(this.getSelectionInfo())); }); } /** * * Limpa a seleção. * * @param executionCtx - Contexto de execução da seleção dos registros do DataUnit. * */ public clearSelection(executionCtx?: ExecutionContext) { this.setSelection([], executionCtx); } /** * * Atualiza a seleção dos registros atuais. * * @param selection - IDs dos registros selecionados no snapshot atual * @param executionCtx - Contexto de execução da seleção dos registros do DataUnit. * @returns - Informações sobre a seleção. */ public updatePageSelection(selection?: Array, executionCtx?: ExecutionContext): Promise { if (!selection) return Promise.resolve(this.getSelectionInfo()); return new Promise(resolve => { const newSelection: Set = new Set(selection); this.dispatchAction(Action.SELECTION_CHANGED, { type: "id", selection: Array.from(newSelection) }, executionCtx) .then(() => resolve(this.getSelectionInfo())); }); } private updatePageSelectionAll(addRecords: boolean): Promise { return new Promise(resolve => { const newSelection: Set = new Set(); const cachedSelection = getSelection(this._stateManager); cachedSelection.forEach(item => newSelection.add(item)); const recordsIds = this.records.map(r => r.__record__id__); recordsIds.forEach(id => addRecords ? newSelection.add(id) : newSelection.delete(id)); this.dispatchAction(Action.SELECTION_CHANGED, { type: "id", selection: Array.from(newSelection) }) .then(() => resolve(this.getSelectionInfo())); }); } /** * Seleciona todos os registros da página. * * @returns - Informações sobre a seleção. */ public selectAllRecords(): Promise { return this.updatePageSelectionAll(true); } /** * Desseleciona todos os registros da página. * * @returns - Informações sobre a seleção. */ public unSelectAllRecords(): Promise { return this.updatePageSelectionAll(false); } /** * * Obtém informações sobre a seleção atual. * * @returns - Objeto com informações como registros selecionados e seleção por critério. * **/ public getSelectionInfo(): SelectionInfo { const selectionInfo: SelectionInfo = getSelectionInfo(this._stateManager); selectionInfo.getAllRecords = () => { let records = this.allRecordsLoader?.(this) ?? []; if (selectionInfo.sort != undefined && selectionInfo.sort.length > 0) { const sortingFunction = SortingUtils.getSortingFunction(this, selectionInfo.sort) records = [...records].sort(sortingFunction); } return records; } if (selectionInfo.sort != undefined && selectionInfo.sort.length > 0) { const sortingFunction = SortingUtils.getSortingFunction(this, selectionInfo.sort) selectionInfo.records.sort(sortingFunction) } return selectionInfo; } /** * * Retorna apenas um registro selecionado no Dataunit * * @returns - Registro selecionado. * */ public getSelectedRecord(): Record | undefined { return getSelectedRecord(this._stateManager); } /** * Retorna o DataUnit pai * * @returns DataUnit pai ou undefined */ public getParentDataUnit(): DataUnit | undefined { return this._parentDataUnit; } /** * * Limpa todos os registros do DataUnit * * */ public clearDataUnit(): void { this.records = []; } /** * * Seleciona o próximo registro. * * @param executionCtx - Contexto de execução da seleção do registro do DataUnit. * * */ public async nextRecord(executionCtx?: ExecutionContext): Promise { if (!hasNext(this._stateManager)) { if (hasMorePages(this._stateManager)) { await this.nextPage({ before: (act) => { if (executionCtx && executionCtx.before) { act = executionCtx.before(act); } return act; }, after: () => { this.selectFirst(executionCtx); } }); } } else { this.dispatchAction(Action.NEXT_SELECTED, undefined, executionCtx); } } /** * * Seleciona o registro anterior. * * @param executionCtx - Contexto de execução da seleção do registro do DataUnit. * */ public async previousRecord(executionCtx?: ExecutionContext): Promise { if (!hasPrevious(this._stateManager)) { if (hasPreviousPages(this._stateManager)) { await this.previousPage({ before: (act) => { if (executionCtx && executionCtx.before) { act = executionCtx.before(act); } return act; }, after: () => { this.selectLast(executionCtx); } }); } } else { this.dispatchAction(Action.PREVIOUS_SELECTED, undefined, executionCtx); } } /** * * Cancela edição do registro atual. * * @param executionCtx - Contexto de execução do cancelamento da seleção dos registros. * @param silent: Define se haverá mensagem de confirmação do cancelamento * */ public async cancelEdition(executionCtx?: ExecutionContext, fromParent?: boolean, silent: boolean = false): Promise { const cancelledAction = await this.dispatchAction(Action.EDITION_CANCELED, { fromParent, silent }, executionCtx); for (const [, dataUnit] of this._childByName) { if (dataUnit.isDirty()) { dataUnit.cancelEdition(undefined, true, silent); } } return Promise.resolve(cancelledAction); } /** * * Retorna se existe algum tipo de alteração pendente. * * @param ignoreChildren: Define se deverá ignorar alterações pendentes no DataUnit filho. * @returns Verdadeiro se existir alterações pendentes. * */ public isDirty(ignoreChildren?: boolean): boolean { if (ignoreChildren) return isDirty(this._stateManager); return isDirty(this._stateManager) || this.childrenIsDirty(); } /** * * Retorna se existe alterações pendentes no DataUnit pai. * * @returns Verdadeiro se existir alterações pendentes e Falso caso não exista alterações ou não exista DataUnit pai. * */ public isParentDirty(): boolean { return this._parentDataUnit ? this._parentDataUnit.isDirty(true) : false; } /** * * Retorna se existe algum DataUnit detail com alterações pendentes. * * @returns Verdadeiro se existir alterações pendentes em algum DataUnit detail. * */ private childrenIsDirty(): boolean { for (let [_, dataUnitChild] of this._childByName) { if (dataUnitChild.isDirty()) return true; } return false; } /** * * Retorna se existe algum registro em modo de edição. * * @returns Verdadeiro se existir alterações de registros pendentes. * */ public hasDirtyRecords(): boolean { return hasDirtyRecords(this._stateManager); } /** * * Retorna se existir uma pagina seguinte a atual na paginação. * * @returns Verdadeiro se existir uma próxima página. * */ public hasNext(): boolean { let result = hasNext(this._stateManager); if (!result) { result = hasMorePages(this._stateManager); } return result; } /** * * Retorna se existe uma página anterior a atual na paginação. * * @returns Verdadeiro se existir uma página anterior. * */ public hasPrevious(): boolean { let result = hasPrevious(this._stateManager); if (!result) { result = hasPreviousPages(this._stateManager); } return result; } /** * * Verifica se um registro é proveniente de inclusão. * * @param recordId - O id do registro a ser verificado. * @returns Verdadeiro se o id solicitado é de um registro novo. * */ public isNewRecord(recordId: string): boolean { const newRecords = getAddedRecords(this._stateManager); if (newRecords && newRecords.length > 0) { const record = newRecords.find(newRecord => newRecord.__record__id__ === recordId); return record != undefined; } return false; } /** * * Retorna se existe pelo menos um registro novo. * * @returns Verdadeiro se algum registro foi adicionado. * */ public hasNewRecord(): boolean { const newRecords = getAddedRecords(this._stateManager); return newRecords && newRecords.length > 0; } /** * * Retorna se existe pelo menos um registro novo. * * @returns Verdadeiro se algum registro foi adicionado. * */ public hasCopiedRecord(): boolean { const newRecords = getAddedRecords(this._stateManager); if (newRecords == undefined) { return false; } for (let record of newRecords) { if (record['__record__source__id__'] != undefined) { return true } } return false; } /** * * Retorna se a informação do estado anterior está salva, permitindo desfazer a ação. * * @returns Verdadeiro se for possível desfazer a ação. * */ public canUndo(): boolean { return canUndo(this._stateManager); } /** * * Retorna se a informação do estado futuro está salva, permitindo refazer a ação. * * @returns Verdadeiro se for possível refazer a ação. * */ public canRedo(): boolean { return canRedo(this._stateManager); } /** * * Desfaz a última ação. * * @param executionCtx - Contexto de execução de desfazer a última ação. * * */ public undo(executionCtx?: ExecutionContext) { this.dispatchAction(Action.CHANGE_UNDONE, undefined, executionCtx); } /** * * Refaz a última ação. * * @param executionCtx - Contexto de execução de refazer a última ação. * */ public redo(executionCtx?: ExecutionContext) { this.dispatchAction(Action.CHANGE_REDONE, undefined, executionCtx); } /** * * Obtém a representação textual do DataUnit, nesse caso, o nome do DataUnit. * * @returns - Valor contido na propriedade name. * */ public toString() { return this.name; } // Actions / State manager /** * * Lança ação do DataUnit para que sejam processadas. * * @param actionType - Tipo de ação que será executada. * @param payload - Dados que serão processados na ação. * @param executionCtx - Contexto de execução de lançar a ação que será executada. * * @returns - Verdadeiro se ação iniciada. * */ private async dispatchAction(actionType: Action, payload?: any, executionCtx?: ExecutionContext, options?: DataUnitEventOptions): Promise { return new Promise(async resolve => { let action = new DataUnitAction(actionType, payload); if (executionCtx && executionCtx.before) { action = executionCtx.before(action); } if (action && this._interceptors && this._interceptors.size > 0) { action = await this.intercept(action, this._interceptors.values()); } if (action) { this.doDispatchAction(action, options); if (executionCtx && executionCtx.after) { executionCtx.after(action) } resolve(true); } else { resolve(false); } }); } /** * * Notifica os interceptors de que uma ação foi executada, para que cada interceptor possa fazer sua respectiva tratativa dos dados. * * @param action - Ação que foi executada. * @param interceptors - Interceptors que serão notificados. * * @returns - Ação executada no DataUnit. * */ private async intercept(action: DataUnitAction, interceptors: IterableIterator): Promise { return new Promise(async resolve => { let ite; while (action && !(ite = interceptors.next()).done) { action = await ite.value.interceptAction(action); } resolve(action); }); } /** * * Processa as ações no DataUnit e notifica os observers. * * @param action - Ações em execução no DataUnit. * */ private doDispatchAction(action: DataUnitAction, options: DataUnitEventOptions = {}): void { this._stateManager.process(action); this?._parentDataUnit?.dispatchAction(Action.CHILD_CHANGED, { srcAction: action, srcDataUnit: this }); this._observers.forEach(f => { //if some observer throws exceptions, should be continued try { f(action, options); } catch (e) { console.warn("[DataUnit] error while call observer", e); } }); } /** * * Cria um dataunit filho. * * @param name - Nome do dataunit filho. * */ public getChildDataunit(name: string) { const detail: DataUnit = new DataUnit(name, this); this._childByName.set(name, detail); return detail; } /** * * Remove um dataunit filho. * * @param name - Nome do dataunit filho. * */ public removeChildDataunit(name: string) { this._childByName.delete(name); } /** * * Adiciona um novo observer no DataUnit. * Ela vai ser chamada sempre que uma ação for despachada (dispatchAction()). * * @param observer - Função que recebe como parâmetro as ações que serão monitoradas. * @param uuid - Identificador do observer. Quando não informado, será gerado um identificador aleatório. */ public subscribe(observer: (action: DataUnitAction, options?: DataUnitEventOptions) => void | Promise, uuid?: string): string { if (uuid) { this._observers.set(uuid, observer); } else { uuid = StringUtils.generateUUID(); this._observers.set(uuid, observer); } return uuid; } /** * * Remove um observer existente. * * @param observer - Observer que se deseja remover. * @param uuid - Identificador do observer. Quando não informado o delete removera com base no equals do observer. */ public unsubscribe(observer: Function, uuid?: string) { if (uuid) { this._observers.delete(uuid); } else { this._observers.forEach((valor, chave) => { if (valor == observer) { this._observers.delete(chave); } }); } } /** * * Recarrega registro selecionado com dados atualizados do servidor. * * @returns - Dados atualizados do registro selecionado. * */ public reloadCurrentRecord(): Promise> { return new Promise(async (resolve, fail) => { const selection = getSelection(this._stateManager); await this.dispatchAction(Action.LOADING_RECORD, selection); if (!this.recordLoader || !this.dataLoader) { await this.dispatchAction(Action.LOADING_PROPERTIES_CLEANED); resolve([]); return; } this.recordLoader(this, selection).then(async response => { await this.dispatchAction(Action.RECORD_LOADED, response) await this.dispatchAction(Action.LOADING_PROPERTIES_CLEANED); resolve(response); }).catch(async cause => { await this.dispatchAction(Action.LOADING_PROPERTIES_CLEANED); const { errorCode } = cause; fail(new ErrorException("Erro ao recarregar registro", cause, errorCode)); }); }); } /** * * Obtém a estrutura de ordenação das colunas dos dados. * * @returns - Lista dos ordenáveis por prioridade. * */ public getSort(): Array | undefined { return this._sortingProvider ? this._sortingProvider.getSort(this._name, this._defaultSorting) : this._defaultSorting; } /** * * Obtém todos os filtros de dados. * * @returns - Lista de filtros. * */ public getFilters(): Array | undefined { let filters: Array | undefined = undefined; this._filterProviders.forEach(p => { const f = p.getFilter(this.name); if (f) { filters = (filters || []).concat(f); } }); return filters; } /** * * Obtém as informações da última carga de dados. * * @returns - As informações de filtro e paginação. * */ public getLastLoadRequest(): LoadDataRequest | undefined { return getCurrentRequest(this._stateManager); } /** * * Obtém os filtros aplicados. * * @returns - Lista de filtros. * */ public getAppliedFilters(): Array | undefined { const { filters }: LoadDataRequest = getCurrentRequest(this._stateManager) || {}; return filters } /** * * Habilita um campo do DataUnit * @param fieldName - nome do campo para ser habilitado. * */ public enableField(fieldName: string) { const fieldDescriptor = this.getField(fieldName); if (!fieldDescriptor) { return; } if (fieldDescriptor.readOnly) { fieldDescriptor.readOnly = false; this.metadata = { ...this.metadata }; } } /** * * Desabilita um campo do DataUnit * @param fieldName - nome do campo para ficar desabilitado. * */ public disableField(fieldName: string) { const fieldDescriptor = this.getField(fieldName); if (!fieldDescriptor) { return; } if (!fieldDescriptor.readOnly) { fieldDescriptor.readOnly = true; this.metadata = { ...this.metadata }; } } /** * * Deixa um campo do DataUnit invisível * @param fieldName - nome do campo para ficar invisível. * */ public hideField(fieldName: string, options?: HideFieldOptions) { const fieldDescriptor = this.getField(fieldName); if (!fieldDescriptor) { return; } if (fieldDescriptor.visible) { fieldDescriptor.visible = false; if (options) { fieldDescriptor.properties = { ...fieldDescriptor.properties, visibleOnConfig: options.visibleOnConfig }; } this.metadata = { ...this.metadata }; } } /** * * Deixa um campo do DataUnit visível * @param fieldName - nome do campo para ficar visível. * */ public showField(fieldName: string) { const fieldDescriptor = this.getField(fieldName); if (!fieldDescriptor) { return; } if (!fieldDescriptor.visible) { fieldDescriptor.visible = true; this.metadata = { ...this.metadata }; } } /** * * Obtém todos os registros selecionados. * * @returns - Lista de registros selecionados. * * @deprecated - Utilize o método `getSelectionInfo()` para obter os registros selecionados. * Devido a seleção virtual baseada em critérios e ordenação (ALL_RECORDS), esse * método foi descontinuado e pode retornar erros no caso da seleção virtual. * */ public getSelectedRecords(): Array | undefined { console.warn("DataUnit: O método `getSelectedRecords` foi descontinuado. Use o método `getSelectionInfo`."); const selection = this.getSelectionInfo(); if (selection != undefined && selection.isAllRecords()) { throw new Error("Erro interno: Impossível retornar os registros selecionados. A seleção atual é virtual. Use o método `getSelectionInfo`."); } if (selection) { return selection.records as Array; } } /** * * Obtém ids dos registros selecionados. * * @returns - Lista com id de todos os registros selecionados. * * @deprecated - Utilize o método `getSelectionInfo()` para obter os registros selecionados. * Devido a seleção virtual baseada em critérios e ordenação (ALL_RECORDS), esse * método foi descontinuado e pode retornar erros no caso da seleção virtual. * */ public getSelection(): Array { console.warn("DataUnit: O método `getSelection` foi descontinuado. Use o método `getSelectionInfo`."); const selection = this.getSelectionInfo(); if (selection == undefined) { return []; } if (selection.isAllRecords()) { throw new Error("Erro interno: Impossível retornar os registros selecionados. A seleção atual é virtual. Use o método `getSelectionInfo`."); } return selection?.recordIds as Array; } /** * Adiciona um campo stand-alone ao dataUnit. * * @deprecated - metodo depreciado, utilizar o metodo addField */ public addStandAloneField(): void { console.warn("metodo depreciado, para adicionar um campo standAlone, utilizar o metodo addField") } /** * Adiciona um campo stand-alone ao dataUnit. * * @param field - Campo a ser adicionado. */ public addField(field: Omit): void { const standAloneField = { ...field, standAlone: true }; this.metadata = { ...this.metadata, fields: [...this.metadata.fields, standAloneField] }; } /** * Adiciona um locker para impedir o carregamento dos registros do dataUnit. * @returns Retorna uma função responsável por liberar o lock adicionado. */ public addLoadingLocker(): Function { let loadingLockerResolver = () => {}; this._loadingLockers.push(new Promise((resolve) => { loadingLockerResolver = resolve; })); return loadingLockerResolver; } public set allowReleaseCallbacks(allow: boolean) { this._allowReleaseCallbacks = allow; } /** * Adiciona um mapeamento de origem dos dados de um determinado campo */ public addSourceFieldValue(sourceFieldName: string, targetFieldName: string): void { this._fieldSourceValue.set(sourceFieldName, targetFieldName); } /** * Retornar o campo de origem dos dados caso exista mapeamento */ public getSourceFieldValue(sourceFieldName: string): string | undefined { return this._fieldSourceValue.get(sourceFieldName); } private async processLoadingLockers() { if (this._loadingLockers.length) { await Promise.all(this._loadingLockers); this._loadingLockers = []; } } } export { Record, Change, SelectionInfo, SelectionMode, LoadDataParams, DUActionInterceptor, SavedRecord, WaitingChange, QuickFilter, DataUnitEventOptions, HideFieldOptions }