diff --git a/src/trait_manager/model/Trait.ts b/src/trait_manager/model/Trait.ts index b5a3a10eb..0065be55c 100644 --- a/src/trait_manager/model/Trait.ts +++ b/src/trait_manager/model/Trait.ts @@ -1,12 +1,14 @@ -import { isString, isUndefined } from 'underscore'; +import { isString, isUndefined, keys } 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 } from '../../utils/mixins'; +import { isDef, stringToPath } from '../../utils/mixins'; import TraitsEvents, { TraitGetValueOptions, TraitOption, TraitProperties, TraitSetValueOptions } from '../types'; import TraitView from '../view/TraitView'; import Traits from './Traits'; +import TraitDataVariable from './TraitDataVariable'; +import { DataSourcesEvents, DataVariableListener } from '../../data_sources/types'; /** * @property {String} id Trait id, eg. `my-trait-id`. @@ -26,6 +28,8 @@ export default class Trait extends Model { em: EditorModel; view?: TraitView; el?: HTMLElement; + dataListeners: DataVariableListener[] = []; + dataVariable?: TraitDataVariable; defaults() { return { @@ -51,6 +55,18 @@ export default class Trait extends Model { this.setTarget(target); } this.em = em; + + if ( + this.attributes.value && + typeof this.attributes.value === 'object' && + this.attributes.value.type === 'data-variable' + ) { + this.dataVariable = new TraitDataVariable(this.attributes.value, { em: this.em, trait: this }); + + const dv = this.dataVariable.getDataValue(); + this.set({ value: dv }); + this.listenToDataVariable(this.dataVariable); + } } get parent() { @@ -85,6 +101,32 @@ 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 || []; + + prevListeners.forEach(ls => this.stopListening(ls.obj, ls.event, this.updateValueFromDataVariable)); + + 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 dr = dataVar.getDataValue(); + this.updateValueFromDataVariable(dr); + }) + ); + this.dataListeners = dataListeners; + } + + updateValueFromDataVariable(value: string) { + this.setTargetValue(value); + this.trigger('change:value'); + } + /** * Get the trait id. * @returns {String} @@ -130,6 +172,12 @@ export default class Trait extends Model { * @returns {any} */ getValue(opts?: TraitGetValueOptions) { + if (this.dataVariable) { + const dValue = this.dataVariable.getDataValue(); + + return dValue; + } + return this.getTargetValue(opts); } @@ -146,6 +194,11 @@ export default class Trait extends Model { const valueOpts: { avoidStore?: boolean } = {}; const { setValue } = this.attributes; + // if (value && typeof value === 'object' && value.type === 'data-variable') { + // value = new TraitDataVariable(value, { em: this.em, trait: this }).initialize(); + // this.listenToDataVariable(value); + // } + if (setValue) { setValue({ value, diff --git a/src/trait_manager/model/TraitDataVariable.ts b/src/trait_manager/model/TraitDataVariable.ts new file mode 100644 index 000000000..bcd8daa4d --- /dev/null +++ b/src/trait_manager/model/TraitDataVariable.ts @@ -0,0 +1,51 @@ +import { Model } from '../../common'; +import EditorModel from '../../editor/model/Editor'; +import { stringToPath } from '../../utils/mixins'; +import Trait from './Trait'; + +export default class TraitDataVariable extends Model { + em?: EditorModel; + trait?: Trait; + + defaults() { + return { + type: 'data-variable', + value: '', + path: '', + }; + } + + initialize(attrs: any, options: any) { + super.initialize(attrs, options); + this.em = options.em; + this.trait = options.trait; + + this.listenToDataSource(); + + return this; + } + + listenToDataSource() { + const { path } = this.attributes; + const resolvedPath = stringToPath(path).join('.'); + + if (this.em) { + this.listenTo(this.em.DataSources, `change:${resolvedPath}`, this.onDataSourceChange); + } + } + + getDataValue() { + const { path } = this.attributes; + const [dsId, drId, key] = stringToPath(path); + const ds = this?.em?.DataSources.get(dsId); + const dr = ds && ds.getRecord(drId); + const dv = dr?.get(key); + + return dv; + } + + onDataSourceChange() { + const dv = this.getDataValue(); + this?.trait?.setTargetValue(dv); + } +} diff --git a/test/specs/data_sources/index.ts b/test/specs/data_sources/index.ts index a7fbb11f2..acee0b64c 100644 --- a/test/specs/data_sources/index.ts +++ b/test/specs/data_sources/index.ts @@ -298,6 +298,55 @@ describe('DataSourceManager', () => { }); }); + describe('Traits', () => { + let fixtures: HTMLElement; + let cmpRoot: ComponentWrapper; + + beforeEach(() => { + document.body.innerHTML = '
'; + const { Pages, Components } = em; + Pages.onLoad(); + cmpRoot = Components.getWrapper()!; + const View = Components.getType('wrapper')!.view; + const wrapperEl = new View({ + model: cmpRoot, + config: { ...cmpRoot.config, em }, + }); + wrapperEl.render(); + fixtures = document.body.querySelector('#fixtures')!; + fixtures.appendChild(wrapperEl.el); + }); + + test('component initializes with data-variable trait input', () => { + const inputDataSource: DataSourceProps = { + id: 'test-input', + records: [{ id: 'id1', value: 'test-value' }], + }; + dsm.add(inputDataSource); + + const cmp = cmpRoot.append({ + tagName: 'input', + traits: [ + 'name', + 'type', + { + type: 'text', + label: 'Value', + name: 'value', + value: { + type: 'data-variable', + value: 'default', + path: 'test-input.id1.value', + }, + }, + ], + })[0]; + + const input = cmp.getEl(); + expect(input?.getAttribute('value')).toBe('test-value'); + }); + }); + test('add DataSource with records', () => { const eventAdd = jest.fn(); em.on(dsm.events.add, eventAdd);