diff --git a/packages/core/src/data_sources/model/utils.ts b/packages/core/src/data_sources/model/utils.ts index fe835cb68..c3d72aa37 100644 --- a/packages/core/src/data_sources/model/utils.ts +++ b/packages/core/src/data_sources/model/utils.ts @@ -22,3 +22,29 @@ export function isDataCondition(variable: any) { export function evaluateVariable(variable: any, em: EditorModel) { return isDataVariable(variable) ? new DataVariable(variable, { em }).getDataValue() : variable; } + +export function getDynamicValueInstance(valueDefinition: DynamicValueDefinition, em: EditorModel): DynamicValue { + const dynamicType = valueDefinition.type; + let dynamicVariable: DynamicValue; + + switch (dynamicType) { + case DataVariableType: + dynamicVariable = new DataVariable(valueDefinition, { em: em }); + break; + case ConditionalVariableType: { + const { condition, ifTrue, ifFalse } = valueDefinition; + dynamicVariable = new DataCondition(condition, ifTrue, ifFalse, { em: em }); + break; + } + default: + throw new Error(`Unsupported dynamic type: ${dynamicType}`); + } + + return dynamicVariable; +} + +export function evaluateDynamicValueDefinition(valueDefinition: DynamicValueDefinition, em: EditorModel) { + const dynamicVariable = getDynamicValueInstance(valueDefinition, em); + + return { variable: dynamicVariable, value: dynamicVariable.getDataValue() }; +} \ No newline at end of file diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index 0eef03188..0b0127947 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -675,16 +675,15 @@ export default class Component extends StyleableModel { * component.setAttributes({ id: 'test', 'data-key': 'value' }); */ setAttributes(attrs: ObjectAny, opts: SetAttrOptions = {}) { - const attributes = this.dynamicVariableListenersOBJ.attributes; - const keys = Object.keys(attributes) - keys.forEach(attribute => { + const { propsToWatch, evaluatedProps } = evaluatePropsBeforeInstantiation(attrs, this.em); + const attrArr = Object.keys(this.dynamicVariableListenersOBJ.attributes); + attrArr.filter(attribute => attrs[attribute] !== undefined).forEach(attribute => { this.dynamicVariableListenersOBJ.attributes[attribute].destroy(); delete this.dynamicVariableListenersOBJ.attributes[attribute]; }); - - this.set('attributes', { ...attrs }, opts); - const { propsToWatch } = evaluatePropsBeforeInstantiation(attrs, this.em); this.watchAttributes(propsToWatch); + this.set('attributes', { ...evaluatedProps }, opts); + return this; } @@ -697,9 +696,6 @@ export default class Component extends StyleableModel { * component.addAttributes({ 'data-key': 'value' }); */ addAttributes(attrs: ObjectAny, opts: SetAttrOptions = {}) { - const { propsToWatch } = evaluatePropsBeforeInstantiation(attrs, this.em); - this.watchAttributes(propsToWatch); - return this.setAttributes( { ...this.getAttributes({ noClass: true }), diff --git a/packages/core/src/trait_manager/model/Trait.ts b/packages/core/src/trait_manager/model/Trait.ts index 49575e9c9..c3d17b889 100644 --- a/packages/core/src/trait_manager/model/Trait.ts +++ b/packages/core/src/trait_manager/model/Trait.ts @@ -383,7 +383,6 @@ export default class Trait extends Model { } getInitValue() { - if (this.hasDynamicValue()) return this.getDynamicValue(); const { component } = this; const name = this.getName(); let value; diff --git a/packages/core/test/specs/data_sources/dynamic_values/attributes.ts b/packages/core/test/specs/data_sources/dynamic_values/attributes.ts new file mode 100644 index 000000000..d946af360 --- /dev/null +++ b/packages/core/test/specs/data_sources/dynamic_values/attributes.ts @@ -0,0 +1,213 @@ +import Editor from '../../../../src/editor/model/Editor'; +import DataSourceManager from '../../../../src/data_sources'; +import ComponentWrapper from '../../../../src/dom_components/model/ComponentWrapper'; +import { DataVariableType } from '../../../../src/data_sources/model/DataVariable'; +import { setupTestEditor } from '../../../common'; +import { Component } from '../../../../src'; + +const staticAttributeValue = 'some tiltle'; +describe('TraitDataVariable', () => { + let em: Editor; + let dsm: DataSourceManager; + let cmpRoot: ComponentWrapper; + const staticAttributes = { + staticAttribute: staticAttributeValue, + } + + beforeEach(() => { + ({ em, dsm, cmpRoot } = setupTestEditor()); + }); + + afterEach(() => { + em.destroy(); + }); + + describe('dynamic attributes', () => { + test('static and dynamic attributes', () => { + const inputDataSource = { + id: 'ds_id', + records: [{ id: 'id1', value: 'test-value' }], + }; + dsm.add(inputDataSource); + + const attributes = { + ...staticAttributes, + dynamicAttribute: { + type: DataVariableType, + defaultValue: 'default', + path: "ds_id.id1.value", + } + } + const cmp = cmpRoot.append({ + tagName: 'input', + attributes, + })[0]; + + testAttribute(cmp, 'dynamicAttribute', 'test-value'); + testStaticAttributes(cmp); + }); + + test('dynamic attributes should listen to change', () => { + const dataSource = { + id: 'ds_id', + records: [{ id: 'id1', value: 'test-value' }], + }; + dsm.add(dataSource); + + const attributes = { + ...staticAttributes, + dynamicAttribute: { + type: DataVariableType, + defaultValue: 'default', + path: "ds_id.id1.value", + } + } + const cmp = cmpRoot.append({ + tagName: 'input', + attributes, + })[0]; + + testAttribute(cmp, 'dynamicAttribute', 'test-value'); + testStaticAttributes(cmp); + + changeDataSourceValue(dsm); + testAttribute(cmp, 'dynamicAttribute', 'changed-value'); + }); + + test('dynamic attributes should listen to the latest dynamic value', () => { + const dataSource = { + id: 'ds_id', + records: [ + { id: 'id1', value: 'test-value' }, + { id: 'id2', value: 'test-value' }, + ], + }; + dsm.add(dataSource); + + const attributes = { + ...staticAttributes, + dynamicAttribute: { + type: DataVariableType, + defaultValue: 'default', + path: "ds_id.id1.value", + } + } + const cmp = cmpRoot.append({ + tagName: 'input', + attributes, + })[0]; + + cmp.addAttributes({ + dynamicAttribute: { + type: DataVariableType, + defaultValue: 'default', + path: "ds_id.id2.value", + } + }); + changeDataSourceValue(dsm); + testAttribute(cmp, 'dynamicAttribute', 'test-value'); + }); + + test('dynamic attributes should stop listening to change if the value changed to static', () => { + const dataSource = { + id: 'ds_id', + records: [{ id: 'id1', value: 'test-value' }], + }; + dsm.add(dataSource); + + const attributes = { + ...staticAttributes, + dynamicAttribute: { + type: DataVariableType, + defaultValue: 'default', + path: "ds_id.id1.value", + } + } + const cmp = cmpRoot.append({ + tagName: 'input', + attributes, + })[0]; + + testAttribute(cmp, 'dynamicAttribute', 'test-value'); + testStaticAttributes(cmp); + + cmp.setAttributes({ + dynamicAttribute: 'static-value' + }) + changeDataSourceValue(dsm); + testAttribute(cmp, 'dynamicAttribute', 'static-value'); + }); + + test('dynamic attributes should stop listening to change if the value changed to dynamic value', () => { + const dataSource = { + id: 'ds_id', + records: [{ id: 'id1', value: 'test-value' }], + }; + dsm.add(dataSource); + + const attributes = { + ...staticAttributes, + dynamicAttribute: 'static-value' + } + const cmp = cmpRoot.append({ + tagName: 'input', + attributes, + })[0]; + + cmp.setAttributes({ + dynamicAttribute: { + type: DataVariableType, + defaultValue: 'default', + path: "ds_id.id1.value", + } + }) + testAttribute(cmp, 'dynamicAttribute', 'test-value'); + changeDataSourceValue(dsm); + testAttribute(cmp, 'dynamicAttribute', 'changed-value'); + }); + + test('dynamic attributes should stop listening to change if the attribute was removed', () => { + const dataSource = { + id: 'ds_id', + records: [{ id: 'id1', value: 'test-value' }], + }; + dsm.add(dataSource); + + const attributes = { + ...staticAttributes, + dynamicAttribute: { + type: DataVariableType, + defaultValue: 'default', + path: "ds_id.id1.value", + } + } + const cmp = cmpRoot.append({ + tagName: 'input', + attributes, + })[0]; + + testAttribute(cmp, 'dynamicAttribute', 'test-value'); + testStaticAttributes(cmp); + + cmp.removeAttributes('dynamicAttribute'); + changeDataSourceValue(dsm); + expect(cmp?.getAttributes()['dynamicAttribute']).toBe(undefined); + const input = cmp.getEl(); + expect(input?.getAttribute('dynamicAttribute')).toBe(null); + }); + }); +}); + +function changeDataSourceValue(dsm: DataSourceManager) { + dsm.get('ds_id').getRecord('id1')?.set('value', 'changed-value') +} + +function testStaticAttributes(cmp: Component) { + testAttribute(cmp, 'staticAttribute', staticAttributeValue) +} + +function testAttribute(cmp: Component, attribute: string, value: string) { + expect(cmp?.getAttributes()[attribute]).toBe(value); + const input = cmp.getEl(); + expect(input?.getAttribute(attribute)).toBe(value); +}