diff --git a/docs/.vuepress/theme/layouts/StudioSdkBannerSidebar.vue b/docs/.vuepress/theme/layouts/StudioSdkBannerSidebar.vue
index 726258c1a..2036df118 100644
--- a/docs/.vuepress/theme/layouts/StudioSdkBannerSidebar.vue
+++ b/docs/.vuepress/theme/layouts/StudioSdkBannerSidebar.vue
@@ -1,22 +1,23 @@
diff --git a/docs/.vuepress/theme/layouts/utils.js b/docs/.vuepress/theme/layouts/utils.js
index a216ed46a..74ef1996b 100644
--- a/docs/.vuepress/theme/layouts/utils.js
+++ b/docs/.vuepress/theme/layouts/utils.js
@@ -1,7 +1,7 @@
export const getSdkUtmParams = (medium = '') => {
- return `utm_source=grapesjs-docs&utm_medium=${medium}`;
-}
+ return `utm_source=grapesjs-docs&utm_medium=${medium}`;
+};
export const getSdkDocsLink = (medium = '') => {
- return `https://app.grapesjs.com/docs-sdk/overview/getting-started?${getSdkUtmParams(medium)}`;
-}
\ No newline at end of file
+ return `https://app.grapesjs.com/docs-sdk/overview/getting-started?${getSdkUtmParams(medium)}`;
+};
diff --git a/docs/api/canvas.md b/docs/api/canvas.md
index 18d8be4d5..415ec65c9 100644
--- a/docs/api/canvas.md
+++ b/docs/api/canvas.md
@@ -257,6 +257,7 @@ Set canvas zoom value
### Parameters
* `value` **[Number][9]** The zoom value, from 0 to 100
+* `opts` **SetZoomOptions** (optional, default `{}`)
### Examples
diff --git a/docs/api/commands.md b/docs/api/commands.md
index 6b9ac74c4..20491ad7c 100644
--- a/docs/api/commands.md
+++ b/docs/api/commands.md
@@ -77,6 +77,20 @@ editor.on('command:run:my-command', ({ result, options }) => { ... });
editor.on('command:stop:before:my-command', ({ options }) => { ... });
```
+* `command:call` Triggered on run or stop of a command.
+
+```javascript
+editor.on('command:call', ({ id, result, options, type }) => {
+ console.log('Command id', id, 'command result', result, 'call type', type);
+});
+```
+
+* `command:call:COMMAND-ID` Triggered on run or stop of a specific command.
+
+```javascript
+editor.on('command:call:my-command', ({ result, options, type }) => { ... });
+```
+
## Methods
* [add][2]
diff --git a/docs/api/component.md b/docs/api/component.md
index ee151e510..9dab1b1cc 100644
--- a/docs/api/component.md
+++ b/docs/api/component.md
@@ -137,6 +137,7 @@ By setting override to specific properties, changes of those properties will be
### Parameters
* `value` **([Boolean][3] | [String][1] | [Array][5]<[String][1]>)**
+* `options` **DynamicWatchersOptions** (optional, default `{}`)
### Examples
@@ -280,7 +281,7 @@ Update attributes of the component
### Parameters
* `attrs` **[Object][2]** Key value attributes
-* `opts` **SetAttrOptions** (optional, default `{}`)
+* `opts` **SetAttrOptions** (optional, default `{skipWatcherUpdates:false,fromDataSource:false}`)
* `options` **[Object][2]** Options for the model update
### Examples
diff --git a/docs/api/editor.md b/docs/api/editor.md
index 92db6bdde..64a1c8c0d 100644
--- a/docs/api/editor.md
+++ b/docs/api/editor.md
@@ -12,19 +12,65 @@ const editor = grapesjs.init({
```
## Available Events
+* `update` Event triggered on any change of the project (eg. component added/removed, style changes, etc.)
-You can make use of available events in this way
+```javascript
+editor.on('update', () => { ... });
+```
-```js
-editor.on('EVENT-NAME', (some, argument) => {
- // do something
-})
+* `undo` Undo executed.
+
+```javascript
+editor.on('undo', () => { ... });
+```
+
+* `redo` Redo executed.
+
+```javascript
+editor.on('redo', () => { ... });
+```
+
+* `load` Editor is loaded. At this stage, the project is loaded in the editor and elements in the canvas are rendered.
+
+```javascript
+editor.on('load', () => { ... });
+```
+
+* `project:load` Project JSON loaded in the editor. The event is triggered on the initial load and on the `editor.loadProjectData` method.
+
+```javascript
+editor.on('project:load', ({ project, initial }) => { ... });
+```
+
+* `project:get` Event triggered on request of the project data. This can be used to extend the project with custom data.
+
+```javascript
+editor.on('project:get', ({ project }) => { project.myCustomKey = 'value' });
+```
+
+* `log` Log message triggered.
+
+```javascript
+editor.on('log', (msg, opts) => { ... });
+```
+
+* `telemetry:init` Initial telemetry data are sent.
+
+```javascript
+editor.on('telemetry:init', () => { ... });
```
-* `update` - The structure of the template is updated (its HTML/CSS)
-* `undo` - Undo executed
-* `redo` - Redo executed
-* `load` - Editor is loaded
+* `destroy` Editor started destroy (on `editor.destroy()`).
+
+```javascript
+editor.on('destroy', () => { ... });
+```
+
+* `destroyed` Editor destroyed.
+
+```javascript
+editor.on('destroyed', () => { ... });
+```
### Components
diff --git a/docs/api/pages.md b/docs/api/pages.md
index a59317b29..3cfdff8bc 100644
--- a/docs/api/pages.md
+++ b/docs/api/pages.md
@@ -113,6 +113,32 @@ pageManager.remove(somePage);
Returns **[Page]** Removed Page
+## move
+
+Move a page to a specific index in the pages collection.
+If the index is out of bounds, the page will not be moved.
+
+### Parameters
+
+* `page` **([string][3] | [Page])** Page or page id to move.
+* `opts` **[Object][2]?** Move options (optional, default `{}`)
+
+ * `opts.at` **[number][4]?** The target index where the page should be moved.
+
+### Examples
+
+```javascript
+// Move a page to index 2
+const movedPage = pageManager.move('page-id', { at: 2 });
+if (movedPage) {
+ console.log('Page moved successfully:', movedPage);
+} else {
+ console.log('Page could not be moved.');
+}
+```
+
+Returns **(Page | [undefined][5])** The moved page, or `undefined` if the page does not exist or the index is out of bounds.
+
## get
Get page by id
@@ -192,3 +218,7 @@ Returns **[Page]**
[2]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object
[3]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String
+
+[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number
+
+[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/undefined
diff --git a/docs/api/parser.md b/docs/api/parser.md
index 2e48d361b..df9d92993 100644
--- a/docs/api/parser.md
+++ b/docs/api/parser.md
@@ -19,9 +19,43 @@ const { Parser } = editor;
```
## Available Events
+* `parse:html` On HTML parse, an object containing the input and the output of the parser is passed as an argument.
-* `parse:html` - On HTML parse, an object containing the input and the output of the parser is passed as an argument
-* `parse:css` - On CSS parse, an object containing the input and the output of the parser is passed as an argument
+```javascript
+editor.on('parse:html', ({ input, output }) => { ... });
+```
+
+* `parse:html:before` Event triggered before the HTML parsing starts. An object containing the input is passed as an argument.
+
+```javascript
+editor.on('parse:html:before', (options) => {
+ console.log('Parser input', options.input);
+ // You can also process the input and update `options.input`
+ options.input += '
Extra content
';
+});
+```
+
+* `parse:css` On CSS parse, an object containing the input and the output of the parser is passed as an argument.
+
+```javascript
+editor.on('parse:css', ({ input, output }) => { ... });
+```
+
+* `parse:css:before` Event triggered before the CSS parsing starts. An object containing the input is passed as an argument.
+
+```javascript
+editor.on('parse:css:before', (options) => {
+ console.log('Parser input', options.input);
+ // You can also process the input and update `options.input`
+ options.input += '.my-class { color: red; }';
+});
+```
+
+* `parse` Catch-all event for all the events mentioned above. An object containing all the available data about the triggered event is passed as an argument to the callback.
+
+```javascript
+editor.on('parse', ({ event, ... }) => { ... });
+```
## Methods
diff --git a/docs/package.json b/docs/package.json
index 45020bbbd..fdb51f3fe 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -2,7 +2,7 @@
"name": "@grapesjs/docs",
"private": true,
"description": "Free and Open Source Web Builder Framework",
- "version": "0.22.4",
+ "version": "0.22.5",
"license": "BSD-3-Clause",
"homepage": "http://grapesjs.com",
"files": [
diff --git a/packages/cli/src/build.ts b/packages/cli/src/build.ts
index 2afb77f0b..71d8a0819 100644
--- a/packages/cli/src/build.ts
+++ b/packages/cli/src/build.ts
@@ -52,10 +52,15 @@ export const buildLocale = async (opts: BuildOptions = {}) => {
const babelOpts = { ...babelConfig(buildWebpackArgs(opts) as any) };
fs.readdirSync(localDst).forEach((file) => {
const filePath = `${localDst}/${file}`;
+ const esModuleFileName = filePath.replace(/\.[^.]+$/, '.mjs');
+ fs.copyFileSync(filePath, esModuleFileName);
const compiled = transformFileSync(filePath, babelOpts).code;
fs.writeFileSync(filePath, compiled);
});
+ // Remove the index.mjs as it is useless
+ fs.unlinkSync(`${localDst}/index.mjs`);
+
printRow('Locale files building completed successfully!');
};
diff --git a/packages/core/package.json b/packages/core/package.json
index 0e15c7719..612ae7839 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,7 +1,7 @@
{
"name": "grapesjs",
"description": "Free and Open Source Web Builder Framework",
- "version": "0.22.6-rc.0",
+ "version": "0.22.6",
"author": "Artur Arseniev",
"license": "BSD-3-Clause",
"homepage": "http://grapesjs.com",
diff --git a/packages/core/src/data_sources/model/ComponentDataVariable.ts b/packages/core/src/data_sources/model/ComponentDataVariable.ts
index 504956c40..6f8aa0d97 100644
--- a/packages/core/src/data_sources/model/ComponentDataVariable.ts
+++ b/packages/core/src/data_sources/model/ComponentDataVariable.ts
@@ -1,8 +1,14 @@
+import { ObjectAny } from '../../common';
import Component from '../../dom_components/model/Component';
-import { ComponentOptions } from '../../dom_components/model/types';
+import { ComponentDefinition, ComponentOptions, ComponentProperties } from '../../dom_components/model/types';
import { toLowerCase } from '../../utils/mixins';
import DataVariable, { DataVariableProps, DataVariableType } from './DataVariable';
+export interface ComponentDataVariableProps extends ComponentProperties {
+ type: typeof DataVariableType;
+ dataResolver: DataVariableProps;
+}
+
export default class ComponentDataVariable extends Component {
dataResolver: DataVariable;
@@ -10,17 +16,20 @@ export default class ComponentDataVariable extends Component {
return {
// @ts-ignore
...super.defaults,
- type: DataVariableType,
- path: '',
- defaultValue: '',
droppable: false,
+ type: DataVariableType,
+ dataResolver: {
+ path: '',
+ defaultValue: '',
+ },
};
}
- constructor(props: DataVariableProps, opt: ComponentOptions) {
+ constructor(props: ComponentDataVariableProps, opt: ComponentOptions) {
super(props, opt);
- const { type, path, defaultValue } = props;
- this.dataResolver = new DataVariable({ type, path, defaultValue }, opt);
+
+ this.dataResolver = new DataVariable(props.dataResolver, opt);
+ this.listenToPropsChange();
}
getPath() {
@@ -47,6 +56,23 @@ export default class ComponentDataVariable extends Component {
this.dataResolver.set('defaultValue', newValue);
}
+ private listenToPropsChange() {
+ this.on('change:dataResolver', () => {
+ this.dataResolver.set(this.get('dataResolver'));
+ });
+ }
+
+ toJSON(opts?: ObjectAny): ComponentDefinition {
+ const json = super.toJSON(opts);
+ const dataResolver = this.dataResolver.toJSON();
+ delete dataResolver.type;
+
+ return {
+ ...json,
+ dataResolver,
+ };
+ }
+
static isComponent(el: HTMLElement) {
return toLowerCase(el.tagName) === DataVariableType;
}
diff --git a/packages/core/src/data_sources/model/DataResolverListener.ts b/packages/core/src/data_sources/model/DataResolverListener.ts
index 0b13f8a43..b10f7e262 100644
--- a/packages/core/src/data_sources/model/DataResolverListener.ts
+++ b/packages/core/src/data_sources/model/DataResolverListener.ts
@@ -4,7 +4,11 @@ import { Model } from '../../common';
import EditorModel from '../../editor/model/Editor';
import DataVariable, { DataVariableType } from './DataVariable';
import { DataResolver } from '../types';
-import { DataCondition, DataConditionType } from './conditional_variables/DataCondition';
+import {
+ DataCondition,
+ DataConditionOutputChangedEvent,
+ DataConditionType,
+} from './conditional_variables/DataCondition';
import { DataCollectionVariableType } from './data_collection/constants';
import DataCollectionVariable from './data_collection/DataCollectionVariable';
@@ -64,12 +68,13 @@ export default class DataResolverListener {
}
private listenToConditionalVariable(dataVariable: DataCondition): ListenerWithCallback[] {
- const { em } = this;
- const dataListeners = dataVariable.getDependentDataVariables().flatMap((dataVariable) => {
- return this.listenToDataVariable(new DataVariable(dataVariable, { em }));
- });
-
- return dataListeners;
+ return [
+ {
+ obj: dataVariable,
+ event: DataConditionOutputChangedEvent,
+ callback: this.onChange,
+ },
+ ];
}
private listenToDataVariable(dataVariable: DataVariable): ListenerWithCallback[] {
diff --git a/packages/core/src/data_sources/model/DataVariable.ts b/packages/core/src/data_sources/model/DataVariable.ts
index caaaa5722..f64a86d87 100644
--- a/packages/core/src/data_sources/model/DataVariable.ts
+++ b/packages/core/src/data_sources/model/DataVariable.ts
@@ -4,7 +4,7 @@ import EditorModel from '../../editor/model/Editor';
export const DataVariableType = 'data-variable' as const;
export interface DataVariableProps {
- type: typeof DataVariableType;
+ type?: typeof DataVariableType;
path: string;
defaultValue?: string;
}
diff --git a/packages/core/src/data_sources/model/conditional_variables/ComponentDataCondition.ts b/packages/core/src/data_sources/model/conditional_variables/ComponentDataCondition.ts
index 755262883..f5e21d52d 100644
--- a/packages/core/src/data_sources/model/conditional_variables/ComponentDataCondition.ts
+++ b/packages/core/src/data_sources/model/conditional_variables/ComponentDataCondition.ts
@@ -1,61 +1,123 @@
import Component from '../../../dom_components/model/Component';
-import { ComponentDefinition, ComponentOptions } from '../../../dom_components/model/types';
+import {
+ ComponentDefinition as ComponentProperties,
+ ComponentDefinitionDefined,
+ ComponentOptions,
+ ToHTMLOptions,
+ ComponentAddType,
+} from '../../../dom_components/model/types';
import { toLowerCase } from '../../../utils/mixins';
-import { DataCondition, DataConditionProps, DataConditionType } from './DataCondition';
+import { DataCondition, DataConditionOutputChangedEvent, DataConditionProps, DataConditionType } from './DataCondition';
import { ConditionProps } from './DataConditionEvaluator';
+import { StringOperation } from './operators/StringOperator';
+import { ObjectAny } from '../../../common';
+import { DataConditionIfTrueType, DataConditionIfFalseType } from './constants';
+
+export type DataConditionDisplayType = typeof DataConditionIfTrueType | typeof DataConditionIfFalseType;
+
+export interface ComponentDataConditionProps extends ComponentProperties {
+ type: typeof DataConditionType;
+ dataResolver: DataConditionProps;
+}
export default class ComponentDataCondition extends Component {
dataResolver: DataCondition;
- constructor(props: DataConditionProps, opt: ComponentOptions) {
- const dataConditionInstance = new DataCondition(props, { em: opt.em });
-
- super(
- {
- ...props,
- type: DataConditionType,
- components: dataConditionInstance.getDataValue(),
- droppable: false,
+ get defaults(): ComponentDefinitionDefined {
+ return {
+ // @ts-ignore
+ ...super.defaults,
+ droppable: false,
+ type: DataConditionType,
+ dataResolver: {
+ condition: {
+ left: '',
+ operator: StringOperation.equalsIgnoreCase,
+ right: '',
+ },
},
- opt,
- );
- this.dataResolver = dataConditionInstance;
- this.dataResolver.onValueChange = this.handleConditionChange.bind(this);
+ components: [
+ {
+ type: DataConditionIfTrueType,
+ },
+ {
+ type: DataConditionIfFalseType,
+ },
+ ],
+ };
}
- getCondition() {
- return this.dataResolver.getCondition();
+ constructor(props: ComponentDataConditionProps, opt: ComponentOptions) {
+ // @ts-ignore
+ super(props, opt);
+
+ const { condition } = props.dataResolver;
+ this.dataResolver = new DataCondition({ condition }, { em: opt.em });
+
+ this.listenToPropsChange();
}
- getIfTrue() {
- return this.dataResolver.getIfTrue();
+ isTrue() {
+ return this.dataResolver.isTrue();
}
- getIfFalse() {
- return this.dataResolver.getIfFalse();
+ getCondition() {
+ return this.dataResolver.getCondition();
}
- private handleConditionChange() {
- this.components(this.dataResolver.getDataValue());
+ getIfTrueContent(): Component | undefined {
+ return this.components().at(0);
}
- static isComponent(el: HTMLElement) {
- return toLowerCase(el.tagName) === DataConditionType;
+ getIfFalseContent(): Component | undefined {
+ return this.components().at(1);
+ }
+
+ getOutputContent(): Component | undefined {
+ return this.isTrue() ? this.getIfTrueContent() : this.getIfFalseContent();
}
setCondition(newCondition: ConditionProps) {
this.dataResolver.setCondition(newCondition);
}
- setIfTrue(newIfTrue: any) {
- this.dataResolver.setIfTrue(newIfTrue);
+ setIfTrueComponents(content: ComponentAddType) {
+ this.setComponentsAtIndex(0, content);
+ }
+
+ setIfFalseComponents(content: ComponentAddType) {
+ this.setComponentsAtIndex(1, content);
+ }
+
+ getInnerHTML(opts?: ToHTMLOptions): string {
+ return this.getOutputContent()?.getInnerHTML(opts) ?? '';
+ }
+
+ private setComponentsAtIndex(index: number, newContent: ComponentAddType) {
+ const component = this.components().at(index);
+ component?.components(newContent);
+ }
+
+ private listenToPropsChange() {
+ this.on('change:dataResolver', () => {
+ this.dataResolver.set(this.get('dataResolver'));
+ });
}
- setIfFalse(newIfFalse: any) {
- this.dataResolver.setIfFalse(newIfFalse);
+ toJSON(opts?: ObjectAny): ComponentProperties {
+ const json = super.toJSON(opts);
+ const dataResolver = this.dataResolver.toJSON();
+ delete dataResolver.type;
+ delete dataResolver.ifTrue;
+ delete dataResolver.ifFalse;
+
+ return {
+ ...json,
+ dataResolver,
+ };
}
- toJSON(): ComponentDefinition {
- return this.dataResolver.toJSON();
+ static isComponent(el: HTMLElement) {
+ return toLowerCase(el.tagName) === DataConditionType;
}
}
diff --git a/packages/core/src/data_sources/model/conditional_variables/ConditionalOutputBase.ts b/packages/core/src/data_sources/model/conditional_variables/ConditionalOutputBase.ts
new file mode 100644
index 000000000..65bc1f92b
--- /dev/null
+++ b/packages/core/src/data_sources/model/conditional_variables/ConditionalOutputBase.ts
@@ -0,0 +1,19 @@
+import Component from '../../../dom_components/model/Component';
+import { ComponentDefinitionDefined, ToHTMLOptions } from '../../../dom_components/model/types';
+import { toLowerCase } from '../../../utils/mixins';
+import { isDataConditionDisplayType } from '../../utils';
+
+export default class ConditionalOutputBase extends Component {
+ get defaults(): ComponentDefinitionDefined {
+ return {
+ // @ts-ignore
+ ...super.defaults,
+ removable: false,
+ draggable: false,
+ };
+ }
+
+ static isComponent(el: HTMLElement) {
+ return isDataConditionDisplayType(toLowerCase(el.tagName));
+ }
+}
diff --git a/packages/core/src/data_sources/model/conditional_variables/DataCondition.ts b/packages/core/src/data_sources/model/conditional_variables/DataCondition.ts
index fc72ef2f0..1f3c03cc1 100644
--- a/packages/core/src/data_sources/model/conditional_variables/DataCondition.ts
+++ b/packages/core/src/data_sources/model/conditional_variables/DataCondition.ts
@@ -2,7 +2,7 @@ import { Model } from '../../../common';
import EditorModel from '../../../editor/model/Editor';
import DataVariable, { DataVariableProps } from '../DataVariable';
import DataResolverListener from '../DataResolverListener';
-import { evaluateVariable, isDataVariable } from '../utils';
+import { resolveDynamicValue, isDataVariable } from '../../utils';
import { DataConditionEvaluator, ConditionProps } from './DataConditionEvaluator';
import { AnyTypeOperation } from './operators/AnyTypeOperator';
import { BooleanOperation } from './operators/BooleanOperator';
@@ -10,12 +10,14 @@ import { NumberOperation } from './operators/NumberOperator';
import { StringOperation } from './operators/StringOperator';
import { isUndefined } from 'underscore';
-export const DataConditionType = 'data-condition';
+export const DataConditionType = 'data-condition' as const;
+export const DataConditionEvaluationChangedEvent = 'data-condition-evaluation-changed';
+export const DataConditionOutputChangedEvent = 'data-condition-output-changed';
export interface ExpressionProps {
- left: any;
- operator: AnyTypeOperation | StringOperation | NumberOperation;
- right: any;
+ left?: any;
+ operator?: AnyTypeOperation | StringOperation | NumberOperation;
+ right?: any;
}
export interface LogicGroupProps {
@@ -24,129 +26,159 @@ export interface LogicGroupProps {
}
export interface DataConditionProps {
- type: typeof DataConditionType;
+ type?: typeof DataConditionType;
condition: ConditionProps;
- ifTrue: any;
- ifFalse: any;
+ ifTrue?: any;
+ ifFalse?: any;
}
-interface DataConditionPropsDefined extends Omit {
- condition: DataConditionEvaluator;
-}
-
-export class DataCondition extends Model {
+export class DataCondition extends Model {
private em: EditorModel;
private resolverListeners: DataResolverListener[] = [];
- private _onValueChange?: () => void;
-
- constructor(
- props: {
- condition: ConditionProps;
- ifTrue: any;
- ifFalse: any;
- },
- opts: { em: EditorModel; onValueChange?: () => void },
- ) {
+ private _previousEvaluationResult: boolean | null = null;
+ private _conditionEvaluator: DataConditionEvaluator;
+
+ defaults() {
+ return {
+ type: DataConditionType,
+ condition: {
+ left: '',
+ operator: StringOperation.equalsIgnoreCase,
+ right: '',
+ },
+ ifTrue: {},
+ ifFalse: {},
+ };
+ }
+
+ constructor(props: DataConditionProps, opts: { em: EditorModel; onValueChange?: () => void }) {
if (isUndefined(props.condition)) {
opts.em.logError('No condition was provided to a conditional component.');
}
- const conditionInstance = new DataConditionEvaluator({ condition: props.condition }, { em: opts.em });
-
- super({
- type: DataConditionType,
- ...props,
- condition: conditionInstance,
- });
+ // @ts-ignore
+ super(props, opts);
this.em = opts.em;
- this.listenToDataVariables();
- this._onValueChange = opts.onValueChange;
-
- this.on('change:condition change:ifTrue change:ifFalse', () => {
- this.listenToDataVariables();
- this._onValueChange?.();
- });
- }
- private get conditionEvaluator() {
- return this.get('condition')!;
+ const { condition = {} } = props;
+ const instance = new DataConditionEvaluator({ condition }, { em: this.em });
+ this._conditionEvaluator = instance;
+ this.listenToDataVariables();
+ this.listenToPropsChange();
}
getCondition(): ConditionProps {
- return this.get('condition')?.get('condition')!;
+ return this._conditionEvaluator.get('condition')!;
}
getIfTrue() {
- return this.get('ifTrue')!;
+ return this.get('ifTrue');
}
getIfFalse() {
- return this.get('ifFalse')!;
+ return this.get('ifFalse');
+ }
+
+ setCondition(condition: ConditionProps) {
+ this._conditionEvaluator.set('condition', condition);
+ this.trigger(DataConditionOutputChangedEvent, this.getDataValue());
+ }
+
+ setIfTrue(newIfTrue: any) {
+ this.set('ifTrue', newIfTrue);
+ }
+
+ setIfFalse(newIfFalse: any) {
+ this.set('ifFalse', newIfFalse);
}
isTrue(): boolean {
- return this.conditionEvaluator.evaluate();
+ return this._conditionEvaluator.evaluate();
}
getDataValue(skipDynamicValueResolution: boolean = false): any {
- const ifTrue = this.get('ifTrue');
- const ifFalse = this.get('ifFalse');
+ const ifTrue = this.getIfTrue();
+ const ifFalse = this.getIfFalse();
const isConditionTrue = this.isTrue();
if (skipDynamicValueResolution) {
return isConditionTrue ? ifTrue : ifFalse;
}
- return isConditionTrue ? evaluateVariable(ifTrue, this.em) : evaluateVariable(ifFalse, this.em);
+ return isConditionTrue ? resolveDynamicValue(ifTrue, this.em) : resolveDynamicValue(ifFalse, this.em);
}
- set onValueChange(newFunction: () => void) {
- this._onValueChange = newFunction;
+ private listenToPropsChange() {
+ this.on('change:condition', this.handleConditionChange.bind(this));
+ this.on('change:condition change:ifTrue change:ifFalse', () => {
+ this.listenToDataVariables();
+ });
}
- setCondition(newCondition: ConditionProps) {
- const newConditionInstance = new DataConditionEvaluator({ condition: newCondition }, { em: this.em });
- this.set('condition', newConditionInstance);
+ private handleConditionChange() {
+ this.setCondition(this.get('condition')!);
}
- setIfTrue(newIfTrue: any) {
- this.set('ifTrue', newIfTrue);
- }
+ private listenToDataVariables() {
+ // Clear previous listeners to avoid memory leaks
+ this.cleanupListeners();
- setIfFalse(newIfFalse: any) {
- this.set('ifFalse', newIfFalse);
+ this.setupConditionDataVariableListeners();
+ this.setupOutputDataVariableListeners();
}
- private listenToDataVariables() {
- const { em } = this;
- if (!em) return;
+ private setupConditionDataVariableListeners() {
+ this._conditionEvaluator.getDependentDataVariables().forEach((variable) => {
+ this.addListener(variable, () => {
+ this.emitConditionEvaluationChange();
+ });
+ });
+ }
- // Clear previous listeners to avoid memory leaks
- this.cleanupListeners();
+ private setupOutputDataVariableListeners() {
+ const isConditionTrue = this.isTrue();
- const dataVariables = this.getDependentDataVariables();
+ this.setupOutputVariableListener(this.getIfTrue(), isConditionTrue);
+ this.setupOutputVariableListener(this.getIfFalse(), !isConditionTrue);
+ }
- dataVariables.forEach((variable) => {
- const listener = new DataResolverListener({
- em,
- resolver: new DataVariable(variable, { em: this.em }),
- onUpdate: (() => {
- this._onValueChange?.();
- }).bind(this),
+ /**
+ * Sets up a listener for an output variable (ifTrue or ifFalse).
+ * @param outputVariable - The output variable to listen to.
+ * @param isConditionTrue - Whether the condition is currently true.
+ */
+ private setupOutputVariableListener(outputVariable: any, isConditionTrue: boolean) {
+ if (isDataVariable(outputVariable)) {
+ this.addListener(outputVariable, () => {
+ if (isConditionTrue) {
+ this.trigger(DataConditionOutputChangedEvent, outputVariable);
+ }
});
+ }
+ }
- this.resolverListeners.push(listener);
+ private addListener(variable: DataVariableProps, onUpdate: () => void) {
+ const listener = new DataResolverListener({
+ em: this.em,
+ resolver: new DataVariable(variable, { em: this.em }),
+ onUpdate,
});
+
+ this.resolverListeners.push(listener);
}
- getDependentDataVariables() {
- const dataVariables: DataVariableProps[] = this.conditionEvaluator.getDependentDataVariables();
- const ifTrue = this.get('ifTrue');
- const ifFalse = this.get('ifFalse');
- if (isDataVariable(ifTrue)) dataVariables.push(ifTrue);
- if (isDataVariable(ifFalse)) dataVariables.push(ifFalse);
+ private emitConditionEvaluationChange() {
+ const currentEvaluationResult = this.isTrue();
+ if (this._previousEvaluationResult !== currentEvaluationResult) {
+ this._previousEvaluationResult = currentEvaluationResult;
+ this.trigger(DataConditionEvaluationChangedEvent, currentEvaluationResult);
+ this.emitOutputValueChange();
+ }
+ }
- return dataVariables;
+ private emitOutputValueChange() {
+ const currentOutputValue = this.getDataValue();
+ this.trigger(DataConditionOutputChangedEvent, currentOutputValue);
}
private cleanupListeners() {
@@ -154,13 +186,13 @@ export class DataCondition extends Model {
this.resolverListeners = [];
}
- toJSON() {
- const ifTrue = this.get('ifTrue');
- const ifFalse = this.get('ifFalse');
+ toJSON(): DataConditionProps {
+ const ifTrue = this.getIfTrue();
+ const ifFalse = this.getIfFalse();
return {
type: DataConditionType,
- condition: this.conditionEvaluator,
+ condition: this._conditionEvaluator.toJSON(),
ifTrue,
ifFalse,
};
diff --git a/packages/core/src/data_sources/model/conditional_variables/DataConditionEvaluator.ts b/packages/core/src/data_sources/model/conditional_variables/DataConditionEvaluator.ts
index b8f5aba9b..a51b666a7 100644
--- a/packages/core/src/data_sources/model/conditional_variables/DataConditionEvaluator.ts
+++ b/packages/core/src/data_sources/model/conditional_variables/DataConditionEvaluator.ts
@@ -1,6 +1,6 @@
import { DataVariableProps } from '../DataVariable';
import EditorModel from '../../../editor/model/Editor';
-import { evaluateVariable, isDataVariable } from '../utils';
+import { resolveDynamicValue, isDataVariable } from '../../utils';
import { ExpressionProps, LogicGroupProps } from './DataCondition';
import { LogicalGroupEvaluator } from './LogicalGroupEvaluator';
import { Operator } from './operators/BaseOperator';
@@ -39,9 +39,10 @@ export class DataConditionEvaluator extends Model {
if (this.isExpression(condition)) {
const { left, operator, right } = condition;
- const evaluateLeft = evaluateVariable(left, this.em);
- const evaluateRight = evaluateVariable(right, this.em);
+ const evaluateLeft = resolveDynamicValue(left, this.em);
+ const evaluateRight = resolveDynamicValue(right, this.em);
const op = this.getOperator(evaluateLeft, operator);
+ if (!op) return false;
const evaluated = op.evaluate(evaluateLeft, evaluateRight);
return evaluated;
@@ -54,7 +55,7 @@ export class DataConditionEvaluator extends Model {
/**
* Factory method for creating operators based on the data type.
*/
- private getOperator(left: any, operator: string): Operator {
+ private getOperator(left: any, operator: string | undefined): Operator | undefined {
const em = this.em;
if (this.isOperatorInEnum(operator, AnyTypeOperation)) {
@@ -64,7 +65,9 @@ export class DataConditionEvaluator extends Model {
} else if (typeof left === 'string') {
return new StringOperator(operator as StringOperation, { em });
}
- throw new Error(`Unsupported data type: ${typeof left}`);
+
+ this.em?.logError(`Unsupported data type: ${typeof left}`);
+ return;
}
getDependentDataVariables(): DataVariableProps[] {
@@ -95,7 +98,7 @@ export class DataConditionEvaluator extends Model {
return condition && typeof condition.left !== 'undefined' && typeof condition.operator === 'string';
}
- private isOperatorInEnum(operator: string, enumObject: any): boolean {
+ private isOperatorInEnum(operator: string | undefined, enumObject: any): boolean {
return Object.values(enumObject).includes(operator);
}
diff --git a/packages/core/src/data_sources/model/conditional_variables/constants.ts b/packages/core/src/data_sources/model/conditional_variables/constants.ts
new file mode 100644
index 000000000..618d7e425
--- /dev/null
+++ b/packages/core/src/data_sources/model/conditional_variables/constants.ts
@@ -0,0 +1,2 @@
+export const DataConditionIfTrueType = 'data-condition-true-content';
+export const DataConditionIfFalseType = 'data-condition-false-content';
diff --git a/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts b/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts
index 1fa19bc3e..bfdc8ff6d 100644
--- a/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts
+++ b/packages/core/src/data_sources/model/data_collection/ComponentDataCollection.ts
@@ -7,7 +7,7 @@ import { isObject, serialize, toLowerCase } from '../../../utils/mixins';
import DataResolverListener from '../DataResolverListener';
import DataSource from '../DataSource';
import DataVariable, { DataVariableProps, DataVariableType } from '../DataVariable';
-import { isDataVariable } from '../utils';
+import { ensureComponentInstance, isDataVariable } from '../../utils';
import { DataCollectionType, keyCollectionDefinition, keyCollectionsStateMap, keyIsCollectionItem } from './constants';
import {
ComponentDataCollectionProps,
@@ -200,12 +200,9 @@ export default class ComponentDataCollection extends Component {
};
if (isFirstItem) {
- const componentType = (componentDef?.type as string) || 'default';
- let type = this.em.Components.getType(componentType) || this.em.Components.getType('default');
- const Model = type.model;
- symbolMain = new Model(
+ symbolMain = ensureComponentInstance(
{
- ...serialize(componentDef),
+ ...componentDef,
draggable: false,
removable: false,
},
diff --git a/packages/core/src/data_sources/model/utils.ts b/packages/core/src/data_sources/model/utils.ts
deleted file mode 100644
index ae2130ef0..000000000
--- a/packages/core/src/data_sources/model/utils.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import EditorModel from '../../editor/model/Editor';
-import { DataResolver, DataResolverProps } from '../types';
-import { DataCollectionStateMap } from './data_collection/types';
-import DataCollectionVariable from './data_collection/DataCollectionVariable';
-import { DataCollectionVariableType } from './data_collection/constants';
-import { DataConditionType, DataCondition } from './conditional_variables/DataCondition';
-import DataVariable, { DataVariableProps, DataVariableType } from './DataVariable';
-
-export function isDataResolverProps(value: any): value is DataResolverProps {
- return (
- typeof value === 'object' && [DataVariableType, DataConditionType, DataCollectionVariableType].includes(value?.type)
- );
-}
-
-export function isDataResolver(value: any): value is DataResolver {
- return value instanceof DataVariable || value instanceof DataCondition;
-}
-
-export function isDataVariable(variable: any): variable is DataVariableProps {
- return variable?.type === DataVariableType;
-}
-
-export function isDataCondition(variable: any) {
- return variable?.type === DataConditionType;
-}
-
-export function evaluateVariable(variable: any, em: EditorModel) {
- return isDataVariable(variable) ? new DataVariable(variable, { em }).getDataValue() : variable;
-}
-
-export function getDataResolverInstance(
- resolverProps: DataResolverProps,
- options: { em: EditorModel; collectionsStateMap?: DataCollectionStateMap },
-): DataResolver {
- const { type } = resolverProps;
- let resolver: DataResolver;
-
- switch (type) {
- case DataVariableType:
- resolver = new DataVariable(resolverProps, options);
- break;
- case DataConditionType: {
- resolver = new DataCondition(resolverProps, options);
- break;
- }
- case DataCollectionVariableType: {
- resolver = new DataCollectionVariable(resolverProps, options);
- break;
- }
- default:
- throw new Error(`Unsupported dynamic type: ${type}`);
- }
-
- return resolver;
-}
-
-export function getDataResolverInstanceValue(
- resolverProps: DataResolverProps,
- options: {
- em: EditorModel;
- collectionsStateMap?: DataCollectionStateMap;
- },
-) {
- const resolver = getDataResolverInstance(resolverProps, options);
-
- return resolver.getDataValue();
-}
diff --git a/packages/core/src/data_sources/utils.ts b/packages/core/src/data_sources/utils.ts
new file mode 100644
index 000000000..72a3bca60
--- /dev/null
+++ b/packages/core/src/data_sources/utils.ts
@@ -0,0 +1,91 @@
+import EditorModel from '../editor/model/Editor';
+import { DataResolver, DataResolverProps } from './types';
+import { DataConditionDisplayType } from './model/conditional_variables/ComponentDataCondition';
+import { DataCollectionStateMap } from './model/data_collection/types';
+import DataCollectionVariable from './model/data_collection/DataCollectionVariable';
+import { DataCollectionVariableType } from './model/data_collection/constants';
+import { DataConditionType, DataCondition } from './model/conditional_variables/DataCondition';
+import DataVariable, { DataVariableProps, DataVariableType } from './model/DataVariable';
+import Component from '../dom_components/model/Component';
+import { ComponentDefinition, ComponentOptions } from '../dom_components/model/types';
+import { serialize } from '../utils/mixins';
+import { DataConditionIfFalseType, DataConditionIfTrueType } from './model/conditional_variables/constants';
+
+export function isDataResolverProps(value: any): value is DataResolverProps {
+ return (
+ typeof value === 'object' && [DataVariableType, DataConditionType, DataCollectionVariableType].includes(value?.type)
+ );
+}
+
+export function isDataResolver(value: any): value is DataResolver {
+ return value instanceof DataVariable || value instanceof DataCondition;
+}
+
+export function isDataVariable(variable: any): variable is DataVariableProps {
+ return variable?.type === DataVariableType;
+}
+
+export function isDataCondition(variable: any) {
+ return variable?.type === DataConditionType;
+}
+
+export function resolveDynamicValue(variable: any, em: EditorModel) {
+ return isDataResolverProps(variable) ? getDataResolverInstanceValue(variable, { em }) : variable;
+}
+
+export function getDataResolverInstance(
+ resolverProps: DataResolverProps,
+ options: { em: EditorModel; collectionsStateMap?: DataCollectionStateMap },
+) {
+ const { type } = resolverProps;
+ let resolver: DataResolver;
+
+ switch (type) {
+ case DataVariableType:
+ resolver = new DataVariable(resolverProps, options);
+ break;
+ case DataConditionType: {
+ resolver = new DataCondition(resolverProps, options);
+ break;
+ }
+ case DataCollectionVariableType: {
+ resolver = new DataCollectionVariable(resolverProps, options);
+ break;
+ }
+ default:
+ options.em?.logError(`Unsupported dynamic type: ${type}`);
+ return;
+ }
+
+ return resolver;
+}
+
+export function getDataResolverInstanceValue(
+ resolverProps: DataResolverProps,
+ options: {
+ em: EditorModel;
+ collectionsStateMap?: DataCollectionStateMap;
+ },
+) {
+ const resolver = getDataResolverInstance(resolverProps, options);
+
+ return resolver?.getDataValue();
+}
+
+export const ensureComponentInstance = (
+ cmp: Component | ComponentDefinition | undefined,
+ opt: ComponentOptions,
+): Component => {
+ if (cmp instanceof Component) return cmp;
+
+ const componentType = (cmp?.type as string) ?? 'default';
+ const defaultModel = opt.em.Components.getType('default');
+ const type = opt.em.Components.getType(componentType) ?? defaultModel;
+ const Model = type.model;
+
+ return new Model(serialize(cmp ?? {}), opt);
+};
+
+export const isDataConditionDisplayType = (type: string | undefined): type is DataConditionDisplayType => {
+ return !!type && [DataConditionIfTrueType, DataConditionIfFalseType].includes(type);
+};
diff --git a/packages/core/src/data_sources/view/ComponentDataConditionView.ts b/packages/core/src/data_sources/view/ComponentDataConditionView.ts
index c8bf42438..6ec3f9316 100644
--- a/packages/core/src/data_sources/view/ComponentDataConditionView.ts
+++ b/packages/core/src/data_sources/view/ComponentDataConditionView.ts
@@ -1,4 +1,46 @@
import ComponentView from '../../dom_components/view/ComponentView';
import ComponentDataCondition from '../model/conditional_variables/ComponentDataCondition';
+import DataResolverListener from '../model/DataResolverListener';
-export default class ComponentDataConditionView extends ComponentView {}
+export default class ComponentDataConditionView extends ComponentView {
+ dataResolverListener!: DataResolverListener;
+
+ initialize(opt = {}) {
+ super.initialize(opt);
+
+ this.postRender = this.postRender.bind(this);
+ this.listenTo(this.model.components(), 'reset', this.postRender);
+ this.dataResolverListener = new DataResolverListener({
+ em: this.em,
+ resolver: this.model.dataResolver,
+ onUpdate: this.postRender,
+ });
+ }
+
+ renderDataResolver() {
+ const componentTrue = this.model.getIfTrueContent();
+ const componentFalse = this.model.getIfFalseContent();
+
+ const elTrue = componentTrue?.getEl();
+ const elFalse = componentFalse?.getEl();
+
+ const isTrue = this.model.isTrue();
+ if (elTrue) {
+ elTrue.style.display = isTrue ? '' : 'none';
+ }
+ if (elFalse) {
+ elFalse.style.display = isTrue ? 'none' : '';
+ }
+ }
+
+ postRender() {
+ this.renderDataResolver();
+ super.postRender();
+ }
+
+ remove() {
+ this.stopListening(this.model.components(), 'reset', this.postRender);
+ this.dataResolverListener.destroy();
+ return super.remove();
+ }
+}
diff --git a/packages/core/src/dom_components/index.ts b/packages/core/src/dom_components/index.ts
index 224dfc585..6de1385f8 100644
--- a/packages/core/src/dom_components/index.ts
+++ b/packages/core/src/dom_components/index.ts
@@ -126,13 +126,18 @@ import ComponentDataVariable from '../data_sources/model/ComponentDataVariable';
import ComponentDataVariableView from '../data_sources/view/ComponentDataVariableView';
import { DataVariableType } from '../data_sources/model/DataVariable';
import { DataConditionType } from '../data_sources/model/conditional_variables/DataCondition';
-import ComponentDataCondition from '../data_sources/model/conditional_variables/ComponentDataCondition';
import ComponentDataConditionView from '../data_sources/view/ComponentDataConditionView';
import ComponentDataCollection from '../data_sources/model/data_collection/ComponentDataCollection';
import { DataCollectionType, DataCollectionVariableType } from '../data_sources/model/data_collection/constants';
import ComponentDataCollectionVariable from '../data_sources/model/data_collection/ComponentDataCollectionVariable';
import ComponentDataCollectionVariableView from '../data_sources/view/ComponentDataCollectionVariableView';
import ComponentDataCollectionView from '../data_sources/view/ComponentDataCollectionView';
+import ComponentDataCondition from '../data_sources/model/conditional_variables/ComponentDataCondition';
+import {
+ DataConditionIfFalseType,
+ DataConditionIfTrueType,
+} from '../data_sources/model/conditional_variables/constants';
+import ConditionalOutputBase from '../data_sources/model/conditional_variables/ConditionalOutputBase';
export type ComponentEvent =
| 'component:create'
@@ -198,6 +203,16 @@ export interface CanMoveResult {
export default class ComponentManager extends ItemManagerModule {
componentTypes: ComponentStackItem[] = [
+ {
+ id: DataConditionIfTrueType,
+ model: ConditionalOutputBase,
+ view: ComponentView,
+ },
+ {
+ id: DataConditionIfFalseType,
+ model: ConditionalOutputBase,
+ view: ComponentView,
+ },
{
id: DataCollectionVariableType,
model: ComponentDataCollectionVariable,
diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts
index 1bd4e035c..c9f0cbf26 100644
--- a/packages/core/src/dom_components/model/Component.ts
+++ b/packages/core/src/dom_components/model/Component.ts
@@ -1378,8 +1378,9 @@ export default class Component extends StyleableModel {
cloned.set(keySymbol, 0);
cloned.set(keySymbols, 0);
} else if (symbol) {
+ const mainSymbolInstances = getSymbolInstances(symbol) ?? [];
// Contains already a reference to a symbol
- symbol.set(keySymbols, [...getSymbolInstances(symbol)!, cloned]);
+ symbol.set(keySymbols, [...mainSymbolInstances!, cloned]);
initSymbol(cloned);
} else if (opt.symbol) {
// Request to create a symbol
diff --git a/packages/core/src/dom_components/model/ComponentResolverWatcher.ts b/packages/core/src/dom_components/model/ComponentResolverWatcher.ts
index 11ca8ba74..ed6d4a851 100644
--- a/packages/core/src/dom_components/model/ComponentResolverWatcher.ts
+++ b/packages/core/src/dom_components/model/ComponentResolverWatcher.ts
@@ -2,11 +2,7 @@ import { ObjectAny } from '../../common';
import { DataCollectionVariableType } from '../../data_sources/model/data_collection/constants';
import { DataCollectionStateMap } from '../../data_sources/model/data_collection/types';
import DataResolverListener from '../../data_sources/model/DataResolverListener';
-import {
- getDataResolverInstance,
- getDataResolverInstanceValue,
- isDataResolverProps,
-} from '../../data_sources/model/utils';
+import { getDataResolverInstance, getDataResolverInstanceValue, isDataResolverProps } from '../../data_sources/utils';
import EditorModel from '../../editor/model/Editor';
import { DataResolverProps } from '../../data_sources/types';
import Component from './Component';
@@ -94,7 +90,7 @@ export class ComponentResolverWatcher {
continue;
}
- const resolver = getDataResolverInstance(resolverProps, { em, collectionsStateMap });
+ const resolver = getDataResolverInstance(resolverProps, { em, collectionsStateMap })!;
this.resolverListeners[key] = new DataResolverListener({
em,
resolver,
diff --git a/packages/core/src/domain_abstract/model/StyleableModel.ts b/packages/core/src/domain_abstract/model/StyleableModel.ts
index 4aa41e892..e70550a12 100644
--- a/packages/core/src/domain_abstract/model/StyleableModel.ts
+++ b/packages/core/src/domain_abstract/model/StyleableModel.ts
@@ -15,7 +15,7 @@ import {
getDataResolverInstanceValue,
isDataResolver,
isDataResolverProps,
-} from '../../data_sources/model/utils';
+} from '../../data_sources/utils';
import { DataResolver } from '../../data_sources/types';
export type StyleProps = Record;
diff --git a/packages/core/src/editor/types.ts b/packages/core/src/editor/types.ts
index 4dba074be..112a3c2f4 100644
--- a/packages/core/src/editor/types.ts
+++ b/packages/core/src/editor/types.ts
@@ -71,3 +71,6 @@ export enum EditorEvents {
destroyed = 'destroyed',
}
/**{END_EVENTS}*/
+
+// need this to avoid the TS documentation generator to break
+export default EditorEvents;
diff --git a/packages/core/src/parser/types.ts b/packages/core/src/parser/types.ts
index b9f3839d1..f69a78324 100644
--- a/packages/core/src/parser/types.ts
+++ b/packages/core/src/parser/types.ts
@@ -45,3 +45,6 @@ export enum ParserEvents {
all = 'parse',
}
/**{END_EVENTS}*/
+
+// need this to avoid the TS documentation generator to break
+export default ParserEvents;
diff --git a/packages/core/test/common.ts b/packages/core/test/common.ts
index 58b754ffe..e14d11cf1 100644
--- a/packages/core/test/common.ts
+++ b/packages/core/test/common.ts
@@ -1,4 +1,11 @@
+import { DataSourceManager } from '../src';
import CanvasEvents from '../src/canvas/types';
+import { ObjectAny } from '../src/common';
+import {
+ DataConditionIfFalseType,
+ DataConditionIfTrueType,
+} from '../src/data_sources/model/conditional_variables/constants';
+import { NumberOperation } from '../src/data_sources/model/conditional_variables/operators/NumberOperator';
import Editor from '../src/editor';
import { EditorConfig } from '../src/editor/config/config';
import EditorModel from '../src/editor/model/Editor';
@@ -97,3 +104,56 @@ export function filterObjectForSnapshot(obj: any, parentKey: string = ''): any {
return result;
}
+
+const baseComponent = {
+ type: 'text',
+ tagName: 'h1',
+};
+
+const createContent = (content: string) => ({
+ ...baseComponent,
+ content,
+});
+
+/**
+ * Creates a component definition for a conditional component (ifTrue or ifFalse).
+ * @param type - The component type (e.g., DataConditionIfTrueType).
+ * @param content - The text content.
+ * @returns The component definition.
+ */
+const createConditionalComponentDef = (type: string, content: string) => ({
+ type,
+ components: [createContent(content)],
+});
+
+export const ifTrueText = 'true text';
+export const newIfTrueText = 'new true text';
+export const ifFalseText = 'false text';
+export const newIfFalseText = 'new false text';
+
+export const ifTrueComponentDef = createConditionalComponentDef(DataConditionIfTrueType, ifTrueText);
+export const newIfTrueComponentDef = createConditionalComponentDef(DataConditionIfTrueType, newIfTrueText);
+export const ifFalseComponentDef = createConditionalComponentDef(DataConditionIfFalseType, ifFalseText);
+export const newIfFalseComponentDef = createConditionalComponentDef(DataConditionIfFalseType, newIfFalseText);
+
+export function isObjectContained(received: ObjectAny, expected: ObjectAny): boolean {
+ return Object.keys(expected).every((key) => {
+ if (typeof expected[key] === 'object' && expected[key] !== null) {
+ return isObjectContained(received[key], expected[key]);
+ }
+
+ return received?.[key] === expected?.[key];
+ });
+}
+
+export const TRUE_CONDITION = {
+ left: 1,
+ operator: NumberOperation.greaterThan,
+ right: 0,
+};
+
+export const FALSE_CONDITION = {
+ left: 0,
+ operator: NumberOperation.lessThan,
+ right: -1,
+};
diff --git a/packages/core/test/specs/data_sources/__snapshots__/serialization.ts.snap b/packages/core/test/specs/data_sources/__snapshots__/serialization.ts.snap
index 6e141da55..bbd79c1c5 100644
--- a/packages/core/test/specs/data_sources/__snapshots__/serialization.ts.snap
+++ b/packages/core/test/specs/data_sources/__snapshots__/serialization.ts.snap
@@ -13,8 +13,10 @@ exports[`DataSource Serialization .getProjectData ComponentDataVariable 1`] = `
{
"components": [
{
- "defaultValue": "default",
- "path": "component-serialization.id1.content",
+ "dataResolver": {
+ "defaultValue": "default",
+ "path": "component-serialization.id1.content",
+ },
"type": "data-variable",
},
],
diff --git a/packages/core/test/specs/data_sources/__snapshots__/storage.ts.snap b/packages/core/test/specs/data_sources/__snapshots__/storage.ts.snap
index 4c29ba791..e1f65681d 100644
--- a/packages/core/test/specs/data_sources/__snapshots__/storage.ts.snap
+++ b/packages/core/test/specs/data_sources/__snapshots__/storage.ts.snap
@@ -23,8 +23,10 @@ exports[`DataSource Storage .getProjectData ComponentDataVariable 1`] = `
{
"components": [
{
- "defaultValue": "default",
- "path": "component-storage.id1.content",
+ "dataResolver": {
+ "defaultValue": "default",
+ "path": "component-storage.id1.content",
+ },
"type": "data-variable",
},
],
@@ -84,8 +86,10 @@ exports[`DataSource Storage .loadProjectData ComponentDataVariable 1`] = `
{
"components": [
{
- "defaultValue": "default",
- "path": "component-storage.id1.content",
+ "dataResolver": {
+ "defaultValue": "default",
+ "path": "component-storage.id1.content",
+ },
"type": "data-variable",
},
],
diff --git a/packages/core/test/specs/data_sources/jsonplaceholder.ts b/packages/core/test/specs/data_sources/jsonplaceholder.ts
index 139113e45..b33468868 100644
--- a/packages/core/test/specs/data_sources/jsonplaceholder.ts
+++ b/packages/core/test/specs/data_sources/jsonplaceholder.ts
@@ -89,8 +89,7 @@ describe('JsonPlaceholder Usage', () => {
components: [
{
type: DataVariableType,
- defaultValue: 'default',
- path: `comments.${record?.id}.name`,
+ dataResolver: { defaultValue: 'default', path: `comments.${record?.id}.name` },
},
],
},
@@ -99,8 +98,7 @@ describe('JsonPlaceholder Usage', () => {
components: [
{
type: DataVariableType,
- defaultValue: 'default',
- path: `comments.${record?.id}.id`,
+ dataResolver: { defaultValue: 'default', path: `comments.${record?.id}.id` },
},
],
},
@@ -109,8 +107,7 @@ describe('JsonPlaceholder Usage', () => {
components: [
{
type: DataVariableType,
- defaultValue: 'default',
- path: `comments.${record?.id}.body`,
+ dataResolver: { defaultValue: 'default', path: `comments.${record?.id}.body` },
},
],
},
diff --git a/packages/core/test/specs/data_sources/model/ComponentDataVariable.getters-setters.ts b/packages/core/test/specs/data_sources/model/ComponentDataVariable.getters-setters.ts
index de8cfd768..84a90fb59 100644
--- a/packages/core/test/specs/data_sources/model/ComponentDataVariable.getters-setters.ts
+++ b/packages/core/test/specs/data_sources/model/ComponentDataVariable.getters-setters.ts
@@ -30,8 +30,10 @@ describe('ComponentDataVariable - setPath and setDefaultValue', () => {
test('component updates when path is changed using setPath', () => {
const cmp = cmpRoot.append({
type: DataVariableType,
- defaultValue: 'default',
- path: 'ds_id.id1.name',
+ dataResolver: {
+ defaultValue: 'default',
+ path: 'ds_id.id1.name',
+ },
})[0] as ComponentDataVariable;
expect(cmp.getEl()?.innerHTML).toContain('Name1');
@@ -45,8 +47,7 @@ describe('ComponentDataVariable - setPath and setDefaultValue', () => {
test('component updates when default value is changed using setDefaultValue', () => {
const cmp = cmpRoot.append({
type: DataVariableType,
- defaultValue: 'default',
- path: 'unknown.id1.name',
+ dataResolver: { defaultValue: 'default', path: 'unknown.id1.name' },
})[0] as ComponentDataVariable;
expect(cmp.getEl()?.innerHTML).toContain('default');
@@ -60,8 +61,7 @@ describe('ComponentDataVariable - setPath and setDefaultValue', () => {
test('component updates correctly after path and default value are changed', () => {
const cmp = cmpRoot.append({
type: DataVariableType,
- defaultValue: 'default',
- path: 'ds_id.id1.name',
+ dataResolver: { defaultValue: 'default', path: 'ds_id.id1.name' },
})[0] as ComponentDataVariable;
expect(cmp.getEl()?.innerHTML).toContain('Name1');
@@ -78,8 +78,7 @@ describe('ComponentDataVariable - setPath and setDefaultValue', () => {
test('component updates correctly after path is changed and data is updated', () => {
const cmp = cmpRoot.append({
type: DataVariableType,
- defaultValue: 'default',
- path: 'ds_id.id1.name',
+ dataResolver: { defaultValue: 'default', path: 'ds_id.id1.name' },
})[0] as ComponentDataVariable;
expect(cmp.getEl()?.innerHTML).toContain('Name1');
diff --git a/packages/core/test/specs/data_sources/model/ComponentDataVariable.ts b/packages/core/test/specs/data_sources/model/ComponentDataVariable.ts
index 6dcbee46b..8844ad155 100644
--- a/packages/core/test/specs/data_sources/model/ComponentDataVariable.ts
+++ b/packages/core/test/specs/data_sources/model/ComponentDataVariable.ts
@@ -1,7 +1,6 @@
import DataSourceManager from '../../../../src/data_sources';
import ComponentWrapper from '../../../../src/dom_components/model/ComponentWrapper';
import { DataVariableType } from '../../../../src/data_sources/model/DataVariable';
-import { DataSourceProps } from '../../../../src/data_sources/types';
import { setupTestEditor } from '../../../common';
import EditorModel from '../../../../src/editor/model/Editor';
@@ -31,8 +30,7 @@ describe('ComponentDataVariable', () => {
components: [
{
type: DataVariableType,
- defaultValue: 'default',
- path: 'ds1.id1.name',
+ dataResolver: { defaultValue: 'default', path: 'ds1.id1.name' },
},
],
})[0];
@@ -54,8 +52,7 @@ describe('ComponentDataVariable', () => {
components: [
{
type: DataVariableType,
- defaultValue: 'default',
- path: 'ds2.id1.name',
+ dataResolver: { defaultValue: 'default', path: 'ds2.id1.name' },
},
],
})[0];
@@ -77,8 +74,7 @@ describe('ComponentDataVariable', () => {
components: [
{
type: DataVariableType,
- defaultValue: 'default',
- path: 'unknown.id1.name',
+ dataResolver: { defaultValue: 'default', path: 'unknown.id1.name' },
},
],
})[0];
@@ -99,8 +95,7 @@ describe('ComponentDataVariable', () => {
components: [
{
type: DataVariableType,
- defaultValue: 'default',
- path: 'ds3.id1.name',
+ dataResolver: { defaultValue: 'default', path: 'ds3.id1.name' },
},
],
})[0];
@@ -126,8 +121,7 @@ describe('ComponentDataVariable', () => {
components: [
{
type: DataVariableType,
- defaultValue: 'default',
- path: `${dataSource.id}.id1.name`,
+ dataResolver: { defaultValue: 'default', path: `${dataSource.id}.id1.name` },
},
],
})[0];
@@ -155,8 +149,7 @@ describe('ComponentDataVariable', () => {
components: [
{
type: DataVariableType,
- defaultValue: 'default',
- path: 'ds4.id1.name',
+ dataResolver: { defaultValue: 'default', path: 'ds4.id1.name' },
},
],
})[0];
@@ -191,8 +184,7 @@ describe('ComponentDataVariable', () => {
components: [
{
type: DataVariableType,
- defaultValue: 'default',
- path: 'dsNestedObject.id1.nestedObject.name',
+ dataResolver: { defaultValue: 'default', path: 'dsNestedObject.id1.nestedObject.name' },
},
],
})[0];
@@ -232,8 +224,7 @@ describe('ComponentDataVariable', () => {
components: [
{
type: DataVariableType,
- defaultValue: 'default',
- path: 'dsNestedArray.id1.items.0.nestedObject.name',
+ dataResolver: { defaultValue: 'default', path: 'dsNestedArray.id1.items.0.nestedObject.name' },
},
],
})[0];
@@ -268,8 +259,7 @@ describe('ComponentDataVariable', () => {
components: [
{
type: DataVariableType,
- defaultValue: 'default',
- path: `${dataSource.id}.id1.content`,
+ dataResolver: { defaultValue: 'default', path: `${dataSource.id}.id1.content` },
},
],
style: {
diff --git a/packages/core/test/specs/data_sources/model/conditional_variables/ComponentDataCondition.getters-setters.ts b/packages/core/test/specs/data_sources/model/conditional_variables/ComponentDataCondition.getters-setters.ts
index 96eab39ff..e5af18d5c 100644
--- a/packages/core/test/specs/data_sources/model/conditional_variables/ComponentDataCondition.getters-setters.ts
+++ b/packages/core/test/specs/data_sources/model/conditional_variables/ComponentDataCondition.getters-setters.ts
@@ -1,22 +1,32 @@
-import { Component, DataSourceManager, Editor } from '../../../../../src';
+import { DataSourceManager } from '../../../../../src';
import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable';
import ComponentDataCondition from '../../../../../src/data_sources/model/conditional_variables/ComponentDataCondition';
import { DataConditionType } from '../../../../../src/data_sources/model/conditional_variables/DataCondition';
import { AnyTypeOperation } from '../../../../../src/data_sources/model/conditional_variables/operators/AnyTypeOperator';
-import { NumberOperation } from '../../../../../src/data_sources/model/conditional_variables/operators/NumberOperator';
import ComponentDataConditionView from '../../../../../src/data_sources/view/ComponentDataConditionView';
import ComponentWrapper from '../../../../../src/dom_components/model/ComponentWrapper';
import EditorModel from '../../../../../src/editor/model/Editor';
-import { setupTestEditor } from '../../../../common';
+import {
+ ifFalseText,
+ setupTestEditor,
+ ifTrueComponentDef,
+ ifFalseComponentDef,
+ newIfTrueText,
+ ifTrueText,
+ FALSE_CONDITION,
+ TRUE_CONDITION,
+ newIfFalseText,
+ newIfTrueComponentDef,
+ newIfFalseComponentDef,
+} from '../../../../common';
describe('ComponentDataCondition Setters', () => {
- let editor: Editor;
let em: EditorModel;
let dsm: DataSourceManager;
let cmpRoot: ComponentWrapper;
beforeEach(() => {
- ({ editor, em, dsm, cmpRoot } = setupTestEditor());
+ ({ em, dsm, cmpRoot } = setupTestEditor());
});
afterEach(() => {
@@ -26,66 +36,42 @@ describe('ComponentDataCondition Setters', () => {
it('should update the condition using setCondition', () => {
const component = cmpRoot.append({
type: DataConditionType,
- condition: {
- left: 0,
- operator: NumberOperation.greaterThan,
- right: -1,
- },
- ifTrue: 'some text
',
- ifFalse: 'false text
',
+ dataResolver: { condition: TRUE_CONDITION },
+ components: [ifTrueComponentDef, ifFalseComponentDef],
})[0] as ComponentDataCondition;
- const newCondition = {
- left: 1,
- operator: NumberOperation.lessThan,
- right: 0,
- };
-
- component.setCondition(newCondition);
- expect(component.getCondition()).toEqual(newCondition);
- expect(component.getInnerHTML()).toBe('false text
');
+ component.setCondition(FALSE_CONDITION);
+ expect(component.getCondition()).toEqual(FALSE_CONDITION);
+ expect(component.getInnerHTML()).toContain(ifFalseText);
+ expect(component.getEl()?.innerHTML).toContain(ifFalseText);
});
- it('should update the ifTrue value using setIfTrue', () => {
+ it('should update the ifTrue value using setIfTrueComponents', () => {
const component = cmpRoot.append({
type: DataConditionType,
- condition: {
- left: 0,
- operator: NumberOperation.greaterThan,
- right: -1,
- },
- ifTrue: 'some text
',
- ifFalse: 'false text
',
+ dataResolver: { condition: TRUE_CONDITION },
+ components: [ifTrueComponentDef, ifFalseComponentDef],
})[0] as ComponentDataCondition;
- const newIfTrue = 'new true text
';
- component.setIfTrue(newIfTrue);
- expect(component.getIfTrue()).toEqual(newIfTrue);
- expect(component.getInnerHTML()).toBe(newIfTrue);
+ component.setIfTrueComponents(newIfTrueComponentDef.components);
+ expect(JSON.parse(JSON.stringify(component.getIfTrueContent()))).toEqual(newIfTrueComponentDef);
+ expect(component.getInnerHTML()).toContain(newIfTrueText);
+ expect(component.getEl()?.innerHTML).toContain(newIfTrueText);
});
- it('should update the ifFalse value using setIfFalse', () => {
+ it('should update the ifFalse value using setIfFalseComponents', () => {
const component = cmpRoot.append({
type: DataConditionType,
- condition: {
- left: 0,
- operator: NumberOperation.greaterThan,
- right: -1,
- },
- ifTrue: 'some text
',
- ifFalse: 'false text
',
+ dataResolver: { condition: TRUE_CONDITION },
+ components: [ifTrueComponentDef, ifFalseComponentDef],
})[0] as ComponentDataCondition;
- const newIfFalse = 'new false text
';
- component.setIfFalse(newIfFalse);
- expect(component.getIfFalse()).toEqual(newIfFalse);
+ component.setIfFalseComponents(newIfFalseComponentDef.components);
+ expect(JSON.parse(JSON.stringify(component.getIfFalseContent()))).toEqual(newIfFalseComponentDef);
- component.setCondition({
- left: 0,
- operator: NumberOperation.lessThan,
- right: -1,
- });
- expect(component.getInnerHTML()).toBe(newIfFalse);
+ component.setCondition(FALSE_CONDITION);
+ expect(component.getInnerHTML()).toContain(newIfFalseText);
+ expect(component.getEl()?.innerHTML).toContain(newIfFalseText);
});
it('should update the data sources and re-evaluate the condition', () => {
@@ -100,57 +86,54 @@ describe('ComponentDataCondition Setters', () => {
const component = cmpRoot.append({
type: DataConditionType,
- condition: {
- left: {
- type: DataVariableType,
- path: 'ds1.left_id.left',
- },
- operator: AnyTypeOperation.equals,
- right: {
- type: DataVariableType,
- path: 'ds1.right_id.right',
+ dataResolver: {
+ condition: {
+ left: {
+ type: DataVariableType,
+ path: 'ds1.left_id.left',
+ },
+ operator: AnyTypeOperation.equals,
+ right: {
+ type: DataVariableType,
+ path: 'ds1.right_id.right',
+ },
},
},
- ifTrue: 'True value
',
- ifFalse: 'False value
',
+ components: [ifTrueComponentDef, ifFalseComponentDef],
})[0] as ComponentDataCondition;
- expect(component.getInnerHTML()).toBe('True value
');
+ expect(component.getInnerHTML()).toContain(ifTrueText);
changeDataSourceValue(dsm, 'Different value');
- expect(component.getInnerHTML()).toBe('False value
');
+ expect(component.getInnerHTML()).toContain(ifFalseText);
+ expect(component.getEl()?.innerHTML).toContain(ifFalseText);
changeDataSourceValue(dsm, 'Name1');
- expect(component.getInnerHTML()).toBe('True value
');
+ expect(component.getInnerHTML()).toContain(ifTrueText);
+ expect(component.getEl()?.innerHTML).toContain(ifTrueText);
});
it('should re-render the component when condition, ifTrue, or ifFalse changes', () => {
const component = cmpRoot.append({
type: DataConditionType,
- condition: {
- left: 0,
- operator: NumberOperation.greaterThan,
- right: -1,
- },
- ifTrue: 'some text
',
- ifFalse: 'false text
',
+ dataResolver: { condition: TRUE_CONDITION },
+ components: [ifTrueComponentDef, ifFalseComponentDef],
})[0] as ComponentDataCondition;
const componentView = component.getView() as ComponentDataConditionView;
- component.setIfTrue('new true text
');
- expect(componentView.el.innerHTML).toContain('new true text');
+ component.setIfTrueComponents(newIfTrueComponentDef);
+
+ expect(component.getInnerHTML()).toContain(newIfTrueText);
+ expect(componentView.el.innerHTML).toContain(newIfTrueText);
- component.setIfFalse('new false text
');
- component.setCondition({
- left: 0,
- operator: NumberOperation.lessThan,
- right: -1,
- });
- expect(componentView.el.innerHTML).toContain('new false text');
+ component.setIfFalseComponents(newIfFalseComponentDef);
+ component.setCondition(FALSE_CONDITION);
+ expect(component.getInnerHTML()).toContain(newIfFalseText);
+ expect(componentView.el.innerHTML).toContain(newIfFalseText);
});
});
-function changeDataSourceValue(dsm: DataSourceManager, newValue: string) {
+export const changeDataSourceValue = (dsm: DataSourceManager, newValue: string) => {
dsm.get('ds1').getRecord('left_id')?.set('left', newValue);
-}
+};
diff --git a/packages/core/test/specs/data_sources/model/conditional_variables/ComponentDataCondition.ts b/packages/core/test/specs/data_sources/model/conditional_variables/ComponentDataCondition.ts
index 222e1bcd7..f3b1987b4 100644
--- a/packages/core/test/specs/data_sources/model/conditional_variables/ComponentDataCondition.ts
+++ b/packages/core/test/specs/data_sources/model/conditional_variables/ComponentDataCondition.ts
@@ -1,14 +1,23 @@
-import { Component, DataSourceManager, Editor } from '../../../../../src';
+import { Component, Components, ComponentView, DataSourceManager, Editor } from '../../../../../src';
+import { DataConditionIfTrueType } from '../../../../../src/data_sources/model/conditional_variables/constants';
import { DataVariableType } from '../../../../../src/data_sources/model/DataVariable';
import { DataConditionType } from '../../../../../src/data_sources/model/conditional_variables/DataCondition';
import { AnyTypeOperation } from '../../../../../src/data_sources/model/conditional_variables/operators/AnyTypeOperator';
import { NumberOperation } from '../../../../../src/data_sources/model/conditional_variables/operators/NumberOperator';
import ComponentDataConditionView from '../../../../../src/data_sources/view/ComponentDataConditionView';
import ComponentWrapper from '../../../../../src/dom_components/model/ComponentWrapper';
-import ComponentTableView from '../../../../../src/dom_components/view/ComponentTableView';
-import ComponentTextView from '../../../../../src/dom_components/view/ComponentTextView';
import EditorModel from '../../../../../src/editor/model/Editor';
-import { setupTestEditor } from '../../../../common';
+import {
+ FALSE_CONDITION,
+ ifFalseComponentDef,
+ ifFalseText,
+ ifTrueComponentDef,
+ ifTrueText,
+ isObjectContained,
+ setupTestEditor,
+ TRUE_CONDITION,
+} from '../../../../common';
+import ComponentDataCondition from '../../../../../src/data_sources/model/conditional_variables/ComponentDataCondition';
describe('ComponentDataCondition', () => {
let editor: Editor;
@@ -24,60 +33,34 @@ describe('ComponentDataCondition', () => {
em.destroy();
});
- it('should add a component with a condition that evaluates a component definition', () => {
+ it('should add a component with a condition', () => {
const component = cmpRoot.append({
type: DataConditionType,
- condition: {
- left: 0,
- operator: NumberOperation.greaterThan,
- right: -1,
- },
- ifTrue: {
- tagName: 'h1',
- type: 'text',
- content: 'some text',
- },
- })[0];
+ dataResolver: { condition: TRUE_CONDITION },
+ components: [ifTrueComponentDef],
+ })[0] as ComponentDataCondition;
expect(component).toBeDefined();
expect(component.get('type')).toBe(DataConditionType);
- expect(component.getInnerHTML()).toBe('some text
');
const componentView = component.getView();
expect(componentView).toBeInstanceOf(ComponentDataConditionView);
- expect(componentView?.el.textContent).toBe('some text');
-
- const childComponent = getFirstChild(component);
- const childView = getFirstChildView(component);
- expect(childComponent).toBeDefined();
- expect(childComponent.get('type')).toBe('text');
- expect(childComponent.getInnerHTML()).toBe('some text');
- expect(childView).toBeInstanceOf(ComponentTextView);
- expect(childView?.el.innerHTML).toBe('some text');
+
+ expect(component.getInnerHTML()).toContain(ifTrueText);
+ expect(component.getEl()?.innerHTML).toContain(ifTrueText);
+ const ifTrueContent = component.getIfTrueContent()!;
+ expect(ifTrueContent.getInnerHTML()).toContain(ifTrueText);
+ expect(ifTrueContent.getEl()?.textContent).toBe(ifTrueText);
+ expect(ifTrueContent.getEl()?.style.display).toBe('');
});
- it('should add a component with a condition that evaluates a string', () => {
+ it('ComponentDataCondition getIfTrueContent and getIfFalseContent', () => {
const component = cmpRoot.append({
type: DataConditionType,
- condition: {
- left: 0,
- operator: NumberOperation.greaterThan,
- right: -1,
- },
- ifTrue: 'some text
',
- })[0];
- expect(component).toBeDefined();
- expect(component.get('type')).toBe(DataConditionType);
- expect(component.getInnerHTML()).toBe('some text
');
- const componentView = component.getView();
- expect(componentView).toBeInstanceOf(ComponentDataConditionView);
- expect(componentView?.el.textContent).toBe('some text');
-
- const childComponent = getFirstChild(component);
- const childView = getFirstChildView(component);
- expect(childComponent).toBeDefined();
- expect(childComponent.get('type')).toBe('text');
- expect(childComponent.getInnerHTML()).toBe('some text');
- expect(childView).toBeInstanceOf(ComponentTextView);
- expect(childView?.el.innerHTML).toBe('some text');
+ dataResolver: { condition: TRUE_CONDITION },
+ components: [ifTrueComponentDef, ifFalseComponentDef],
+ })[0] as ComponentDataCondition;
+
+ expect(JSON.parse(JSON.stringify(component.getIfTrueContent()!))).toEqual(ifTrueComponentDef);
+ expect(JSON.parse(JSON.stringify(component.getIfFalseContent()!))).toEqual(ifFalseComponentDef);
});
it('should test component variable with data-source', () => {
@@ -92,41 +75,49 @@ describe('ComponentDataCondition', () => {
const component = cmpRoot.append({
type: DataConditionType,
- condition: {
- left: {
- type: DataVariableType,
- path: 'ds1.left_id.left',
- },
- operator: AnyTypeOperation.equals,
- right: {
- type: DataVariableType,
- path: 'ds1.right_id.right',
+ dataResolver: {
+ condition: {
+ left: {
+ type: DataVariableType,
+ path: 'ds1.left_id.left',
+ },
+ operator: AnyTypeOperation.equals,
+ right: {
+ type: DataVariableType,
+ path: 'ds1.right_id.right',
+ },
},
},
- ifTrue: {
- tagName: 'h1',
- type: 'text',
- content: 'Some value',
- },
- ifFalse: {
- tagName: 'h1',
- type: 'text',
- content: 'False value',
- },
- })[0];
+ components: [ifTrueComponentDef, ifFalseComponentDef],
+ })[0] as ComponentDataCondition;
+ expect(component.getInnerHTML()).toContain(ifTrueText);
+ expect(component.getEl()?.innerHTML).toContain(ifTrueText);
+ const ifTrueContent = component.getIfTrueContent()!;
+ expect(ifTrueContent.getInnerHTML()).toContain(ifTrueText);
+ expect(ifTrueContent.getEl()?.textContent).toBe(ifTrueText);
+ expect(ifTrueContent.getEl()?.style.display).toBe('');
- const childComponent = getFirstChild(component);
- expect(childComponent).toBeDefined();
- expect(childComponent.get('type')).toBe('text');
- expect(childComponent.getInnerHTML()).toBe('Some value');
+ expect(component.getInnerHTML()).not.toContain(ifFalseText);
+ expect(component.getEl()?.innerHTML).toContain(ifFalseText);
+ const ifFalseContent = component.getIfFalseContent()!;
+ expect(ifFalseContent.getInnerHTML()).toContain(ifFalseText);
+ expect(ifFalseContent.getEl()?.textContent).toBe(ifFalseText);
+ expect(ifFalseContent.getEl()?.style.display).toBe('none');
/* Test changing datasources */
- changeDataSourceValue(dsm, 'Diffirent value');
- expect(getFirstChild(component).getInnerHTML()).toBe('False value');
- expect(getFirstChildView(component)?.el.innerHTML).toBe('False value');
- changeDataSourceValue(dsm, 'Name1');
- expect(getFirstChild(component).getInnerHTML()).toBe('Some value');
- expect(getFirstChildView(component)?.el.innerHTML).toBe('Some value');
+ const WrongValue = 'Diffirent value';
+ changeDataSourceValue(dsm, WrongValue);
+ expect(component.getEl()?.innerHTML).toContain(ifTrueText);
+ expect(component.getEl()?.innerHTML).toContain(ifFalseText);
+ expect(ifTrueContent.getEl()?.style.display).toBe('none');
+ expect(ifFalseContent.getEl()?.style.display).toBe('');
+
+ const CorrectValue = 'Name1';
+ changeDataSourceValue(dsm, CorrectValue);
+ expect(component.getEl()?.innerHTML).toContain(ifTrueText);
+ expect(component.getEl()?.innerHTML).toContain(ifFalseText);
+ expect(ifTrueContent.getEl()?.style.display).toBe('');
+ expect(ifFalseContent.getEl()?.style.display).toBe('none');
});
it('should test a conditional component with a child that is also a conditional component', () => {
@@ -141,65 +132,54 @@ describe('ComponentDataCondition', () => {
const component = cmpRoot.append({
type: DataConditionType,
- condition: {
- left: {
- type: DataVariableType,
- path: 'ds1.left_id.left',
- },
- operator: AnyTypeOperation.equals,
- right: {
- type: DataVariableType,
- path: 'ds1.right_id.right',
+ dataResolver: {
+ condition: {
+ left: {
+ type: DataVariableType,
+ path: 'ds1.left_id.left',
+ },
+ operator: AnyTypeOperation.equals,
+ right: {
+ type: DataVariableType,
+ path: 'ds1.right_id.right',
+ },
},
},
- ifTrue: {
- tagName: 'div',
- components: [
- {
+ components: [
+ {
+ type: DataConditionIfTrueType,
+ components: {
type: DataConditionType,
- condition: {
- left: {
- type: DataVariableType,
- path: 'ds1.left_id.left',
- },
- operator: AnyTypeOperation.equals,
- right: {
- type: DataVariableType,
- path: 'ds1.right_id.right',
+ dataResolver: {
+ condition: {
+ left: {
+ type: DataVariableType,
+ path: 'ds1.left_id.left',
+ },
+ operator: AnyTypeOperation.equals,
+ right: {
+ type: DataVariableType,
+ path: 'ds1.right_id.right',
+ },
},
},
- ifTrue: {
- tagName: 'table',
- type: 'table',
- },
+ components: ifTrueComponentDef,
},
- ],
- },
- })[0];
-
- const innerComponent = getFirstChild(getFirstChild(component));
- const innerComponentView = getFirstChildView(innerComponent);
- const innerHTML = '';
- expect(innerComponent.getInnerHTML()).toBe(innerHTML);
- expect(innerComponentView).toBeInstanceOf(ComponentTableView);
- expect(innerComponentView?.el.tagName).toBe('TABLE');
+ },
+ ifFalseComponentDef,
+ ],
+ })[0] as ComponentDataCondition;
+ const ifTrueContent = component.getIfTrueContent()!;
+ expect(ifTrueContent.getInnerHTML()).toContain(ifTrueText);
+ expect(ifTrueContent.getEl()?.textContent).toBe(ifTrueText);
+ expect(ifTrueContent.getEl()?.style.display).toBe('');
});
it('should store conditional components', () => {
const conditionalCmptDef = {
type: DataConditionType,
- condition: {
- left: 0,
- operator: NumberOperation.greaterThan,
- right: -1,
- },
- ifTrue: [
- {
- tagName: 'h1',
- type: 'text',
- content: 'some text',
- },
- ],
+ dataResolver: { condition: FALSE_CONDITION },
+ components: [ifTrueComponentDef, ifFalseComponentDef],
};
cmpRoot.append(conditionalCmptDef)[0];
@@ -208,18 +188,84 @@ describe('ComponentDataCondition', () => {
const page = projectData.pages[0];
const frame = page.frames[0];
const storageCmptDef = frame.component.components[0];
- expect(storageCmptDef).toEqual(conditionalCmptDef);
+ expect(isObjectContained(storageCmptDef, conditionalCmptDef)).toBe(true);
});
-});
-function changeDataSourceValue(dsm: DataSourceManager, newValue: string) {
- dsm.get('ds1').getRecord('left_id')?.set('left', newValue);
-}
+ it('should dynamically display ifTrue, ifFalse components in the correct order', () => {
+ const component = cmpRoot.append({
+ type: DataConditionType,
+ dataResolver: { condition: TRUE_CONDITION },
+ components: [ifTrueComponentDef, ifFalseComponentDef],
+ })[0] as ComponentDataCondition;
+ const el = component.getEl()!;
+ const ifTrueEl = el.childNodes[0] as any;
+ const ifFalseEl = el.childNodes[1] as any;
+ expect(ifTrueEl.textContent).toContain(ifTrueText);
+ expect(ifTrueEl.style.display).toBe('');
+ expect(ifFalseEl.textContent).toContain(ifFalseText);
+ expect(ifFalseEl.style.display).toBe('none');
-function getFirstChildView(component: Component) {
- return getFirstChild(component).getView();
-}
+ component.setCondition(FALSE_CONDITION);
+ expect(ifTrueEl.style.display).toBe('none');
+ expect(ifTrueEl.textContent).toContain(ifTrueText);
+ expect(ifFalseEl.style.display).toBe('');
+ expect(ifFalseEl.textContent).toContain(ifFalseText);
+ });
+
+ it('should dynamically update display components when data source changes', () => {
+ const dataSource = {
+ id: 'ds1',
+ records: [{ id: 'left_id', left: 1 }],
+ };
+ dsm.add(dataSource);
+
+ const component = cmpRoot.append({
+ type: DataConditionType,
+ dataResolver: {
+ condition: {
+ left: {
+ type: DataVariableType,
+ path: 'ds1.left_id.left',
+ },
+ operator: NumberOperation.greaterThan,
+ right: 0,
+ },
+ },
+ components: [ifTrueComponentDef, ifFalseComponentDef],
+ })[0] as ComponentDataCondition;
+
+ const el = component.view!.el!;
+ const falseValue = -1;
+ changeDataSourceValue(dsm, falseValue);
+ expect(el.innerHTML).toContain(ifTrueText);
+ expect(el.innerHTML).toContain(ifFalseText);
+
+ const ifTrueEl = el.childNodes[0] as any;
+ const ifFalseEl = el.childNodes[1] as any;
+ expect(ifTrueEl!.style.display).toBe('none');
+ expect(ifTrueEl.textContent).toContain(ifTrueText);
+ expect(ifFalseEl.style.display).toBe('');
+ expect(ifFalseEl.textContent).toContain(ifFalseText);
+ });
-function getFirstChild(component: Component) {
- return component.components().at(0);
+ it('should update content of ifTrue, ifFalse components when condition changes', () => {
+ const component = cmpRoot.append({
+ type: DataConditionType,
+ dataResolver: { condition: TRUE_CONDITION },
+ components: [ifTrueComponentDef, ifFalseComponentDef],
+ })[0] as ComponentDataCondition;
+ const el = component.view!.el;
+
+ component.setCondition(FALSE_CONDITION);
+ const ifTrueEl = el.childNodes[0] as any;
+ const ifFalseEl = el.childNodes[1] as any;
+ expect(ifTrueEl!.style.display).toBe('none');
+ expect(ifTrueEl.textContent).toContain(ifTrueText);
+ expect(ifFalseEl.style.display).toBe('');
+ expect(ifFalseEl.textContent).toContain(ifFalseText);
+ });
+});
+
+function changeDataSourceValue(dsm: DataSourceManager, newValue: string | number) {
+ dsm.get('ds1').getRecord('left_id')?.set('left', newValue);
}
diff --git a/packages/core/test/specs/data_sources/serialization.ts b/packages/core/test/specs/data_sources/serialization.ts
index 57c88471e..2f67b17e8 100644
--- a/packages/core/test/specs/data_sources/serialization.ts
+++ b/packages/core/test/specs/data_sources/serialization.ts
@@ -53,8 +53,7 @@ describe('DataSource Serialization', () => {
components: [
{
type: DataVariableType,
- defaultValue: 'default',
- path: `${componentDataSource.id}.id1.content`,
+ dataResolver: { defaultValue: 'default', path: `${componentDataSource.id}.id1.content` },
},
],
})[0];
@@ -118,8 +117,7 @@ describe('DataSource Serialization', () => {
test('ComponentDataVariable', () => {
const dataVariable = {
type: DataVariableType,
- defaultValue: 'default',
- path: `${componentDataSource.id}.id1.content`,
+ dataResolver: { defaultValue: 'default', path: `${componentDataSource.id}.id1.content` },
};
cmpRoot.append({
@@ -309,9 +307,9 @@ describe('DataSource Serialization', () => {
{
components: [
{
- path: 'component-serialization.id1.content',
- type: 'data-variable',
value: 'default',
+ type: DataVariableType,
+ dataResolver: { path: 'component-serialization.id1.content' },
},
],
tagName: 'h1',
@@ -403,7 +401,7 @@ describe('DataSource Serialization', () => {
style: {
color: {
path: 'colors-data.id1.color',
- type: 'data-variable',
+ type: DataVariableType,
defaultValue: 'black',
},
},
diff --git a/packages/core/test/specs/data_sources/storage.ts b/packages/core/test/specs/data_sources/storage.ts
index 47ed85165..9d9475814 100644
--- a/packages/core/test/specs/data_sources/storage.ts
+++ b/packages/core/test/specs/data_sources/storage.ts
@@ -39,8 +39,7 @@ describe('DataSource Storage', () => {
test('ComponentDataVariable', () => {
const dataVariable = {
type: DataVariableType,
- defaultValue: 'default',
- path: `${storedDataSource.id}.id1.content`,
+ dataResolver: { defaultValue: 'default', path: `${storedDataSource.id}.id1.content` },
};
cmpRoot.append({
@@ -87,9 +86,8 @@ describe('DataSource Storage', () => {
{
components: [
{
- defaultValue: 'default',
- path: `${storedDataSource.id}.id1.content`,
- type: 'data-variable',
+ type: DataVariableType,
+ dataResolver: { defaultValue: 'default', path: `${storedDataSource.id}.id1.content` },
},
],
tagName: 'h1',
diff --git a/packages/core/test/specs/data_sources/transformers.ts b/packages/core/test/specs/data_sources/transformers.ts
index 6dbec0c0f..eecb75ee8 100644
--- a/packages/core/test/specs/data_sources/transformers.ts
+++ b/packages/core/test/specs/data_sources/transformers.ts
@@ -41,8 +41,7 @@ describe('DataSource Transformers', () => {
components: [
{
type: DataVariableType,
- defaultValue: 'default',
- path: 'test-data-source.id1.content',
+ dataResolver: { defaultValue: 'default', path: 'test-data-source.id1.content' },
},
],
})[0];
@@ -85,8 +84,7 @@ describe('DataSource Transformers', () => {
components: [
{
type: DataVariableType,
- defaultValue: 'default',
- path: 'test-data-source.id1.content',
+ dataResolver: { defaultValue: 'default', path: 'test-data-source.id1.content' },
},
],
})[0];