From 843492fbc7c6115b980d8e9dbcc47ffe8bb33b9a Mon Sep 17 00:00:00 2001 From: danstarns Date: Wed, 28 Aug 2024 23:11:20 -0700 Subject: [PATCH] feat: reuse data listeners for style and traits --- .../model/DataVariableListenerManager.ts | 60 +++++++++++++++++++ src/domain_abstract/model/StyleableModel.ts | 60 +++++++++---------- src/trait_manager/model/Trait.ts | 45 ++++---------- 3 files changed, 99 insertions(+), 66 deletions(-) create mode 100644 src/data_sources/model/DataVariableListenerManager.ts diff --git a/src/data_sources/model/DataVariableListenerManager.ts b/src/data_sources/model/DataVariableListenerManager.ts new file mode 100644 index 000000000..0caf56b98 --- /dev/null +++ b/src/data_sources/model/DataVariableListenerManager.ts @@ -0,0 +1,60 @@ +import { DataSourcesEvents, DataVariableListener } from '../../data_sources/types'; +import { stringToPath } from '../../utils/mixins'; +import { Model } from '../../common'; +import EditorModel from '../../editor/model/Editor'; +import DataVariable from './DataVariable'; + +export interface DataVariableListenerManagerOptions { + model: Model; + em: EditorModel; + dataVariable: DataVariable; + updateValueFromDataVariable: (value: any) => void; +} + +export default class DataVariableListenerManager { + private dataListeners: DataVariableListener[] = []; + private em: EditorModel; + private model: Model; + private dataVariable: DataVariable; + private updateValueFromDataVariable: (value: any) => void; + + constructor(options: DataVariableListenerManagerOptions) { + this.em = options.em; + this.model = options.model; + this.dataVariable = options.dataVariable; + this.updateValueFromDataVariable = options.updateValueFromDataVariable; + + this.listenToDataVariable(); + } + + listenToDataVariable() { + const { em, dataVariable, model, updateValueFromDataVariable } = this; + const { path } = dataVariable.attributes; + const normPath = stringToPath(path || '').join('.'); + const prevListeners = this.dataListeners || []; + const [ds, dr] = this.em.DataSources.fromPath(path); + + prevListeners.forEach((ls) => model.stopListening(ls.obj, ls.event, updateValueFromDataVariable)); + + const dataListeners: DataVariableListener[] = []; + ds && dataListeners.push({ obj: ds.records, event: 'add remove reset' }); + dr && dataListeners.push({ obj: dr, event: 'change' }); + dataListeners.push({ obj: dataVariable, event: 'change:value' }); + dataListeners.push({ obj: em, event: `${DataSourcesEvents.path}:${normPath}` }); + dataListeners.push( + { obj: dataVariable, event: 'change:path change:value' }, + { obj: em.DataSources.all, event: 'add remove reset' }, + { obj: em, event: `${DataSourcesEvents.path}:${normPath}` }, + ); + + dataListeners.forEach((ls) => + model.listenTo(ls.obj, ls.event, () => { + const value = dataVariable.getDataValue(); + + updateValueFromDataVariable(value); + }), + ); + + this.dataListeners = dataListeners; + } +} diff --git a/src/domain_abstract/model/StyleableModel.ts b/src/domain_abstract/model/StyleableModel.ts index 840557e7c..05d4579c3 100644 --- a/src/domain_abstract/model/StyleableModel.ts +++ b/src/domain_abstract/model/StyleableModel.ts @@ -2,11 +2,11 @@ import { isArray, isString, keys } from 'underscore'; import { Model, ObjectAny, ObjectHash, SetOptions } from '../../common'; import ParserHtml from '../../parser/model/ParserHtml'; import Selectors from '../../selector_manager/model/Selectors'; -import { shallowDiff, stringToPath } from '../../utils/mixins'; +import { shallowDiff } from '../../utils/mixins'; import EditorModel from '../../editor/model/Editor'; import StyleDataVariable from '../../data_sources/model/StyleDataVariable'; -import { DataSourcesEvents, DataVariableListener } from '../../data_sources/types'; import { DataVariableType } from '../../data_sources/model/DataVariable'; +import DataVariableListenerManager from '../../data_sources/model/DataVariableListenerManager'; export type StyleProps = Record< string, @@ -34,10 +34,10 @@ export const getLastStyleValue = (value: string | string[]) => { export default class StyleableModel extends Model { em?: EditorModel; - dataListeners: DataVariableListener[] = []; + dataVariableListeners: Record = {}; /** - * Forward style string to `parseStyle` to be parse to an object + * Parse style string to an object * @param {string} str * @returns */ @@ -46,8 +46,7 @@ export default class StyleableModel extends Model } /** - * To trigger the style change event on models I have to - * pass a new object instance + * Trigger style change event with a new object instance * @param {Object} prop * @return {Object} */ @@ -97,13 +96,14 @@ export default class StyleableModel extends Model // Remove empty style properties if (newStyle[key] === '') { delete newStyle[key]; - return; } const styleValue = newStyle[key]; if (typeof styleValue === 'object' && styleValue.type === DataVariableType) { - newStyle[key] = new StyleDataVariable(styleValue, { em: this.em }); + const styleDataVariable = new StyleDataVariable(styleValue, { em: this.em }); + newStyle[key] = styleDataVariable; + this.manageDataVariableListener(styleDataVariable, key); } }); @@ -123,38 +123,31 @@ export default class StyleableModel extends Model if (em) { em.trigger('styleable:change', this, pr, opts); em.trigger(`styleable:change:${pr}`, this, pr, opts); - - const styleValue = newStyle[pr]; - if (styleValue instanceof StyleDataVariable) { - this.listenToDataVariable(styleValue, pr); - } } }); return newStyle; } - listenToDataVariable(dataVar: StyleDataVariable, styleProp: string) { - const { em } = this; - const { path } = dataVar.attributes; - const normPath = stringToPath(path || '').join('.'); - const dataListeners: DataVariableListener[] = []; - const prevListeners = this.dataListeners || []; - - prevListeners.forEach((ls) => this.stopListening(ls.obj, ls.event, this.updateStyleProp)); - - dataListeners.push({ obj: dataVar, event: 'change:value' }); - dataListeners.push({ obj: em, event: `${DataSourcesEvents.path}:${normPath}` }); - - dataListeners.forEach((ls) => - this.listenTo(ls.obj, ls.event, () => { - const newValue = dataVar.getDataValue(); - this.updateStyleProp(styleProp, newValue); - }), - ); - this.dataListeners = dataListeners; + /** + * Manage DataVariableListenerManager for a style property + */ + manageDataVariableListener(dataVar: StyleDataVariable, styleProp: string) { + if (this.dataVariableListeners[styleProp]) { + this.dataVariableListeners[styleProp].listenToDataVariable(); + } else { + this.dataVariableListeners[styleProp] = new DataVariableListenerManager({ + model: this, + em: this.em!, + dataVariable: dataVar, + updateValueFromDataVariable: (newValue: string) => this.updateStyleProp(styleProp, newValue), + }); + } } + /** + * Update a specific style property + */ updateStyleProp(prop: string, value: string) { const style = this.getStyle(); style[prop] = value; @@ -162,6 +155,9 @@ export default class StyleableModel extends Model this.trigger(`change:style:${prop}`); } + /** + * Resolve data variables to their actual values + */ resolveDataVariables(style: StyleProps): StyleProps { const resolvedStyle = { ...style }; keys(resolvedStyle).forEach((key) => { diff --git a/src/trait_manager/model/Trait.ts b/src/trait_manager/model/Trait.ts index f8287a951..c03779305 100644 --- a/src/trait_manager/model/Trait.ts +++ b/src/trait_manager/model/Trait.ts @@ -1,15 +1,16 @@ -import { isString, isUndefined, keys } from 'underscore'; +import { isString, isUndefined } from 'underscore'; import Category from '../../abstract/ModuleCategory'; import { LocaleOptions, Model, SetOptions } from '../../common'; import Component from '../../dom_components/model/Component'; import EditorModel from '../../editor/model/Editor'; -import { isDef, stringToPath } from '../../utils/mixins'; +import { isDef } from '../../utils/mixins'; import TraitsEvents, { TraitGetValueOptions, TraitOption, TraitProperties, TraitSetValueOptions } from '../types'; import TraitView from '../view/TraitView'; import Traits from './Traits'; -import { DataSourcesEvents, DataVariableListener } from '../../data_sources/types'; +import { DataVariableListener } from '../../data_sources/types'; import TraitDataVariable from '../../data_sources/model/TraitDataVariable'; import { DataVariableType } from '../../data_sources/model/DataVariable'; +import DataVariableListenerManager from '../../data_sources/model/DataVariableListenerManager'; /** * @property {String} id Trait id, eg. `my-trait-id`. @@ -31,6 +32,7 @@ export default class Trait extends Model { el?: HTMLElement; dataListeners: DataVariableListener[] = []; dataVariable?: TraitDataVariable; + dataVariableListener?: DataVariableListenerManager; defaults() { return { @@ -66,7 +68,12 @@ export default class Trait extends Model { const dv = this.dataVariable.getDataValue(); this.set({ value: dv }); - this.listenToDataVariable(this.dataVariable); + this.dataVariableListener = new DataVariableListenerManager({ + model: this, + em: this.em, + dataVariable: this.dataVariable, + updateValueFromDataVariable: this.updateValueFromDataVariable.bind(this), + }); } } @@ -102,36 +109,6 @@ export default class Trait extends Model { } } - listenToDataVariable(dataVar: TraitDataVariable) { - const { em } = this; - const { path } = dataVar.attributes; - const normPath = stringToPath(path || '').join('.'); - const dataListeners: DataVariableListener[] = []; - const prevListeners = this.dataListeners || []; - const [ds, dr] = this.em.DataSources.fromPath(path); - - prevListeners.forEach((ls) => this.stopListening(ls.obj, ls.event, this.updateValueFromDataVariable)); - - ds && dataListeners.push({ obj: ds.records, event: 'add remove reset' }); - dr && dataListeners.push({ obj: dr, event: 'change' }); - dataListeners.push({ obj: dataVar, event: 'change:value' }); - dataListeners.push({ obj: em, event: `${DataSourcesEvents.path}:${normPath}` }); - - dataListeners.push( - { obj: dataVar, event: 'change:path change:value' }, - { obj: em.DataSources.all, event: 'add remove reset' }, - { obj: em, event: `${DataSourcesEvents.path}:${normPath}` }, - ); - - dataListeners.forEach((ls) => - this.listenTo(ls.obj, ls.event, () => { - const dr = dataVar.getDataValue(); - this.updateValueFromDataVariable(dr); - }), - ); - this.dataListeners = dataListeners; - } - updateValueFromDataVariable(value: string) { this.setTargetValue(value); this.trigger('change:value');