From d81686d476fb71c882fc0744c555c8a8d2375249 Mon Sep 17 00:00:00 2001 From: mohamedsalem401 Date: Wed, 3 Sep 2025 08:48:41 +0300 Subject: [PATCH] Add data resolver to wrapper component --- .../model/ComponentWithCollectionsState.ts | 139 ++++++++++++++++++ .../dom_components/model/ComponentWrapper.ts | 102 ++++++++++++- 2 files changed, 239 insertions(+), 2 deletions(-) create mode 100644 packages/core/src/data_sources/model/ComponentWithCollectionsState.ts diff --git a/packages/core/src/data_sources/model/ComponentWithCollectionsState.ts b/packages/core/src/data_sources/model/ComponentWithCollectionsState.ts new file mode 100644 index 000000000..e4cc64839 --- /dev/null +++ b/packages/core/src/data_sources/model/ComponentWithCollectionsState.ts @@ -0,0 +1,139 @@ +import { DataCollectionStateMap } from '../../data_sources/model/data_collection/types'; +import DataResolverListener from '../../data_sources/model/DataResolverListener'; +import DataVariable, { DataVariableProps, DataVariableType } from '../../data_sources/model/DataVariable'; +import Components from '../../dom_components/model/Components'; +import Component from '../../dom_components/model/Component'; +import { ObjectAny } from '../../common'; +import { isDataVariable } from '../utils'; +import { isObject } from '../../utils/mixins'; +import DataSource from './DataSource'; +import { isArray } from 'underscore'; + +export type DataVariableMap = Record; + +export default class ComponentWithCollectionsState extends Component { + collectionsStateMap: DataCollectionStateMap = {}; + dataSourceWatcher?: DataResolverListener; + + constructor(props: any, opt: any) { + super(props, opt); + this.listenToPropsChange(); + } + + onCollectionsStateMapUpdate(collectionsStateMap: DataCollectionStateMap) { + this.collectionsStateMap = collectionsStateMap; + this.dataResolverWatchers?.onCollectionsStateMapUpdate?.(); + + this.components().forEach((cmp) => { + cmp.onCollectionsStateMapUpdate?.(collectionsStateMap); + }); + } + + syncOnComponentChange(model: Component, collection: Components, opts: any) { + const prev = this.collectionsStateMap; + this.collectionsStateMap = {}; + super.syncOnComponentChange(model, collection, opts); + this.collectionsStateMap = prev; + this.onCollectionsStateMapUpdate(prev); + } + + syncComponentsCollectionState() { + super.syncComponentsCollectionState(); + this.components().forEach((cmp) => cmp.syncComponentsCollectionState?.()); + } + + protected listenToDataSource() { + const path = this.dataSourcePath; + if (!path) return; + + const { em } = this; + this.dataSourceWatcher = new DataResolverListener({ + em, + resolver: new DataVariable( + { type: DataVariableType, path }, + { em, collectionsStateMap: this.collectionsStateMap }, + ), + onUpdate: () => this.onDataSourceChange(), + }); + } + + protected listenToPropsChange() { + this.on(`change:dataResolver`, () => { + this.listenToDataSource(); + }); + + this.listenToDataSource(); + } + + protected get dataSourceProps(): DataVariableProps | undefined { + return this.get('dataResolver'); + } + + protected get dataSourcePath(): string | undefined { + return this.dataSourceProps?.path; + } + + protected onDataSourceChange() { + this.onCollectionsStateMapUpdate(this.collectionsStateMap); + } + + protected getDataSourceItems() { + const dataSourceProps = this.dataSourceProps; + if (!dataSourceProps) return []; + const items = this.listDataSourceItems(dataSourceProps); + if (items && isArray(items)) { + return items; + } + + const clone = { ...items }; + delete clone['__p']; + return clone; + } + + protected listDataSourceItems(dataSource: DataSource | DataVariableProps): DataVariableProps[] | DataVariableMap { + const em = this.em; + switch (true) { + case isObject(dataSource) && dataSource instanceof DataSource: { + const id = dataSource.get('id')!; + return this.listDataSourceVariables(id); + } + case isDataVariable(dataSource): { + const path = dataSource.path; + if (!path) return []; + const isDataSourceId = path.split('.').length === 1; + if (isDataSourceId) { + return this.listDataSourceVariables(path); + } else { + return em.DataSources.getValue(path, []); + } + } + default: + return []; + } + } + + protected getItemKey(items: DataVariableProps[] | { [x: string]: DataVariableProps }, index: number) { + return isArray(items) ? index : Object.keys(items)[index]; + } + + private removePropsListeners() { + this.off(`change:dataResolver`); + this.dataSourceWatcher?.destroy(); + this.dataSourceWatcher = undefined; + } + + private listDataSourceVariables(path: string): DataVariableProps[] { + const records = this.em.DataSources.getValue(path, []); + const keys = Object.keys(records); + + return keys.map((key) => ({ + type: DataVariableType, + path: path + '.' + key, + })); + } + + destroy(options?: ObjectAny): false | JQueryXHR { + this.removePropsListeners(); + return super.destroy(options); + } +} diff --git a/packages/core/src/dom_components/model/ComponentWrapper.ts b/packages/core/src/dom_components/model/ComponentWrapper.ts index d32080d74..9431ff9e8 100644 --- a/packages/core/src/dom_components/model/ComponentWrapper.ts +++ b/packages/core/src/dom_components/model/ComponentWrapper.ts @@ -1,10 +1,17 @@ -import { isUndefined } from 'underscore'; +import { isArray, isUndefined } from 'underscore'; import { attrToString } from '../../utils/dom'; import Component from './Component'; import ComponentHead, { type as typeHead } from './ComponentHead'; import { ToHTMLOptions } from './types'; +import Components from './Components'; +import DataResolverListener from '../../data_sources/model/DataResolverListener'; +import { DataVariableProps } from '../../data_sources/model/DataVariable'; +import { DataCollectionStateMap } from '../../data_sources/model/data_collection/types'; +import ComponentWithCollectionsState, { DataVariableMap } from '../../data_sources/model/ComponentWithCollectionsState'; + +export default class ComponentWrapper extends ComponentWithCollectionsState { + dataSourceWatcher?: DataResolverListener; -export default class ComponentWrapper extends Component { get defaults() { return { // @ts-ignore @@ -30,6 +37,21 @@ export default class ComponentWrapper extends Component { }; } + constructor(props: any, opt: any) { + super(props, opt); + + const hasDataResolver = this.getDataResolver(); + console.log('🚀 ~ ComponentWrapper ~ constructor ~ hasDataResolver:', hasDataResolver); + if (hasDataResolver) { + this.syncComponentsCollectionState(); + console.log( + '🚀 ~ ComponentWrapper ~ constructor ~ this.getCollectionsStateMap():', + this.getCollectionsStateMap(), + ); + this.onCollectionsStateMapUpdate(this.getCollectionsStateMap()); + } + } + preInit() { const { opt, attributes: props } = this; const cmp = this.em?.Components; @@ -78,6 +100,82 @@ export default class ComponentWrapper extends Component { return asDoc ? `${doctype}${headStr}${body}` : body; } + setDataResolver(dataResolver: DataVariableProps) { + return this.set('dataResolver', dataResolver); + } + + getDataResolver() { + return this.get('dataResolver'); + } + + onCollectionsStateMapUpdate(collectionsStateMap: DataCollectionStateMap) { + const { page, head } = this; + super.onCollectionsStateMapUpdate(collectionsStateMap); + head.onCollectionsStateMapUpdate(collectionsStateMap); + if (page) page.collectionsStateMap = collectionsStateMap; + } + + syncComponentsCollectionState() { + super.syncComponentsCollectionState(); + this.head.syncComponentsCollectionState(); + } + + syncOnComponentChange(model: Component, collection: Components, opts: any) { + const collectionsStateMap: any = this.getCollectionsStateMap(); + + this.collectionsStateMap = collectionsStateMap; + super.syncOnComponentChange(model, collection, opts); + this.onCollectionsStateMapUpdate(collectionsStateMap); + } + + protected listenToPropsChange() { + this.on(`change:dataResolver`, () => { + this.onCollectionsStateMapUpdate(this.getCollectionsStateMap()); + this.listenToDataSource(); + }); + + this.listenToDataSource(); + } + + private getCollectionsStateMapForItem(items: DataVariableProps[] | DataVariableMap, key: number | string) { + const totalItems = Object.keys(items).length; + let item: DataVariableProps = (items as any)[key]; + const numericKey = typeof key === 'string' ? Object.keys(items).indexOf(key) : key; + const offset = numericKey - 0; + const remainingItems = totalItems - (1 + offset); + const collectionState = { + collectionId: '__pages', + currentIndex: numericKey, + currentItem: item, + currentKey: key, + startIndex: 0, + endIndex: totalItems - 1, + totalItems: totalItems, + remainingItems, + }; + + return collectionState; + } + + private getCollectionsStateMap(): DataCollectionStateMap { + const path = this.dataSourcePath; + if (!path) return this.collectionsStateMap; + const items = this.getDataSourceItems(); + const pages = []; + const length = Object.keys(items).length; + for (let index = 0; index <= length; index++) { + const key = this.getItemKey(items, index); + const collectionsStateMap = this.getCollectionsStateMapForItem(items, key); + pages.push(collectionsStateMap); + } + + const collectionsStateMap = { + __pages: { currentItem: pages }, + }; + + return collectionsStateMap as any; + } + __postAdd() { const um = this.em?.UndoManager; !this.__hasUm && um?.add(this);