diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source/data_source_controller.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source/data_source_controller.ts index 1079a7ec73eb..9dbb3115e6dc 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source/data_source_controller.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source/data_source_controller.ts @@ -9,7 +9,7 @@ import type { DataSource } from '@ts/data/data_source/data_source'; import type { StoreLoadOptions } from '@ts/data/data_source/types'; import type DataSourceAdapter from '@ts/grids/grid_core/data_source_adapter/m_data_source_adapter'; import type { - DataSourceAdapterProvider, LoadOperation, OperationTypes, RawItemData, RemoteOperationsOptions, + DataSourceAdapterProvider, LastLoadOptions, OperationTypes, RawItemData, RemoteOperationsOptions, } from '@ts/grids/grid_core/data_source_adapter/types'; import modules from '@ts/grids/grid_core/modules/modules'; import type { RowKey } from '@ts/grids/grid_core/types'; @@ -179,7 +179,7 @@ export class DataSourceController< return this.adapter?.select(); } - public lastLoadOptions(): Partial> { + public lastLoadOptions(): Partial { return this.adapter?.lastLoadOptions() ?? {}; } diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts index 4bdb01956ba9..47d862ceabe2 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter.ts @@ -10,11 +10,13 @@ import { extend } from '@js/core/utils/extend'; import { each } from '@js/core/utils/iterator'; import { isDefined, isPlainObject } from '@js/core/utils/type'; import type { StoreChange } from '@js/data/store'; +import type { EventsStrategy } from '@ts/core/events_strategy'; import type Store from '@ts/data/abstract_store'; import type { StoreKey } from '@ts/data/abstract_store'; import type { DataSource } from '@ts/data/data_source/data_source'; import type { ChangingEvent, StoreLoadOptions } from '@ts/data/data_source/types'; import type { BeforePushEvent } from '@ts/data/types'; +import type { RowKey } from '@ts/grids/grid_core/types'; import gridCoreUtils from '../m_utils'; import modules from '../modules/modules'; @@ -27,7 +29,12 @@ import { setPageDataToCache, } from './m_data_source_adapter_utils'; import type { - ChangedEvent, LoadOperation, OperationTypes, RawItemData, RemoteOperationsOptions, + ChangedEvent, + LastLoadOptions, + LoadOperation, + OperationTypes, + RawItemData, + RemoteOperationsOptions, } from './types'; import { normalizeRemoteOperations } from './utils/remoteOperations'; @@ -40,35 +47,35 @@ export default class DataSourceAdapter extends modules.Controller { private _hasLastPage!: boolean; - private _currentTotalCount: any; + private _currentTotalCount!: number; - protected _items: any; + protected _items?: RawItemData[]; - private _cachedData: any; + private _cachedData!: LoadOperation['cachedData']; protected _cachedStoreData?: RawItemData[]; - private _cachedPagingData: any; + private _cachedPagingData?: RawItemData[]; private _lastOperationTypes!: OperationTypes; - private _eventsStrategy: any; + private _eventsStrategy!: EventsStrategy; - protected _totalCountCorrection: any; + protected _totalCountCorrection!: number; - protected _lastLoadOptions: any; + protected _lastLoadOptions?: LastLoadOptions; - private _dataIndexGetter: any; + private _dataIndexGetter?: (data: RawItemData) => number; - private _dataIndexByKey: any; + private _dataIndexByKey?: Record; - private _isRefreshing: any; + private _isRefreshing?: boolean; private _loadingOperationTypes?: OperationTypes; - private _isRefreshed: any; + private _isRefreshed?: boolean; - protected _lastOperationId: any; + protected _lastOperationId?: number; private _operationTypes?: OperationTypes; @@ -88,13 +95,13 @@ export default class DataSourceAdapter extends modules.Controller { private customizeStoreLoadOptionsHandlerProxy!: (e: LoadOperation) => void; - private customizeLoadResultHandlerProxy!: (e: any) => any; + private customizeLoadResultHandlerProxy!: (e: LoadOperation) => void; - private loadingChangedHandlerProxy!: (e: any) => any; + private loadingChangedHandlerProxy!: (e: boolean) => void; private loadErrorHandlerProxy!: (e: Error | string) => void; - private pushHandlerProxy!: (e: BeforePushEvent) => any; + private pushHandlerProxy!: (e: BeforePushEvent) => void; private changingHandlerProxy!: (e: ChangingEvent) => void; @@ -264,7 +271,7 @@ export default class DataSourceAdapter extends modules.Controller { this._dataIndexByKey = undefined; } - protected resetCache() { + protected resetCache(): void { this.setCachedStoreData(undefined); this._cachedPagingData = undefined; } @@ -273,14 +280,17 @@ export default class DataSourceAdapter extends modules.Controller { * @extended: virtual_scrolling */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - protected resetPagesCache(isLiveUpdate?) { + protected resetPagesCache(isLiveUpdate?: boolean): void { this._cachedData = createEmptyCachedData(); } - private _needClearStoreDataCache() { + private _needClearStoreDataCache(): boolean { const remoteOperations = this.remoteOperations(); - const operationTypes = this._calculateOperationTypes(this._lastLoadOptions || {}, {}); - const isLocalOperations = Object.keys(remoteOperations).every((operationName) => !operationTypes[operationName] || !remoteOperations[operationName]); + const operationTypes = this._calculateOperationTypes(this._lastLoadOptions ?? {}, {}); + const isLocalOperations = Object.keys(remoteOperations).every( + (operationName) => !operationTypes[operationName as keyof OperationTypes] + || !remoteOperations[operationName as keyof RemoteOperationsOptions], + ); return !isLocalOperations; } @@ -317,13 +327,13 @@ export default class DataSourceAdapter extends modules.Controller { if (!this._dataIndexGetter) { const store = this.store(); - this._dataIndexGetter = (data) => { + this._dataIndexGetter = (data): number => { if (!this._dataIndexByKey) { const storeData = this._cachedStoreData ?? []; this._dataIndexByKey = {}; - for (let i = 0; i < storeData.length; i++) { + for (let i = 0; i < storeData.length; i += 1) { this._dataIndexByKey[getKeyHash(store.keyOf(storeData[i]))] = i; } } @@ -338,36 +348,39 @@ export default class DataSourceAdapter extends modules.Controller { /** * @extended: TreeLists's data_source_adapter */ - protected _getKeyInfo() { + protected _getKeyInfo(): Store { return this.store(); } /** * @extended: TreeLists's data_source_adapter */ - protected _needToCopyDataObject() { + protected _needToCopyDataObject(): boolean { return true; } /** * @extended: TreeLists's data_source_adapter */ - protected _applyBatch(changes, fromStore?) { + protected _applyBatch(changes: StoreChange[], fromStore = false): void { const keyInfo = this._getKeyInfo(); const dataSource = this._dataSource; const groupCount = gridCoreUtils.normalizeSortingInfo(this.group()).length; const isReshapeMode = this.option('editing.refreshMode') === 'reshape'; const isVirtualMode = this.option('scrolling.mode') === 'virtual'; - changes = changes.filter((change) => !dataSource.paginate() || change.type !== 'insert' || change.index !== undefined); + const filteredChanges = changes.filter( + (change) => !dataSource.paginate() || change.type !== 'insert' || change.index !== undefined, + ); - const getItemCount = () => (groupCount ? this.itemsCount() : this.items().length); + const getItemCount = (): number => (groupCount ? this.itemsCount() : this.items().length); const oldItemCount = getItemCount(); applyBatch({ keyInfo, + // @ts-expect-error _items is set by the virtual_scrolling extender data: this._items, - changes, + changes: filteredChanges, groupCount, useInsertIndex: true, skipCopying: !this._needToCopyDataObject(), @@ -375,7 +388,7 @@ export default class DataSourceAdapter extends modules.Controller { applyBatch({ keyInfo, data: dataSource.items(), - changes, + changes: filteredChanges, groupCount, useInsertIndex: true, skipCopying: !this._needToCopyDataObject(), @@ -390,7 +403,7 @@ export default class DataSourceAdapter extends modules.Controller { this._totalCountCorrection += getItemCount() - oldItemCount; } - changes.splice(0, changes.length); + filteredChanges.splice(0, filteredChanges.length); } /** @@ -405,17 +418,24 @@ export default class DataSourceAdapter extends modules.Controller { this._applyBatch(e.changes, true); } - private _needCleanCacheByOperation(operationType, remoteOperations) { + private _needCleanCacheByOperation( + operationType: string, + remoteOperations: RemoteOperationsOptions, + ): boolean { const operationTypesByOrder = ['filtering', 'sorting', 'paging']; const operationTypeIndex = operationTypesByOrder.indexOf(operationType); - const currentOperationTypes = operationTypeIndex >= 0 ? operationTypesByOrder.slice(operationTypeIndex) : [operationType]; + const currentOperationTypes = operationTypeIndex >= 0 + ? operationTypesByOrder.slice(operationTypeIndex) + : [operationType]; - return currentOperationTypes.some((operationType) => remoteOperations[operationType]); + return currentOperationTypes.some( + (type) => remoteOperations[type as keyof RemoteOperationsOptions], + ); } protected _calculateOperationTypes( - loadOptions, - lastLoadOptions, + loadOptions: StoreLoadOptions, + lastLoadOptions: (StoreLoadOptions & { groupExpand?: boolean }) | undefined, isFullReload?: boolean, ): OperationTypes { return calculateOperationTypes(loadOptions, lastLoadOptions, isFullReload); @@ -424,16 +444,23 @@ export default class DataSourceAdapter extends modules.Controller { /** * @extended: virtual_scrolling, TreeLists's data_source_adapter, DataGrid's m_grouping */ - protected _customizeRemoteOperations(options, operationTypes) { + protected _customizeRemoteOperations( + options: LoadOperation, + operationTypes: OperationTypes, + ): void { let cachedStoreData = this._cachedStoreData; let cachedPagingData = this._cachedPagingData; let cachedData = this._cachedData; - - if ((options.storeLoadOptions.filter && !options.remoteOperations.filtering) || (options.storeLoadOptions.sort && !options.remoteOperations.sorting)) { - options.remoteOperations = { - filtering: options.remoteOperations.filtering, - summary: options.remoteOperations.summary, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- set before load + let remoteOperations = options.remoteOperations!; + + if ((options.storeLoadOptions.filter && !remoteOperations.filtering) + || (options.storeLoadOptions.sort && !remoteOperations.sorting)) { + remoteOperations = { + filtering: remoteOperations.filtering, + summary: remoteOperations.summary, }; + options.remoteOperations = remoteOperations; } if (operationTypes.fullReload) { @@ -449,7 +476,7 @@ export default class DataSourceAdapter extends modules.Controller { } each(operationTypes, (operationType, value) => { - if (value && this._needCleanCacheByOperation(operationType, options.remoteOperations)) { + if (value && this._needCleanCacheByOperation(operationType, remoteOperations)) { cachedStoreData = undefined; cachedPagingData = undefined; } @@ -457,7 +484,7 @@ export default class DataSourceAdapter extends modules.Controller { } if (cachedPagingData) { - options.remoteOperations.paging = false; + remoteOperations.paging = false; } options.cachedStoreData = cachedStoreData; @@ -474,8 +501,7 @@ export default class DataSourceAdapter extends modules.Controller { protected customizeStoreLoadOptionsHandler(options: LoadOperation): void { this._handleDataLoading(options); if (!(Array.isArray(options.data) && options.data.length === 0)) { - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - options.data = getPageDataFromCache(options, true) || options.cachedStoreData; + options.data = getPageDataFromCache(options, true) ?? options.cachedStoreData; } } @@ -498,9 +524,16 @@ export default class DataSourceAdapter extends modules.Controller { options.delay = undefined; } - const loadOptions = extend({ pageIndex: this.pageIndex(), pageSize: this.pageSize() }, options.storeLoadOptions); + const loadOptions = extend( + { pageIndex: this.pageIndex(), pageSize: this.pageSize() }, + options.storeLoadOptions, + ); - const operationTypes = this._calculateOperationTypes(loadOptions, lastLoadOptions, isFullReload); + const operationTypes = this._calculateOperationTypes( + loadOptions, + lastLoadOptions, + isFullReload, + ); this._customizeRemoteOperations(options, operationTypes); @@ -513,6 +546,7 @@ export default class DataSourceAdapter extends modules.Controller { this._loadingOperationTypes = operationTypes; this._isRefreshing = true; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- OR of flags when(isRefreshing || this._isRefreshed || this.refresh(options, operationTypes)).done(() => { if (this._lastOperationId === options.operationId) { this._isRefreshed = true; @@ -523,15 +557,18 @@ export default class DataSourceAdapter extends modules.Controller { }).fail(() => { // `operationId` is only absent on the synthetic load operations // `loadAll` builds, and those are always custom loading. - dataSource.cancel(options.operationId!); + // @ts-expect-error operationId is set for non-custom loading + dataSource.cancel(options.operationId); }).always(() => { this._isRefreshing = false; }); + // @ts-expect-error an unset id before the first load cancels nothing dataSource.cancel(this._lastOperationId); this._lastOperationId = options.operationId; if (this._isRefreshing) { + // @ts-expect-error operationId is set for non-custom loading dataSource.cancel(this._lastOperationId); } } @@ -539,11 +576,14 @@ export default class DataSourceAdapter extends modules.Controller { this._handleDataLoadingCore(options); } - private _handleDataLoadingCore(options) { - const { remoteOperations } = options; + private _handleDataLoadingCore(options: LoadOperation): void { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- set before load + const remoteOperations = options.remoteOperations!; - options.loadOptions = {}; + const loadOptions: StoreLoadOptions = {}; + options.loadOptions = loadOptions; + // @ts-expect-error cachedData is set before load const cachedExtra = options.cachedData.extra; const localLoadOptionNames = { filter: !remoteOperations.filtering, @@ -552,13 +592,13 @@ export default class DataSourceAdapter extends modules.Controller { summary: !remoteOperations.summary, skip: !remoteOperations.paging, take: !remoteOperations.paging, - requireTotalCount: cachedExtra && 'totalCount' in cachedExtra || !remoteOperations.paging, + requireTotalCount: (cachedExtra !== undefined && 'totalCount' in cachedExtra) || !remoteOperations.paging, langParams: !remoteOperations.filtering || !remoteOperations.sorting, }; each(options.storeLoadOptions, (optionName, optionValue) => { if (localLoadOptionNames[optionName]) { - options.loadOptions[optionName] = optionValue; + loadOptions[optionName] = optionValue; delete options.storeLoadOptions[optionName]; } }); @@ -577,12 +617,14 @@ export default class DataSourceAdapter extends modules.Controller { const { cachedData } = options; const { storeLoadOptions } = options; const needCache = this.option('cacheEnabled') !== false && storeLoadOptions; - const needPageCache = needCache && !options.isCustomLoading && cachedData && (!localPaging || storeLoadOptions.group); + const needPageCache = needCache && !options.isCustomLoading && cachedData + && (!localPaging || storeLoadOptions.group); const needPagingCache = needCache && localPaging; const needStoreCache = needPagingCache && !options.isCustomLoading; if (!loadOptions) { - this._dataSource.cancel(options.operationId!); + // @ts-expect-error operationId is set for non-custom loading + this._dataSource.cancel(options.operationId); return; } @@ -595,10 +637,14 @@ export default class DataSourceAdapter extends modules.Controller { } if (loadOptions.group) { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- group may be '' loadOptions.group = options.group || loadOptions.group; } - const groupCount = gridCoreUtils.normalizeSortingInfo(options.group || storeLoadOptions.group || loadOptions.group).length; + const groupCount = gridCoreUtils.normalizeSortingInfo( + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- group may be '' + options.group || storeLoadOptions.group || loadOptions.group, + ).length; if (options.cachedDataPartBegin) { options.data = options.cachedDataPartBegin.concat(options.data as RawItemData[]); @@ -612,13 +658,15 @@ export default class DataSourceAdapter extends modules.Controller { if (needPagingCache && options.cachedPagingData) { options.data = cloneItems(options.cachedPagingData, groupCount); } else { - if (needStoreCache) { - if (!this._cachedStoreData) { - this.setCachedStoreData(cloneItems(options.data, gridCoreUtils.normalizeSortingInfo(storeLoadOptions.group).length)); - } else if (options.mergeStoreLoadData) { - this.setCachedStoreData(this._cachedStoreData.concat(options.data as RawItemData[])); - options.data = this._cachedStoreData; - } + const cachedStoreData = this._cachedStoreData; + if (needStoreCache && !cachedStoreData) { + this.setCachedStoreData(cloneItems( + options.data as RawItemData[], + gridCoreUtils.normalizeSortingInfo(storeLoadOptions.group).length, + )); + } else if (needStoreCache && cachedStoreData && options.mergeStoreLoadData) { + this.setCachedStoreData(cachedStoreData.concat(options.data as RawItemData[])); + options.data = this._cachedStoreData; } new ArrayStore(options.data as RawItemData[]).load(loadOptions).done((data) => { options.data = data as RawItemData[]; @@ -626,8 +674,8 @@ export default class DataSourceAdapter extends modules.Controller { this._cachedPagingData = cloneItems(options.data, groupCount); } }).fail((error) => { - // @ts-expect-error - options.data = new Deferred().reject(error); + // @ts-expect-error badly typed Deferred + options.data = Deferred().reject(error); }); } @@ -636,20 +684,23 @@ export default class DataSourceAdapter extends modules.Controller { options.extra.totalCount = (options.data as RawItemData[]).length; } - if (options.extra && (options.extra.totalCount ?? -1) >= 0 && (storeLoadOptions.requireTotalCount === false || loadOptions.requireTotalCount === false)) { + if (options.extra && (options.extra.totalCount ?? -1) >= 0 + && (storeLoadOptions.requireTotalCount === false + || loadOptions.requireTotalCount === false)) { options.extra.totalCount = -1; } - if (!loadOptions.data && (storeLoadOptions.requireTotalCount || (options.extra?.totalCount ?? -1) >= 0)) { + if (!loadOptions.data + && (storeLoadOptions.requireTotalCount || (options.extra?.totalCount ?? -1) >= 0)) { this._totalCountCorrection = 0; } this.customizeLoadResultHandlerCore(options); if (needPageCache) { - cachedData.extra = cachedData.extra || extend({}, options.extra); + cachedData.extra = cachedData.extra ?? extend({}, options.extra); when(options.data).done((data) => { - setPageDataToCache(options, data, groupCount); + setPageDataToCache(options, data as RawItemData[], groupCount); }); } } @@ -659,7 +710,7 @@ export default class DataSourceAdapter extends modules.Controller { this._lastLoadOptions = options.lastLoadOptions; Object.keys(options.operationTypes ?? {}).forEach((operationType) => { - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- OR of flags this._lastOperationTypes[operationType] ||= options.operationTypes?.[operationType]; }); } @@ -671,7 +722,8 @@ export default class DataSourceAdapter extends modules.Controller { * @extended: TreeLists's data_source_adapter */ protected customizeLoadResultHandlerCore(options: LoadOperation): void { - if (options.remoteOperations && !options.remoteOperations.paging && Array.isArray(options.data)) { + if (options.remoteOperations && !options.remoteOperations.paging + && Array.isArray(options.data)) { if (options.skip !== undefined) { options.data = options.data.slice(options.skip); } @@ -702,7 +754,7 @@ export default class DataSourceAdapter extends modules.Controller { /** * @extended: virtual_scrolling */ - protected _loadPageSize() { + protected _loadPageSize(): number { return this.pageSize(); } @@ -711,11 +763,13 @@ export default class DataSourceAdapter extends modules.Controller { */ // ChangedEvent protected dataChangedHandler(e?: ChangedEvent): void { - let currentTotalCount; + // eslint-disable-next-line @typescript-eslint/init-declarations -- assigned below + let currentTotalCount: number; const dataSource = this._dataSource; let isLoading = false; - // At this stage e.changeType can be defined only if virtual scrolling and scrolling.legacyMode is true + // At this stage e.changeType can be defined only if virtual scrolling + // and scrolling.legacyMode is true const isDataLoading = !e || isDefined(e.changeType); const itemsCount = this.itemsCount(); @@ -771,18 +825,18 @@ export default class DataSourceAdapter extends modules.Controller { return this._operationTypes ?? null; } - public lastLoadOptions(): NonNullable { - return this._lastLoadOptions || {}; + public lastLoadOptions(): Partial { + return this._lastLoadOptions ?? {}; } - private isLastPage() { + private isLastPage(): boolean { return this._isLastPage; } /** * @extended: virtual_scrolling */ - protected _dataSourceTotalCount() { + protected _dataSourceTotalCount(): number { return this._dataSource.totalCount(); } @@ -790,19 +844,19 @@ export default class DataSourceAdapter extends modules.Controller { * @extended: virtual_scrolling, TreeLists's data_source_adapter */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - protected _changeRowExpandCore(path?: any) {} + protected _changeRowExpandCore(path: RowKey): void {} /** * @extended: TreeLists's data_source_adapter */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - public changeRowExpand(path?: unknown): DeferredObj | undefined { + public changeRowExpand(path: RowKey): DeferredObj | undefined { return undefined; } public totalCount(): number { - // eslint-disable-next-line radix - return parseInt((this._currentTotalCount || this._dataSourceTotalCount()) + this._totalCountCorrection); + const totalCount = this._currentTotalCount || this._dataSourceTotalCount(); + return parseInt(String(totalCount + this._totalCountCorrection), 10); } public totalCountCorrection(): number { @@ -814,7 +868,7 @@ export default class DataSourceAdapter extends modules.Controller { * @protected */ public items(): RawItemData[] { - return (this._items ?? []) as RawItemData[]; + return this._items ?? []; } /** diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter_utils.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter_utils.ts index ecb915558ab7..7878632ba89f 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter_utils.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/m_data_source_adapter_utils.ts @@ -1,27 +1,34 @@ import { extend } from '@js/core/utils/extend'; import { isDefined } from '@js/core/utils/type'; import commonUtils from '@ts/core/utils/m_common'; +import type { StoreLoadOptions } from '@ts/data/data_source/types'; import { equalFilterParameters } from '../filter/utils'; import gridCoreUtils from '../m_utils'; -import type { OperationTypes } from './types'; - -export const cloneItems = function (items, groupCount) { - if (items) { - items = items.slice(0); +import type { + LoadOperation, OperationTypes, RawItemData, +} from './types'; + +export const cloneItems = ( + items: RawItemData[] | undefined, + groupCount: number, +): RawItemData[] | undefined => { + let result = items; + if (result) { + result = result.slice(0); if (groupCount) { - for (let i = 0; i < items.length; i++) { - items[i] = extend({ key: items[i].key }, items[i]); - items[i].items = cloneItems(items[i].items, groupCount - 1); + for (let i = 0; i < result.length; i += 1) { + result[i] = extend({ key: result[i].key }, result[i]); + result[i].items = cloneItems(result[i].items as RawItemData[] | undefined, groupCount - 1); } } } - return items; + return result; }; export const calculateOperationTypes = ( - loadOptions, - lastLoadOptions, + loadOptions: StoreLoadOptions, + lastLoadOptions: (StoreLoadOptions & { groupExpand?: boolean }) | undefined, isFullReload?: boolean, ): OperationTypes => { let operationTypes: OperationTypes = { reload: true, fullReload: true }; @@ -48,6 +55,7 @@ export const calculateOperationTypes = ( paging: false, }; + /* eslint-disable @typescript-eslint/prefer-nullish-coalescing -- OR of flags */ operationTypes.reload = Boolean( isFullReload || operationTypes.sorting @@ -59,12 +67,13 @@ export const calculateOperationTypes = ( || operationTypes.pageSize || operationTypes.take, ); + /* eslint-enable @typescript-eslint/prefer-nullish-coalescing */ } return operationTypes; }; -export const executeTask = function (action, timeout) { +export const executeTask = (action: () => void, timeout?: number): void => { if (isDefined(timeout)) { commonUtils.executeAsync(action, timeout); } else { @@ -72,21 +81,80 @@ export const executeTask = function (action, timeout) { } }; -export const createEmptyCachedData = function () { - return { items: {} }; +export const createEmptyCachedData = (): { items: Record } => ({ items: {} }); + +export const getGroupItemFromCache = ( + cacheItem: RawItemData | undefined, + groupCount: number, + skips: (number | undefined)[], + takes: (number | undefined)[], +): RawItemData | undefined => { + if (!groupCount || !cacheItem) { + return cacheItem; + } + + const result: RawItemData = { ...cacheItem }; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- NaN falls back to 0 + const skip = skips[0] || 0; + const take = takes[0]; + const items = cacheItem.items as RawItemData[] | undefined; + + if (items) { + if (take === undefined && !items[skip]) { + return undefined; + } + result.items = []; + if (skips.length) { + result.isContinuation = true; + } + + if (take) { + result.isContinuationOnNextPage = Number(cacheItem.count) > take; + } + + for (let i = 0; take === undefined ? items[i + skip] : i < take; i += 1) { + const childCacheItem = items[i + skip]; + const isLast = i + 1 === take; + const item = getGroupItemFromCache( + childCacheItem, + groupCount - 1, + i === 0 ? skips.slice(1) : [], + isLast ? takes.slice(1) : [], + ); + + if (item !== undefined) { + (result.items as RawItemData[]).push(item); + } else { + return undefined; + } + } + } + + return result; }; -export const getPageDataFromCache = function (options, updatePaging?): any { - const groupCount = gridCoreUtils.normalizeSortingInfo(options.group || options.storeLoadOptions.group || options.loadOptions.group).length; - const items = []; - if (fillItemsFromCache(items, options, groupCount)) { - return items; - } if (updatePaging) { - updatePagingOptionsByCache(items, options, groupCount); +export const getItemFromCache = ( + options: LoadOperation, + cacheItem: RawItemData | undefined, + groupCount: number, + index: number, + take: number, +): RawItemData | undefined => { + if (groupCount && cacheItem) { + const skips = index === 0 ? options.skips ?? [] : []; + const takes = index === take - 1 ? options.takes ?? [] : []; + + return getGroupItemFromCache(cacheItem, groupCount, skips, takes); } + return cacheItem; }; -export const fillItemsFromCache = function (items, options, groupCount, fromEnd?) { +export const fillItemsFromCache = ( + items: RawItemData[], + options: LoadOperation, + groupCount: number, + fromEnd?: boolean, +): boolean => { const { storeLoadOptions } = options; const take = options.take ?? storeLoadOptions.take ?? 0; const cachedItems = options.cachedData?.items; @@ -102,7 +170,13 @@ export const fillItemsFromCache = function (items, options, groupCount, fromEnd? return true; } - const item = getItemFromCache(options, cacheItem, groupCount, localIndex, take); + const item = getItemFromCache( + options, + cacheItem as RawItemData | undefined, + groupCount, + localIndex, + take, + ); if (item) { items.push(item); @@ -115,60 +189,15 @@ export const fillItemsFromCache = function (items, options, groupCount, fromEnd? return false; }; -export const getItemFromCache = function (options, cacheItem, groupCount, index, take) { - if (groupCount && cacheItem) { - const skips = (index === 0 && options.skips) || []; - const takes = (index === take - 1 && options.takes) || []; - - return getGroupItemFromCache(cacheItem, groupCount, skips, takes); - } - return cacheItem; -}; - -export const getGroupItemFromCache = function (cacheItem, groupCount, skips, takes) { - if (groupCount && cacheItem) { - const result = { ...cacheItem }; - const skip: number = skips[0] || 0; - const take = takes[0]; - const { items } = cacheItem; - - if (items) { - if (take === undefined && !items[skip]) { - return; - } - result.items = []; - if (skips.length) { - result.isContinuation = true; - } - - if (take) { - result.isContinuationOnNextPage = cacheItem.count > take; - } - - for (let i = 0; take === undefined ? items[i + skip] : i < take; i += 1) { - const childCacheItem = items[i + skip]; - const isLast = i + 1 === take; - const item = getGroupItemFromCache(childCacheItem, groupCount - 1, i === 0 ? skips.slice(1) : [], isLast ? takes.slice(1) : []); - - if (item !== undefined) { - result.items.push(item); - } else { - return; - } - } - } - - return result; - } - - return cacheItem; -}; - -export const updatePagingOptionsByCache = function (cacheItemsFromBegin, options, groupCount) { +export const updatePagingOptionsByCache = ( + cacheItemsFromBegin: RawItemData[], + options: LoadOperation, + groupCount: number, +): void => { const cacheItemBeginCount = cacheItemsFromBegin.length; const { storeLoadOptions } = options; if (storeLoadOptions.skip !== undefined && storeLoadOptions.take && !groupCount) { - const cacheItemsFromEnd = []; + const cacheItemsFromEnd: RawItemData[] = []; fillItemsFromCache(cacheItemsFromEnd, options, groupCount, true); const cacheItemEndCount = cacheItemsFromEnd.length; @@ -190,33 +219,48 @@ export const updatePagingOptionsByCache = function (cacheItemsFromBegin, options } }; -export const setPageDataToCache = function (options, data, groupCount) { - const { storeLoadOptions } = options; - const skip: number = options.skip ?? storeLoadOptions.skip ?? 0; - const take: number = options.take ?? storeLoadOptions.take ?? 0; - - for (let i = 0; i < take; i += 1) { - const globalIndex = i + skip; - const cacheItems = options.cachedData.items; - const skips = (i === 0 && options.skips) || []; - cacheItems[globalIndex] = getCacheItem(cacheItems[globalIndex], data[i], groupCount, skips); +export const getPageDataFromCache = ( + options: LoadOperation, + updatePaging?: boolean, +): RawItemData[] | undefined => { + const groupCount = gridCoreUtils.normalizeSortingInfo( + // eslint-disable-next-line @stylistic/max-len + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion, @typescript-eslint/prefer-nullish-coalescing -- loadOptions is set when reached; group may be '' + options.group || options.storeLoadOptions.group || options.loadOptions!.group, + ).length; + const items: RawItemData[] = []; + if (fillItemsFromCache(items, options, groupCount)) { + return items; } + if (updatePaging) { + updatePagingOptionsByCache(items, options, groupCount); + } + return undefined; }; -export const getCacheItem = function (cacheItem, loadedItem, groupCount, skips) { +export const getCacheItem = ( + cacheItem: RawItemData | undefined, + loadedItem: RawItemData | undefined, + groupCount: number, + skips: (number | undefined)[], +): RawItemData | undefined => { if (groupCount && loadedItem) { - const result = { ...loadedItem }; + const result: RawItemData = { ...loadedItem }; delete result.isContinuation; delete result.isContinuationOnNextPage; - const skip: number = skips[0] || 0; + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- NaN falls back to 0 + const skip = skips[0] || 0; - if (loadedItem.items) { + const loadedChildren = loadedItem.items as RawItemData[] | undefined; + if (loadedChildren) { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- items is untyped result.items = cacheItem?.items || {}; - loadedItem.items.forEach((item, index: number) => { + const resultItems = result.items as Record; + loadedChildren.forEach((item, index: number) => { const globalIndex = index + skip; const childSkips = index === 0 ? skips.slice(1) : []; - result.items[globalIndex] = getCacheItem( - result.items[globalIndex], + resultItems[globalIndex] = getCacheItem( + resultItems[globalIndex], item, groupCount - 1, childSkips, @@ -229,3 +273,26 @@ export const getCacheItem = function (cacheItem, loadedItem, groupCount, skips) return loadedItem; }; + +export const setPageDataToCache = ( + options: LoadOperation, + data: RawItemData[], + groupCount: number, +): void => { + const { storeLoadOptions } = options; + const skip: number = options.skip ?? storeLoadOptions.skip ?? 0; + const take: number = options.take ?? storeLoadOptions.take ?? 0; + + for (let i = 0; i < take; i += 1) { + const globalIndex = i + skip; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion -- cachedData set by caller + const cacheItems = options.cachedData!.items; + const skips = i === 0 ? options.skips ?? [] : []; + cacheItems[globalIndex] = getCacheItem( + cacheItems[globalIndex] as RawItemData | undefined, + data[i], + groupCount, + skips, + ); + } +}; diff --git a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/types.ts b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/types.ts index 5ebf0bf948cc..a223c593a999 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/types.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/data_source_adapter/types.ts @@ -30,6 +30,11 @@ export interface OperationTypes { paging?: boolean; } +export type LastLoadOptions = StoreLoadOptions & { + pageIndex: number; + pageSize: number; +}; + export interface LoadOperation extends Omit { operationId?: number; data?: RawItemData[] | DeferredObj; @@ -42,10 +47,7 @@ export interface LoadOperation extends Omit { remoteOperations?: RemoteOperationsOptions; isCustomLoading?: boolean; pageIndex?: number; - lastLoadOptions?: StoreLoadOptions & { - pageIndex: number; - pageSize: number; - }; + lastLoadOptions?: LastLoadOptions; operationTypes?: OperationTypes; group?: StoreLoadOptions['group']; extra?: { @@ -65,6 +67,8 @@ export interface LoadOperation extends Omit { cachedDataPartEnd?: RawItemData[]; skip?: number; take?: number; + skips?: (number | undefined)[]; + takes?: (number | undefined)[]; mergeStoreLoadData?: boolean; } diff --git a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/m_virtual_scrolling.ts b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/m_virtual_scrolling.ts index 1fff137a702a..ade11aaeb034 100644 --- a/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/m_virtual_scrolling.ts +++ b/packages/devextreme/js/__internal/grids/grid_core/virtual_scrolling/m_virtual_scrolling.ts @@ -18,7 +18,7 @@ import type { ModuleType } from '@ts/grids/grid_core/types'; import type { ResizingController } from '@ts/grids/grid_core/views/m_grid_view'; import type { RowsView } from '@ts/grids/grid_core/views/m_rows_view'; -import type { ChangedEvent } from '../data_source_adapter/types'; +import type { ChangedEvent, RawItemData } from '../data_source_adapter/types'; import gridCoreUtils from '../m_utils'; import type { StateStoringDataControllerExtension } from '../state_storing/extenders/state_storing_data_controller'; import type { RowsViewScrollEvent } from '../views/types'; @@ -87,6 +87,8 @@ const removeEmptyRows = function ($emptyRows, className) { }; export const dataSourceAdapterExtender = (Base: ModuleType) => class VirtualScrollingCoreDataSourceAdapterExtender extends Base { + protected declare _items: RawItemData[]; + private _totalCount: any; private _isLoaded: any; @@ -197,6 +199,7 @@ export const dataSourceAdapterExtender = (Base: ModuleType) = protected dataChangedHandler(e?: ChangedEvent): void { if (this.option(LEGACY_SCROLLING_MODE) === false) { + // @ts-expect-error DataSource items are typed as unknown this._items = this._dataSource.items().slice(); this._totalCount = this._dataSourceTotalCount(true); super.dataChangedHandler(e);