Skip to content

Commit a4f3d20

Browse files
Dynamic values improvements (#6419)
* Avoid throwing errors if no condition is passed to a DataCondition * Refactor Condition class * Remove expecting errors for conditions tests * Extend and refactor BaseOperator * Rename and refactor GenericOperator * Rename and refactor logicalOperator to BooleanOperator * Refactor StringOperator * Refactor NumberOperator * Refactor and fix DataResolverListener * Update tests for condition Operators * Rename Condition class to ConditionEvaluator * Add missing types file * Update and refactor DataCondition * Update utils * Refactor StyleableModel class * Update ComponentDataCondition * Refactor ComponentResolverWatcher * Fix conditional styles * Rename LogicalGroupStatement to LogicalGroupEvaluator * Fix tests for DataCondition * Add setter methods for component data variable * Add setters and getter to ComponentDataCondition * Add getters to ComponentDataVariable * Rename test file * Make dynamic components undroppable * Fix collection types * Update collections ( add setters and getter, first item editable, sync styles ) * Update data collection tests * Format tests * Fix some ComponentData collection bugs * Refactor setStartIndex and setEndIndex * Fix getComponentDef test * Fix bug with end index = 0 * fix test for setDataSource * fix test for HTML updates * Format * Add tests for the new option in getDataValue for conditions ( skipDynamicValueResolution ) * rename Operation to DataConditionOperation * Run __onStyleChange after style changes as before * Format * Up BooleanOperator --------- Co-authored-by: Artur Arseniev <[email protected]>
1 parent 60163cb commit a4f3d20

32 files changed

+1326
-498
lines changed

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export default class ComponentDataVariable extends Component {
1313
type: DataVariableType,
1414
path: '',
1515
defaultValue: '',
16+
droppable: false,
1617
};
1718
}
1819

@@ -22,6 +23,14 @@ export default class ComponentDataVariable extends Component {
2223
this.dataResolver = new DataVariable({ type, path, defaultValue }, opt);
2324
}
2425

26+
getPath() {
27+
return this.dataResolver.get('path');
28+
}
29+
30+
getDefaultValue() {
31+
return this.dataResolver.get('defaultValue');
32+
}
33+
2534
getDataValue() {
2635
return this.dataResolver.getDataValue();
2736
}
@@ -30,6 +39,14 @@ export default class ComponentDataVariable extends Component {
3039
return this.getDataValue();
3140
}
3241

42+
setPath(newPath: string) {
43+
this.dataResolver.set('path', newPath);
44+
}
45+
46+
setDefaultValue(newValue: string) {
47+
this.dataResolver.set('defaultValue', newValue);
48+
}
49+
3350
static isComponent(el: HTMLElement) {
3451
return toLowerCase(el.tagName) === DataVariableType;
3552
}

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

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,12 @@ export interface DataResolverListenerProps {
1414
onUpdate: (value: any) => void;
1515
}
1616

