UNPKG

devexpress-reporting

Version:

DevExpress Reporting provides the capability to develop a reporting application to create and customize reports.

210 lines (209 loc) 10.4 kB
/** * DevExpress HTML/JS Reporting (designer\internal\fieldlist\_fieldListDataSourcesHelper.js) * Version: 25.1.3 * Build date: Jun 26, 2025 * Copyright (c) 2012 - 2025 Developer Express Inc. ALL RIGHTS RESERVED * License: https://www.devexpress.com/Support/EULAs/universal.xml */ import { createGlobalModuleVariableFunc, DxDeferred, extend, findFirstItemMatchesCondition } from '@devexpress/analytics-core/analytics-internal'; import { ModelSerializer, PathRequest } from '@devexpress/analytics-core/analytics-utils'; import * as ko from 'knockout'; import { ComponentsModel } from '../../controls/properties/components'; import { DataFederationDataSource } from '../../dataObjects/dataFederation'; import { Parameter } from '../../dataObjects/parameters/parameter'; import { RenameDataSourceStrategy } from './_renameDataSourceStrategy'; export const maxNestingLevelUpdate = createGlobalModuleVariableFunc(5); export function patchRequest(request, dataSources, state) { request.state = state; const dataSource = findFirstItemMatchesCondition(dataSources, (ds) => (request.id && ds.id === request.id) || (request.ref && ds.ref === request.ref)); if (dataSource && dataSource.data) { if (dataSource.data instanceof DataFederationDataSource) { const serializer = new ModelSerializer(); dataSource.data.getSerializableModel().serializer = serializer; const serializableFederationDataSourceInfo = dataSource.data.getSerializableModel().getSerializableFederationDataSourceInfo(); extend(request, serializableFederationDataSourceInfo); } else request.dataSource = JSON.stringify(new ModelSerializer().serialize(dataSource.data)); } } export class FieldListDataSourcesHelper { dispose() { this._usedDataSourceSubscription && this._usedDataSourceSubscription.dispose(); this._usedDataSourceSubscription = null; this._clearDataSourceCache(); this.fieldListDataSources([]); this.dataSourceHelper(null); } _clearDataSourceCache(dataSourceRef) { Object.keys(this._fieldListCache).forEach((prop) => { if (dataSourceRef === undefined || prop.split('.')[0] === dataSourceRef) { delete this._fieldListCache[prop]; delete this._innerCache[prop]; } }); dataSourceRef !== undefined && this._cacheIsClearNotificicator.notifySubscribers(); } _subscribeDataSource(dataSource) { if (dataSource.data && dataSource.data.base64) { this._dataSourceSubscriptions.push(dataSource.data.base64.subscribe((newVal) => { this._clearDataSourceCache(dataSource.ref || dataSource.id); this.fieldListDataSources.notifySubscribers(this.fieldListDataSources()); this.dataSourceHelper().usedDataSources.notifySubscribers(this.dataSourceHelper().usedDataSources()); })); } } _updateFieldListDataSources(usedDataSources, parameters) { if (!usedDataSources) { this.fieldListDataSources(null); return; } this._dataSourceSubscriptions.forEach(x => x.dispose()); this._dataSourceSubscriptions = []; this._fieldListCache = {}; this._innerCache = {}; const dataSourcesArray = [].concat(usedDataSources); if (parameters) { dataSourcesArray.splice(-1, 0, { ref: Parameter.ParametersRefString, name: 'Parameters', specifics: 'parameters', data: parameters, dataSerializer: null }); } dataSourcesArray.forEach((item) => this._subscribeDataSource(item)); this.fieldListDataSources(dataSourcesArray); } constructor() { this._fieldListCache = {}; this._dataSourceSubscriptions = []; this._innerCache = {}; this._usedDataSourceSubscription = null; this._cacheIsClearNotificicator = ko.observable(); this.dataSourceHelper = ko.observable(); this.fieldListDataSources = ko.observableArray([]); this._renameDataSourceStrategy = new RenameDataSourceStrategy(this.dataSourceHelper, () => this.fieldListDataSources.valueHasMutated()); } _wrapRequest(request) { const pathParts = request.pathParts.length > 0 ? request.pathParts.map(x => x) : request.fullPath.split('.'); const currentPathLength = Math.floor((pathParts.length - 1) / maxNestingLevelUpdate()); const currentRequestPath = pathParts.splice(0, 1 + currentPathLength * maxNestingLevelUpdate()); return new PathRequest(currentRequestPath.join('.'), currentRequestPath); } _findItems(items, pathParts) { if (pathParts.length === 0 || !items) return items; const itemName = pathParts.splice(0, 1)[0]; const item = items.filter(x => x.name === itemName)[0]; if (!item) return; if (pathParts.length > 0 && item['items']) { return this._findItems(item['items'], pathParts); } else if (pathParts.length === 0) { return item['items']; } } _createRelativePath(fullPath, currentPath) { if (!currentPath) return fullPath; if (fullPath != currentPath) { return fullPath.replace(currentPath + '.', ''); } return ''; } _updateInnerCache(currentRequest, currentPath, result) { result.forEach((item) => { const itemPath = [currentPath, item.name].join('.'); if (item.relationPath) { Object.defineProperty(item, 'items', { get: () => this._findItems(this._innerCache[currentRequest.fullPath], this._createRelativePath(item.relationPath, currentRequest.path).split('.')) }); } else if (item.items) { this._updateInnerCache(currentRequest, itemPath, item.items); } }); } _getPathPartsFromRequest(request) { return request.pathParts.length > 0 ? request.pathParts.map(x => x) : request.fullPath.split('.'); } _getItemsFromCache(currentRequest) { let items = this._innerCache[currentRequest.fullPath]; const closestCachedPath = this._getPathPartsFromRequest(currentRequest); while (!items && closestCachedPath.length > 1) { closestCachedPath.pop(); items = this._innerCache[closestCachedPath.join('.')]; } let currentPath = this._getPathPartsFromRequest(currentRequest); currentPath = currentPath.splice(closestCachedPath.length, currentPath.length); return this._findItems(items, currentPath); } wrapFieldsCallback(fieldsCallback, state, dataSources = this.fieldListDataSources, useCache = true) { const cache = this._fieldListCache; return (request) => { if (cache && useCache) { const items = this._getItemsFromCache(request); if (items) return new DxDeferred().resolve(items).promise(); const newRequest = this._wrapRequest(request); this._cacheIsClearNotificicator(); if (cache[newRequest.fullPath]) { const $deferred = new DxDeferred(); cache[newRequest.fullPath].done(result => { if (!Array.isArray(result)) $deferred.resolve(result); else $deferred.resolve(this._getItemsFromCache(request)); }); return $deferred.promise(); } patchRequest(newRequest, dataSources.peek(), state()); if (newRequest['dataSource']) { const $deferred = new DxDeferred(); cache[newRequest.fullPath] = fieldsCallback(newRequest).done((result) => { if (Array.isArray(result)) { this._innerCache[newRequest.fullPath] = result; this._updateInnerCache(newRequest, newRequest.fullPath, result); $deferred.resolve(this._getItemsFromCache(request)); } else { $deferred.resolve(result); } }); return $deferred.promise(); } cache[newRequest.fullPath] = undefined; return new DxDeferred().reject().promise(); } else { patchRequest(request, dataSources.peek(), state()); return request.dataSource ? fieldsCallback(request) : new DxDeferred().reject().promise(); } }; } _subscribeDataSources(usedDataSources, model) { this._usedDataSourceSubscription = usedDataSources.subscribe((args) => { const changeSet = args[0]; const dataSource = changeSet.value; if (changeSet.status === 'added') { this._subscribeDataSource(dataSource); model.components.push(new ComponentsModel(dataSource, this._renameDataSourceStrategy, model.parameters)); this.fieldListDataSources.splice(changeSet.index, 0, dataSource); } else { if (dataSource.data && dataSource.data.base64) { this._dataSourceSubscriptions[changeSet.index].dispose(); this._dataSourceSubscriptions.splice(changeSet.index, 1); } model.components.splice(changeSet.index, 1); this.fieldListDataSources.splice(changeSet.index, 1); this._clearDataSourceCache(dataSource.ref || dataSource.id); } }, null, 'arrayChange'); } updateDataSources(dsHelper, model, parameters) { this._subscribeDataSources(dsHelper.usedDataSources, model); this._updateFieldListDataSources(dsHelper.usedDataSources(), parameters); this.dataSourceHelper(dsHelper); model.components([]); model.components(dsHelper.usedDataSources() .filter(item => item.specifics !== 'none') .map(item => new ComponentsModel(item, this._renameDataSourceStrategy, parameters))); } }