Skip to content

Commit 21f51ae

Browse files
Add Components dynamic values ( props - attributes ) (#6351)
1 parent b60bf5f commit 21f51ae

File tree

17 files changed

+1131
-771
lines changed

17 files changed

+1131
-771
lines changed

packages/core/src/data_sources/model/DataVariableListenerManager.ts

+5-9
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,11 @@ import { stringToPath } from '../../utils/mixins';
33
import { Model } from '../../common';
44
import EditorModel from '../../editor/model/Editor';
55
import DataVariable, { DataVariableType } from './DataVariable';
6-
import ComponentView from '../../dom_components/view/ComponentView';
76
import { DynamicValue } from '../types';
87
import { DataCondition, ConditionalVariableType } from './conditional_variables/DataCondition';
98
import ComponentDataVariable from './ComponentDataVariable';
109

1110
export interface DynamicVariableListenerManagerOptions {
12-
model: Model | ComponentView;
1311
em: EditorModel;
1412
dataVariable: DynamicValue;
1513
updateValueFromDataVariable: (value: any) => void;
@@ -18,13 +16,12 @@ export interface DynamicVariableListenerManagerOptions {
1816
export default class DynamicVariableListenerManager {
1917
private dataListeners: DataVariableListener[] = [];
2018
private em: EditorModel;
21-
private model: Model | ComponentView;
22-
private dynamicVariable: DynamicValue;
19+
dynamicVariable: DynamicValue;
2320
private updateValueFromDynamicVariable: (value: any) => void;
21+
private model = new Model();
2422

2523
constructor(options: DynamicVariableListenerManagerOptions) {
2624
this.em = options.em;
27-
this.model = options.model;
2825
this.dynamicVariable = options.dataVariable;
2926
this.updateValueFromDynamicVariable = options.updateValueFromDataVariable;
3027

@@ -37,7 +34,7 @@ export default class DynamicVariableListenerManager {
3734
};
3835

3936
listenToDynamicVariable() {
40-
const { em, dynamicVariable, model } = this;
37+
const { em, dynamicVariable } = this;
4138
this.removeListeners();
4239

4340
// @ts-ignore
@@ -51,7 +48,7 @@ export default class DynamicVariableListenerManager {
5148
dataListeners = this.listenToConditionalVariable(dynamicVariable as DataCondition, em);
5249
break;
5350
}
54-
dataListeners.forEach((ls) => model.listenTo(ls.obj, ls.event, this.onChange));
51+
dataListeners.forEach((ls) => this.model.listenTo(ls.obj, ls.event, this.onChange));
5552

5653
this.dataListeners = dataListeners;
5754
}
@@ -81,8 +78,7 @@ export default class DynamicVariableListenerManager {
8178
}
8279

8380
private removeListeners() {
84-
const { model } = this;
85-
this.dataListeners.forEach((ls) => model.stopListening(ls.obj, ls.event, this.onChange));
81+
this.dataListeners.forEach((ls) => this.model.stopListening(ls.obj, ls.event, this.onChange));
8682
this.dataListeners = [];
8783
}
8884

packages/core/src/data_sources/model/conditional_variables/DataCondition.ts

-1
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,6 @@ export class DataCondition extends Model<DataConditionType> {
9494
dataVariables.forEach((variable) => {
9595
const variableInstance = new DataVariable(variable, { em: this.em });
9696
const listener = new DynamicVariableListenerManager({
97-
model: this as any,
9897
em: this.em!,
9998
dataVariable: variableInstance,
10099
updateValueFromDataVariable: (() => {

packages/core/src/data_sources/model/utils.ts

+27-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { ConditionalVariableType, DataCondition } from './conditional_variables/
44
import DataVariable, { DataVariableType } from './DataVariable';
55

66
export function isDynamicValueDefinition(value: any): value is DynamicValueDefinition {
7-
return typeof value === 'object' && [DataVariableType, ConditionalVariableType].includes(value.type);
7+
return typeof value === 'object' && [DataVariableType, ConditionalVariableType].includes(value?.type);
88
}
99

1010
export function isDynamicValue(value: any): value is DynamicValue {
@@ -22,3 +22,29 @@ export function isDataCondition(variable: any) {
2222
export function evaluateVariable(variable: any, em: EditorModel) {
2323
return isDataVariable(variable) ? new DataVariable(variable, { em }).getDataValue() : variable;
2424
}
25+
26+
export function getDynamicValueInstance(valueDefinition: DynamicValueDefinition, em: EditorModel): DynamicValue {
27+
const dynamicType = valueDefinition.type;
28+
let dynamicVariable: DynamicValue;
29+
30+
switch (dynamicType) {
31+
case DataVariableType:
32+
dynamicVariable = new DataVariable(valueDefinition, { em: em });
33+
break;
34+
case ConditionalVariableType: {
35+
const { condition, ifTrue, ifFalse } = valueDefinition;
36+
dynamicVariable = new DataCondition(condition, ifTrue, ifFalse, { em: em });
37+
break;
38+
}
39+
default:
40+
throw new Error(`Unsupported dynamic type: ${dynamicType}`);
41+
}
42+
43+
return dynamicVariable;
44+
}
45+
46+
export function evaluateDynamicValueDefinition(valueDefinition: DynamicValueDefinition, em: EditorModel) {
47+
const dynamicVariable = getDynamicValueInstance(valueDefinition, em);
48+
49+
return { variable: dynamicVariable, value: dynamicVariable.getDataValue() };
50+
}

packages/core/src/data_sources/view/ComponentDataVariableView.ts

-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ export default class ComponentDataVariableView extends ComponentView<ComponentDa
88
initialize(opt = {}) {
99
super.initialize(opt);
1010
this.dynamicVariableListener = new DynamicVariableListenerManager({
11-
model: this,
1211
em: this.em!,
1312
dataVariable: this.model,
1413
updateValueFromDataVariable: () => this.postRender(),

packages/core/src/dom_components/model/Component.ts

+79-46
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
} from 'underscore';
1414
import { shallowDiff, capitalize, isEmptyObj, isObject, toLowerCase } from '../../utils/mixins';
1515
import StyleableModel, { StyleProps, UpdateStyleOptions } from '../../domain_abstract/model/StyleableModel';
16-
import { Model } from 'backbone';
16+
import { Model, ModelDestroyOptions } from 'backbone';
1717
import Components from './Components';
1818
import Selector from '../../selector_manager/model/Selector';
1919
import Selectors from '../../selector_manager/model/Selectors';
@@ -51,14 +51,17 @@ import {
5151
updateSymbolComps,
5252
updateSymbolProps,
5353
} from './SymbolUtils';
54-
import TraitDataVariable from '../../data_sources/model/TraitDataVariable';
55-
import { ConditionalVariableType, DataCondition } from '../../data_sources/model/conditional_variables/DataCondition';
56-
import { isDynamicValue, isDynamicValueDefinition } from '../../data_sources/model/utils';
54+
import { ComponentDynamicValueWatcher } from './ComponentDynamicValueWatcher';
55+
import { DynamicValueWatcher } from './DynamicValueWatcher';
5756
import { DynamicValueDefinition } from '../../data_sources/types';
5857

5958
export interface IComponent extends ExtractMethods<Component> {}
60-
61-
export interface SetAttrOptions extends SetOptions, UpdateStyleOptions {}
59+
export interface DynamicWatchersOptions {
60+
skipWatcherUpdates?: boolean;
61+
fromDataSource?: boolean;
62+
}
63+
export interface SetAttrOptions extends SetOptions, UpdateStyleOptions, DynamicWatchersOptions {}
64+
export interface ComponentSetOptions extends SetOptions, DynamicWatchersOptions {}
6265

6366
const escapeRegExp = (str: string) => {
6467
return str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
@@ -72,7 +75,6 @@ export const keySymbol = '__symbol';
7275
export const keySymbolOvrd = '__symbol_ovrd';
7376
export const keyUpdate = ComponentsEvents.update;
7477
export const keyUpdateInside = ComponentsEvents.updateInside;
75-
export const dynamicAttrKey = 'attributes-dynamic-value';
7678

7779
/**
7880
* The Component object represents a single node of our template structure, so when you update its properties the changes are
@@ -260,9 +262,13 @@ export default class Component extends StyleableModel<ComponentProperties> {
260262
* @private
261263
* @ts-ignore */
262264
collection!: Components;
265+
componentDVListener: ComponentDynamicValueWatcher;
263266

264267
constructor(props: ComponentProperties = {}, opt: ComponentOptions) {
265268
super(props, opt);
269+
this.componentDVListener = new ComponentDynamicValueWatcher(this, opt.em);
270+
this.componentDVListener.addProps(props);
271+
266272
bindAll(this, '__upSymbProps', '__upSymbCls', '__upSymbComps');
267273
const em = opt.em;
268274

@@ -289,7 +295,7 @@ export default class Component extends StyleableModel<ComponentProperties> {
289295
this.opt = opt;
290296
this.em = em!;
291297
this.config = opt.config || {};
292-
this.set('attributes', {
298+
this.setAttributes({
293299
...(result(this, 'defaults').attributes || {}),
294300
...(this.get('attributes') || {}),
295301
});
@@ -331,6 +337,36 @@ export default class Component extends StyleableModel<ComponentProperties> {
331337
}
332338
}
333339

340+
set<A extends string>(
341+
keyOrAttributes: A | Partial<ComponentProperties>,
342+
valueOrOptions?: ComponentProperties[A] | ComponentSetOptions,
343+
optionsOrUndefined?: ComponentSetOptions,
344+
): this {
345+
let attributes: Partial<ComponentProperties>;
346+
let options: ComponentSetOptions = { skipWatcherUpdates: false, fromDataSource: false };
347+
if (typeof keyOrAttributes === 'object') {
348+
attributes = keyOrAttributes;
349+
options = valueOrOptions || (options as ComponentSetOptions);
350+
} else if (typeof keyOrAttributes === 'string') {
351+
attributes = { [keyOrAttributes as string]: valueOrOptions };
352+
options = optionsOrUndefined || options;
353+
} else {
354+
attributes = {};
355+
options = optionsOrUndefined || options;
356+
}
357+
358+
// @ts-ignore
359+
const em = this.em || options.em;
360+
const evaluatedAttributes = DynamicValueWatcher.getStaticValues(attributes, em);
361+
362+
const shouldSkipWatcherUpdates = options.skipWatcherUpdates || options.fromDataSource;
363+
if (!shouldSkipWatcherUpdates) {
364+
this.componentDVListener?.addProps(attributes);
365+
}
366+
367+
return super.set(evaluatedAttributes, options);
368+
}
369+
334370
__postAdd(opts: { recursive?: boolean } = {}) {
335371
const { em } = this;
336372
const um = em?.UndoManager;
@@ -648,8 +684,16 @@ export default class Component extends StyleableModel<ComponentProperties> {
648684
* @example
649685
* component.setAttributes({ id: 'test', 'data-key': 'value' });
650686
*/
651-
setAttributes(attrs: ObjectAny, opts: SetAttrOptions = {}) {
652-
this.set('attributes', { ...attrs }, opts);
687+
setAttributes(attrs: ObjectAny, opts: SetAttrOptions = { skipWatcherUpdates: false, fromDataSource: false }) {
688+
// @ts-ignore
689+
const em = this.em || opts.em;
690+
const evaluatedAttributes = DynamicValueWatcher.getStaticValues(attrs, em);
691+
const shouldSkipWatcherUpdates = opts.skipWatcherUpdates || opts.fromDataSource;
692+
if (!shouldSkipWatcherUpdates) {
693+
this.componentDVListener.setAttributes(attrs);
694+
}
695+
this.set('attributes', { ...evaluatedAttributes }, opts);
696+
653697
return this;
654698
}
655699

@@ -662,9 +706,11 @@ export default class Component extends StyleableModel<ComponentProperties> {
662706
* component.addAttributes({ 'data-key': 'value' });
663707
*/
664708
addAttributes(attrs: ObjectAny, opts: SetAttrOptions = {}) {
709+
const dynamicAttributes = this.componentDVListener.getDynamicAttributesDefs();
665710
return this.setAttributes(
666711
{
667712
...this.getAttributes({ noClass: true }),
713+
...dynamicAttributes,
668714
...attrs,
669715
},
670716
opts,
@@ -682,6 +728,8 @@ export default class Component extends StyleableModel<ComponentProperties> {
682728
*/
683729
removeAttributes(attrs: string | string[] = [], opts: SetOptions = {}) {
684730
const attrArr = Array.isArray(attrs) ? attrs : [attrs];
731+
this.componentDVListener.removeAttributes(attrArr);
732+
685733
const compAttr = this.getAttributes();
686734
attrArr.map((i) => delete compAttr[i]);
687735
return this.setAttributes(compAttr, opts);
@@ -773,29 +821,6 @@ export default class Component extends StyleableModel<ComponentProperties> {
773821
}
774822
}
775823

776-
const attrDataVariable = this.get(dynamicAttrKey) as {
777-
[key: string]: TraitDataVariable | DynamicValueDefinition;
778-
};
779-
if (attrDataVariable) {
780-
Object.entries(attrDataVariable).forEach(([key, value]) => {
781-
let dataVariable: TraitDataVariable | DataCondition;
782-
if (isDynamicValue(value)) {
783-
dataVariable = value;
784-
} else if (isDynamicValueDefinition(value)) {
785-
const type = value.type;
786-
787-
if (type === ConditionalVariableType) {
788-
const { condition, ifTrue, ifFalse } = value;
789-
dataVariable = new DataCondition(condition, ifTrue, ifFalse, { em });
790-
} else {
791-
dataVariable = new TraitDataVariable(value, { em });
792-
}
793-
}
794-
795-
attributes[key] = dataVariable!.getDataValue();
796-
});
797-
}
798-
799824
// Check if we need an ID on the component
800825
if (!has(attributes, 'id')) {
801826
let addId = false;
@@ -934,7 +959,6 @@ export default class Component extends StyleableModel<ComponentProperties> {
934959
this.off(event, this.initTraits);
935960
this.__loadTraits();
936961
const attrs = { ...this.get('attributes') };
937-
const traitDynamicValueAttr: ObjectAny = {};
938962
const traits = this.traits;
939963
traits.each((trait) => {
940964
const name = trait.getName();
@@ -945,13 +969,13 @@ export default class Component extends StyleableModel<ComponentProperties> {
945969
} else {
946970
if (name && value) attrs[name] = value;
947971
}
948-
949-
if (trait.dynamicVariable) {
950-
traitDynamicValueAttr[name] = trait.dynamicVariable;
951-
}
952972
});
953-
traits.length && this.set('attributes', attrs);
954-
Object.keys(traitDynamicValueAttr).length && this.set(dynamicAttrKey, traitDynamicValueAttr);
973+
const dynamicAttributes = this.componentDVListener.getDynamicAttributesDefs();
974+
traits.length &&
975+
this.setAttributes({
976+
...attrs,
977+
...dynamicAttributes,
978+
});
955979
this.on(event, this.initTraits);
956980
changed && em && em.trigger('component:toggled');
957981
return this;
@@ -1147,7 +1171,6 @@ export default class Component extends StyleableModel<ComponentProperties> {
11471171
traits.setTarget(this);
11481172

11491173
if (traitsI.length) {
1150-
traitsI.forEach((tr) => tr.attributes && delete tr.attributes.value);
11511174
traits.add(traitsI);
11521175
}
11531176

@@ -1294,12 +1317,15 @@ export default class Component extends StyleableModel<ComponentProperties> {
12941317
* @ts-ignore */
12951318
clone(opt: { symbol?: boolean; symbolInv?: boolean } = {}): this {
12961319
const em = this.em;
1297-
const attr = { ...this.attributes };
1320+
const attr = {
1321+
...this.componentDVListener.getPropsDefsOrValues(this.attributes),
1322+
};
12981323
const opts = { ...this.opt };
12991324
const id = this.getId();
13001325
const cssc = em?.Css;
1301-
attr.attributes = { ...attr.attributes };
1302-
delete attr.attributes.id;
1326+
attr.attributes = {
1327+
...(attr.attributes ? this.componentDVListener.getAttributesDefsOrValues(attr.attributes) : undefined),
1328+
};
13031329
// @ts-ignore
13041330
attr.components = [];
13051331
// @ts-ignore
@@ -1554,8 +1580,10 @@ export default class Component extends StyleableModel<ComponentProperties> {
15541580
* @private
15551581
*/
15561582
toJSON(opts: ObjectAny = {}): ComponentDefinition {
1557-
const obj = Model.prototype.toJSON.call(this, opts);
1558-
obj.attributes = this.getAttributes();
1583+
let obj = Model.prototype.toJSON.call(this, opts);
1584+
obj = { ...obj, ...this.componentDVListener.getDynamicPropsDefs() };
1585+
obj.attributes = this.componentDVListener.getAttributesDefsOrValues(this.getAttributes());
1586+
delete obj.componentDVListener;
15591587
delete obj.attributes.class;
15601588
delete obj.toolbar;
15611589
delete obj.traits;
@@ -1789,6 +1817,11 @@ export default class Component extends StyleableModel<ComponentProperties> {
17891817
return this;
17901818
}
17911819

1820+
destroy(options?: ModelDestroyOptions | undefined): false | JQueryXHR {
1821+
this.componentDVListener.destroy();
1822+
return super.destroy(options);
1823+
}
1824+
17921825
/**
17931826
* Move the component to another destination component
17941827
* @param {Component} component Destination component (so the current one will be appended as a child)

0 commit comments

Comments
 (0)