17+
interface ListenerWithCallback extends DataSourceListener {
18+
callback: () => void;
19+
}
20+
1721
export default class DataResolverListener {
18-
private listeners: DataSourceListener[] = [];
22+
private listeners: ListenerWithCallback[] = [];
1923
private em: EditorModel;
2024
private onUpdate: (value: any) => void;
2125
private model = new Model();
@@ -33,10 +37,14 @@ export default class DataResolverListener {
3337
this.onUpdate(value);
3438
};
3539

40+
private createListener(obj: any, event: string, callback: () => void = this.onChange): ListenerWithCallback {
41+
return { obj, event, callback };
42+
}
43+
3644
listenToResolver() {
3745
const { resolver, model } = this;
3846
this.removeListeners();
39-
let listeners: DataSourceListener[] = [];
47+
let listeners: ListenerWithCallback[] = [];
4048
const type = resolver.attributes.type;
4149

4250
switch (type) {
@@ -51,11 +59,11 @@ export default class DataResolverListener {
5159
break;
5260
}
5361

54-
listeners.forEach((ls) => model.listenTo(ls.obj, ls.event, this.onChange));
62+
listeners.forEach((ls) => model.listenTo(ls.obj, ls.event, ls.callback));
5563
this.listeners = listeners;
5664
}
5765

58-
private listenToConditionalVariable(dataVariable: DataCondition) {
66+
private listenToConditionalVariable(dataVariable: DataCondition): ListenerWithCallback[] {
5967
const { em } = this;
6068
const dataListeners = dataVariable.getDependentDataVariables().flatMap((dataVariable) => {
6169
return this.listenToDataVariable(new DataVariable(dataVariable, { em }));
@@ -64,29 +72,41 @@ export default class DataResolverListener {
6472
return dataListeners;
6573
}
6674

67-
private listenToDataVariable(dataVariable: DataVariable) {
75+
private listenToDataVariable(dataVariable: DataVariable): ListenerWithCallback[] {
6876
const { em } = this;
69-
const dataListeners: DataSourceListener[] = [];
7077
const { path } = dataVariable.attributes;
7178
const normPath = stringToPath(path || '').join('.');
7279
const [ds, dr] = em.DataSources.fromPath(path!);
73-
ds && dataListeners.push({ obj: ds.records, event: 'add remove reset' });
74-
dr && dataListeners.push({ obj: dr, event: 'change' });
80+
81+
const dataListeners: ListenerWithCallback[] = [];
82+
83+
if (ds) {
84+
dataListeners.push(this.createListener(ds.records, 'add remove reset'));
85+
}
86+
87+
if (dr) {
88+
dataListeners.push(this.createListener(dr, 'change'));
89+
}
90+
7591
dataListeners.push(
76-
{ obj: dataVariable, event: 'change:path change:defaultValue' },
77-
{ obj: em.DataSources.all, event: 'add remove reset' },
78-
{ obj: em, event: `${DataSourcesEvents.path}:${normPath}` },
92+
this.createListener(dataVariable, 'change:path', () => {
93+
this.listenToResolver();
94+
this.onChange();
95+
}),
96+
this.createListener(dataVariable, 'change:defaultValue'),
97+
this.createListener(em.DataSources.all, 'add remove reset'),
98+
this.createListener(em, `${DataSourcesEvents.path}:${normPath}`),
7999
);
80100

81101
return dataListeners;
82102
}
83103

84-
private listenToDataCollectionVariable(dataVariable: DataCollectionVariable) {
85-
return [{ obj: dataVariable, event: 'change:value' }];
104+
private listenToDataCollectionVariable(dataVariable: DataCollectionVariable): ListenerWithCallback[] {
105+
return [this.createListener(dataVariable, 'change:value')];
86106
}
87107

88108
private removeListeners() {
89-
this.listeners.forEach((ls) => this.model.stopListening(ls.obj, ls.event, this.onChange));
109+
this.listeners.forEach((ls) => this.model.stopListening(ls.obj, ls.event, ls.callback));
90110
this.listeners = [];
91111
}
92112

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

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,59 @@ import Component from '../../../dom_components/model/Component';
22
import { ComponentDefinition, ComponentOptions } from '../../../dom_components/model/types';
33
import { toLowerCase } from '../../../utils/mixins';
44
import { DataCondition, DataConditionProps, DataConditionType } from './DataCondition';
5+
import { ConditionProps } from './DataConditionEvaluator';
56

67
export default class ComponentDataCondition extends Component {
78
dataResolver: DataCondition;
89

910
constructor(props: DataConditionProps, opt: ComponentOptions) {
10-
const { condition, ifTrue, ifFalse } = props;
11-
const dataConditionInstance = new DataCondition(condition, ifTrue, ifFalse, { em: opt.em });
11+
const dataConditionInstance = new DataCondition(props, { em: opt.em });
12+
1213
super(
1314
{
1415
...props,
1516
type: DataConditionType,
1617
components: dataConditionInstance.getDataValue(),
18+
droppable: false,
1719
},
1820
opt,
1921
);
2022
this.dataResolver = dataConditionInstance;
2123
this.dataResolver.onValueChange = this.handleConditionChange.bind(this);
2224
}
2325

26+
getCondition() {
27+
return this.dataResolver.getCondition();
28+
}
29+
30+
getIfTrue() {
31+
return this.dataResolver.getIfTrue();
32+
}
33+
34+
getIfFalse() {
35+
return this.dataResolver.getIfFalse();
36+
}
37+
2438
private handleConditionChange() {
25-
this.dataResolver.reevaluate();
2639
this.components(this.dataResolver.getDataValue());
2740
}
2841

2942
static isComponent(el: HTMLElement) {
3043
return toLowerCase(el.tagName) === DataConditionType;
3144
}
3245

46+
setCondition(newCondition: ConditionProps) {
47+
this.dataResolver.setCondition(newCondition);
48+
}
49+
50+
setIfTrue(newIfTrue: any) {
51+
this.dataResolver.setIfTrue(newIfTrue);
52+
}
53+
54+
setIfFalse(newIfFalse: any) {
55+
this.dataResolver.setIfFalse(newIfFalse);
56+
}
57+
3358
toJSON(): ComponentDefinition {
3459
return this.dataResolver.toJSON();
3560
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { Operator } from './operators';
1+
import { Operator } from './operators/BaseOperator';
2+
import { DataConditionOperation } from './operators/types';
23

34
export class ConditionStatement {
45
constructor(
56
private leftValue: any,
6-
private operator: Operator,
7+
private operator: Operator<DataConditionOperation>,
78
private rightValue: any,
89
) {}
910

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

Lines changed: 70 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,23 @@ import EditorModel from '../../../editor/model/Editor';
33
import DataVariable, { DataVariableProps } from '../DataVariable';
44
import DataResolverListener from '../DataResolverListener';
55
import { evaluateVariable, isDataVariable } from '../utils';
6-
import { Condition, ConditionProps } from './Condition';
7-
import { GenericOperation } from './operators/GenericOperator';
8-
import { LogicalOperation } from './operators/LogicalOperator';
6+
import { DataConditionEvaluator, ConditionProps } from './DataConditionEvaluator';
7+
import { AnyTypeOperation } from './operators/AnyTypeOperator';
8+
import { BooleanOperation } from './operators/BooleanOperator';
99
import { NumberOperation } from './operators/NumberOperator';
10-
import { StringOperation } from './operators/StringOperations';
10+
import { StringOperation } from './operators/StringOperator';
11+
import { isUndefined } from 'underscore';
1112

1213
export const DataConditionType = 'data-condition';
1314

1415
export interface ExpressionProps {
1516
left: any;
16-
operator: GenericOperation | StringOperation | NumberOperation;
17+
operator: AnyTypeOperation | StringOperation | NumberOperation;
1718
right: any;
1819
}
1920

2021
export interface LogicGroupProps {
21-
logicalOperator: LogicalOperation;
22+
logicalOperator: BooleanOperation;
2223
statements: ConditionProps[];
2324
}
2425

@@ -30,57 +31,90 @@ export interface DataConditionProps {
3031
}
3132

3233
interface DataConditionPropsDefined extends Omit<DataConditionProps, 'condition'> {
33-
condition: Condition;
34+
condition: DataConditionEvaluator;
3435
}
3536

3637
export class DataCondition extends Model<DataConditionPropsDefined> {
37-
lastEvaluationResult: boolean;
3838
private em: EditorModel;
3939
private resolverListeners: DataResolverListener[] = [];
4040
private _onValueChange?: () => void;
4141

4242
constructor(
43-
condition: ConditionProps,
44-
public ifTrue: any,
45-
public ifFalse: any,
43+
props: {
44+
condition: ConditionProps;
45+
ifTrue: any;
46+
ifFalse: any;
47+
},
4648
opts: { em: EditorModel; onValueChange?: () => void },
4749
) {
48-
if (typeof condition === 'undefined') {
49-
throw new MissingConditionError();
50+
if (isUndefined(props.condition)) {
51+
opts.em.logError('No condition was provided to a conditional component.');
5052
}
5153

52-
const conditionInstance = new Condition(condition, { em: opts.em });
54+
const conditionInstance = new DataConditionEvaluator({ condition: props.condition }, { em: opts.em });
55+
5356
super({
5457
type: DataConditionType,
58+
...props,
5559
condition: conditionInstance,
56-
ifTrue,
57-
ifFalse,
5860
});
5961
this.em = opts.em;
60-
this.lastEvaluationResult = this.evaluate();
6162
this.listenToDataVariables();
6263
this._onValueChange = opts.onValueChange;
64+
65+
this.on('change:condition change:ifTrue change:ifFalse', () => {
66+
this.listenToDataVariables();
67+
this._onValueChange?.();
68+
});
6369
}
6470

65-
get condition() {
71+
private get conditionEvaluator() {
6672
return this.get('condition')!;
6773
}
6874

69-
evaluate() {
70-
return this.condition.evaluate();
75+
getCondition(): ConditionProps {
76+
return this.get('condition')?.get('condition')!;
77+
}
78+
79+
getIfTrue() {
80+
return this.get('ifTrue')!;
81+
}
82+
83+
getIfFalse() {
84+
return this.get('ifFalse')!;
7185
}
7286

73-
getDataValue(): any {
74-
return this.lastEvaluationResult ? evaluateVariable(this.ifTrue, this.em) : evaluateVariable(this.ifFalse, this.em);
87+
isTrue(): boolean {
88+
return this.conditionEvaluator.evaluate();
7589
}
7690

77-
reevaluate(): void {
78-
this.lastEvaluationResult = this.evaluate();
91+
getDataValue(skipDynamicValueResolution: boolean = false): any {
92+
const ifTrue = this.get('ifTrue');
93+
const ifFalse = this.get('ifFalse');
94+
95+
const isConditionTrue = this.isTrue();
96+
if (skipDynamicValueResolution) {
97+
return isConditionTrue ? ifTrue : ifFalse;
98+
}
99+
100+
return isConditionTrue ? evaluateVariable(ifTrue, this.em) : evaluateVariable(ifFalse, this.em);
79101
}
80102

81103
set onValueChange(newFunction: () => void) {
82104
this._onValueChange = newFunction;
83-
this.listenToDataVariables();
105+
}
106+
107+
setCondition(newCondition: ConditionProps) {
108+
const newConditionInstance = new DataConditionEvaluator({ condition: newCondition }, { em: this.em });
109+
this.set('condition', newConditionInstance);
110+
}
111+
112+
setIfTrue(newIfTrue: any) {
113+
this.set('ifTrue', newIfTrue);
114+
}
115+
116+
setIfFalse(newIfFalse: any) {
117+
this.set('ifFalse', newIfFalse);
84118
}
85119

86120
private listenToDataVariables() {
@@ -97,7 +131,6 @@ export class DataCondition extends Model<DataConditionPropsDefined> {
97131
em,
98132
resolver: new DataVariable(variable, { em: this.em }),
99133
onUpdate: (() => {
100-
this.reevaluate();
101134
this._onValueChange?.();
102135
}).bind(this),
103136
});
@@ -107,9 +140,11 @@ export class DataCondition extends Model<DataConditionPropsDefined> {
107140
}
108141

109142
getDependentDataVariables() {
110-
const dataVariables: DataVariableProps[] = this.condition.getDataVariables();
111-
if (isDataVariable(this.ifTrue)) dataVariables.push(this.ifTrue);
112-
if (isDataVariable(this.ifFalse)) dataVariables.push(this.ifFalse);
143+
const dataVariables: DataVariableProps[] = this.conditionEvaluator.getDependentDataVariables();
144+
const ifTrue = this.get('ifTrue');
145+
const ifFalse = this.get('ifFalse');
146+
if (isDataVariable(ifTrue)) dataVariables.push(ifTrue);
147+
if (isDataVariable(ifFalse)) dataVariables.push(ifFalse);
113148

114149
return dataVariables;
115150
}
@@ -120,16 +155,14 @@ export class DataCondition extends Model<DataConditionPropsDefined> {
120155
}
121156

122157
toJSON() {
158+
const ifTrue = this.get('ifTrue');
159+
const ifFalse = this.get('ifFalse');
160+
123161
return {
124162
type: DataConditionType,
125-
condition: this.condition,
126-
ifTrue: this.ifTrue,
127-
ifFalse: this.ifFalse,
163+
condition: this.conditionEvaluator,
164+
ifTrue,
165+
ifFalse,
128166
};
129167
}
130168
}
131-
export class MissingConditionError extends Error {
132-
constructor() {
133-
super('No condition was provided to a conditional component.');
134-
}
135-
}

0 commit comments

Comments
 (0)