Skip to content

Commit

Permalink
Add Components dynamic values ( props - attributes ) (#6351)
Browse files Browse the repository at this point in the history
  • Loading branch information
mohamedsalem401 authored Dec 30, 2024
1 parent b60bf5f commit 21f51ae
Show file tree
Hide file tree
Showing 17 changed files with 1,131 additions and 771 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,11 @@ import { stringToPath } from '../../utils/mixins';
import { Model } from '../../common';
import EditorModel from '../../editor/model/Editor';
import DataVariable, { DataVariableType } from './DataVariable';
import ComponentView from '../../dom_components/view/ComponentView';
import { DynamicValue } from '../types';
import { DataCondition, ConditionalVariableType } from './conditional_variables/DataCondition';
import ComponentDataVariable from './ComponentDataVariable';

export interface DynamicVariableListenerManagerOptions {
model: Model | ComponentView;
em: EditorModel;
dataVariable: DynamicValue;
updateValueFromDataVariable: (value: any) => void;
Expand All @@ -18,13 +16,12 @@ export interface DynamicVariableListenerManagerOptions {
export default class DynamicVariableListenerManager {
private dataListeners: DataVariableListener[] = [];
private em: EditorModel;
private model: Model | ComponentView;
private dynamicVariable: DynamicValue;
dynamicVariable: DynamicValue;
private updateValueFromDynamicVariable: (value: any) => void;
private model = new Model();

constructor(options: DynamicVariableListenerManagerOptions) {
this.em = options.em;
this.model = options.model;
this.dynamicVariable = options.dataVariable;
this.updateValueFromDynamicVariable = options.updateValueFromDataVariable;

Expand All @@ -37,7 +34,7 @@ export default class DynamicVariableListenerManager {
};

listenToDynamicVariable() {
const { em, dynamicVariable, model } = this;
const { em, dynamicVariable } = this;
this.removeListeners();

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

this.dataListeners = dataListeners;
}
Expand Down Expand Up @@ -81,8 +78,7 @@ export default class DynamicVariableListenerManager {
}

private removeListeners() {
const { model } = this;
this.dataListeners.forEach((ls) => model.stopListening(ls.obj, ls.event, this.onChange));
this.dataListeners.forEach((ls) => this.model.stopListening(ls.obj, ls.event, this.onChange));
this.dataListeners = [];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,6 @@ export class DataCondition extends Model<DataConditionType> {
dataVariables.forEach((variable) => {
const variableInstance = new DataVariable(variable, { em: this.em });
const listener = new DynamicVariableListenerManager({
model: this as any,
em: this.em!,
dataVariable: variableInstance,
updateValueFromDataVariable: (() => {
Expand Down
28 changes: 27 additions & 1 deletion packages/core/src/data_sources/model/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { ConditionalVariableType, DataCondition } from './conditional_variables/
import DataVariable, { DataVariableType } from './DataVariable';

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

export function isDynamicValue(value: any): value is DynamicValue {
Expand All @@ -22,3 +22,29 @@ export function isDataCondition(variable: any) {
export function evaluateVariable(variable: any, em: EditorModel) {
return isDataVariable(variable) ? new DataVariable(variable, { em }).getDataValue() : variable;
}

export function getDynamicValueInstance(valueDefinition: DynamicValueDefinition, em: EditorModel): DynamicValue {
const dynamicType = valueDefinition.type;
let dynamicVariable: DynamicValue;

switch (dynamicType) {
case DataVariableType:
dynamicVariable = new DataVariable(valueDefinition, { em: em });
break;
case ConditionalVariableType: {
const { condition, ifTrue, ifFalse } = valueDefinition;
dynamicVariable = new DataCondition(condition, ifTrue, ifFalse, { em: em });
break;
}
default:
throw new Error(`Unsupported dynamic type: ${dynamicType}`);
}

return dynamicVariable;
}

export function evaluateDynamicValueDefinition(valueDefinition: DynamicValueDefinition, em: EditorModel) {
const dynamicVariable = getDynamicValueInstance(valueDefinition, em);

return { variable: dynamicVariable, value: dynamicVariable.getDataValue() };
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export default class ComponentDataVariableView extends ComponentView<ComponentDa
initialize(opt = {}) {
super.initialize(opt);
this.dynamicVariableListener = new DynamicVariableListenerManager({
model: this,
em: this.em!,
dataVariable: this.model,
updateValueFromDataVariable: () => this.postRender(),
Expand Down
125 changes: 79 additions & 46 deletions packages/core/src/dom_components/model/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from 'underscore';
import { shallowDiff, capitalize, isEmptyObj, isObject, toLowerCase } from '../../utils/mixins';
import StyleableModel, { StyleProps, UpdateStyleOptions } from '../../domain_abstract/model/StyleableModel';
import { Model } from 'backbone';
import { Model, ModelDestroyOptions } from 'backbone';
import Components from './Components';
import Selector from '../../selector_manager/model/Selector';
import Selectors from '../../selector_manager/model/Selectors';
Expand Down Expand Up @@ -51,14 +51,17 @@ import {
updateSymbolComps,
updateSymbolProps,
} from './SymbolUtils';
import TraitDataVariable from '../../data_sources/model/TraitDataVariable';
import { ConditionalVariableType, DataCondition } from '../../data_sources/model/conditional_variables/DataCondition';
import { isDynamicValue, isDynamicValueDefinition } from '../../data_sources/model/utils';
import { ComponentDynamicValueWatcher } from './ComponentDynamicValueWatcher';
import { DynamicValueWatcher } from './DynamicValueWatcher';
import { DynamicValueDefinition } from '../../data_sources/types';

export interface IComponent extends ExtractMethods<Component> {}

export interface SetAttrOptions extends SetOptions, UpdateStyleOptions {}
export interface DynamicWatchersOptions {
skipWatcherUpdates?: boolean;
fromDataSource?: boolean;
}
export interface SetAttrOptions extends SetOptions, UpdateStyleOptions, DynamicWatchersOptions {}
export interface ComponentSetOptions extends SetOptions, DynamicWatchersOptions {}

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

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

constructor(props: ComponentProperties = {}, opt: ComponentOptions) {
super(props, opt);
this.componentDVListener = new ComponentDynamicValueWatcher(this, opt.em);
this.componentDVListener.addProps(props);

bindAll(this, '__upSymbProps', '__upSymbCls', '__upSymbComps');
const em = opt.em;

Expand All @@ -289,7 +295,7 @@ export default class Component extends StyleableModel<ComponentProperties> {
this.opt = opt;
this.em = em!;
this.config = opt.config || {};
this.set('attributes', {
this.setAttributes({
...(result(this, 'defaults').attributes || {}),
...(this.get('attributes') || {}),
});
Expand Down Expand Up @@ -331,6 +337,36 @@ export default class Component extends StyleableModel<ComponentProperties> {
}
}

set<A extends string>(
keyOrAttributes: A | Partial<ComponentProperties>,
valueOrOptions?: ComponentProperties[A] | ComponentSetOptions,
optionsOrUndefined?: ComponentSetOptions,
): this {
let attributes: Partial<ComponentProperties>;
let options: ComponentSetOptions = { skipWatcherUpdates: false, fromDataSource: false };
if (typeof keyOrAttributes === 'object') {
attributes = keyOrAttributes;
options = valueOrOptions || (options as ComponentSetOptions);
} else if (typeof keyOrAttributes === 'string') {
attributes = { [keyOrAttributes as string]: valueOrOptions };
options = optionsOrUndefined || options;
} else {
attributes = {};
options = optionsOrUndefined || options;
}

// @ts-ignore
const em = this.em || options.em;
const evaluatedAttributes = DynamicValueWatcher.getStaticValues(attributes, em);

const shouldSkipWatcherUpdates = options.skipWatcherUpdates || options.fromDataSource;
if (!shouldSkipWatcherUpdates) {
this.componentDVListener?.addProps(attributes);
}

return super.set(evaluatedAttributes, options);
}

__postAdd(opts: { recursive?: boolean } = {}) {
const { em } = this;
const um = em?.UndoManager;
Expand Down Expand Up @@ -648,8 +684,16 @@ export default class Component extends StyleableModel<ComponentProperties> {
* @example
* component.setAttributes({ id: 'test', 'data-key': 'value' });
*/
setAttributes(attrs: ObjectAny, opts: SetAttrOptions = {}) {
this.set('attributes', { ...attrs }, opts);
setAttributes(attrs: ObjectAny, opts: SetAttrOptions = { skipWatcherUpdates: false, fromDataSource: false }) {
// @ts-ignore
const em = this.em || opts.em;
const evaluatedAttributes = DynamicValueWatcher.getStaticValues(attrs, em);
const shouldSkipWatcherUpdates = opts.skipWatcherUpdates || opts.fromDataSource;
if (!shouldSkipWatcherUpdates) {
this.componentDVListener.setAttributes(attrs);
}
this.set('attributes', { ...evaluatedAttributes }, opts);

return this;
}

Expand All @@ -662,9 +706,11 @@ export default class Component extends StyleableModel<ComponentProperties> {
* component.addAttributes({ 'data-key': 'value' });
*/
addAttributes(attrs: ObjectAny, opts: SetAttrOptions = {}) {
const dynamicAttributes = this.componentDVListener.getDynamicAttributesDefs();
return this.setAttributes(
{
...this.getAttributes({ noClass: true }),
...dynamicAttributes,
...attrs,
},
opts,
Expand All @@ -682,6 +728,8 @@ export default class Component extends StyleableModel<ComponentProperties> {
*/
removeAttributes(attrs: string | string[] = [], opts: SetOptions = {}) {
const attrArr = Array.isArray(attrs) ? attrs : [attrs];
this.componentDVListener.removeAttributes(attrArr);

const compAttr = this.getAttributes();
attrArr.map((i) => delete compAttr[i]);
return this.setAttributes(compAttr, opts);
Expand Down Expand Up @@ -773,29 +821,6 @@ export default class Component extends StyleableModel<ComponentProperties> {
}
}

const attrDataVariable = this.get(dynamicAttrKey) as {
[key: string]: TraitDataVariable | DynamicValueDefinition;
};
if (attrDataVariable) {
Object.entries(attrDataVariable).forEach(([key, value]) => {
let dataVariable: TraitDataVariable | DataCondition;
if (isDynamicValue(value)) {
dataVariable = value;
} else if (isDynamicValueDefinition(value)) {
const type = value.type;

if (type === ConditionalVariableType) {
const { condition, ifTrue, ifFalse } = value;
dataVariable = new DataCondition(condition, ifTrue, ifFalse, { em });
} else {
dataVariable = new TraitDataVariable(value, { em });
}
}

attributes[key] = dataVariable!.getDataValue();
});
}

// Check if we need an ID on the component
if (!has(attributes, 'id')) {
let addId = false;
Expand Down Expand Up @@ -934,7 +959,6 @@ export default class Component extends StyleableModel<ComponentProperties> {
this.off(event, this.initTraits);
this.__loadTraits();
const attrs = { ...this.get('attributes') };
const traitDynamicValueAttr: ObjectAny = {};
const traits = this.traits;
traits.each((trait) => {
const name = trait.getName();
Expand All @@ -945,13 +969,13 @@ export default class Component extends StyleableModel<ComponentProperties> {
} else {
if (name && value) attrs[name] = value;
}

if (trait.dynamicVariable) {
traitDynamicValueAttr[name] = trait.dynamicVariable;
}
});
traits.length && this.set('attributes', attrs);
Object.keys(traitDynamicValueAttr).length && this.set(dynamicAttrKey, traitDynamicValueAttr);
const dynamicAttributes = this.componentDVListener.getDynamicAttributesDefs();
traits.length &&
this.setAttributes({
...attrs,
...dynamicAttributes,
});
this.on(event, this.initTraits);
changed && em && em.trigger('component:toggled');
return this;
Expand Down Expand Up @@ -1147,7 +1171,6 @@ export default class Component extends StyleableModel<ComponentProperties> {
traits.setTarget(this);

if (traitsI.length) {
traitsI.forEach((tr) => tr.attributes && delete tr.attributes.value);
traits.add(traitsI);
}

Expand Down Expand Up @@ -1294,12 +1317,15 @@ export default class Component extends StyleableModel<ComponentProperties> {
* @ts-ignore */
clone(opt: { symbol?: boolean; symbolInv?: boolean } = {}): this {
const em = this.em;
const attr = { ...this.attributes };
const attr = {
...this.componentDVListener.getPropsDefsOrValues(this.attributes),
};
const opts = { ...this.opt };
const id = this.getId();
const cssc = em?.Css;
attr.attributes = { ...attr.attributes };
delete attr.attributes.id;
attr.attributes = {
...(attr.attributes ? this.componentDVListener.getAttributesDefsOrValues(attr.attributes) : undefined),
};
// @ts-ignore
attr.components = [];
// @ts-ignore
Expand Down Expand Up @@ -1554,8 +1580,10 @@ export default class Component extends StyleableModel<ComponentProperties> {
* @private
*/
toJSON(opts: ObjectAny = {}): ComponentDefinition {
const obj = Model.prototype.toJSON.call(this, opts);
obj.attributes = this.getAttributes();
let obj = Model.prototype.toJSON.call(this, opts);
obj = { ...obj, ...this.componentDVListener.getDynamicPropsDefs() };
obj.attributes = this.componentDVListener.getAttributesDefsOrValues(this.getAttributes());
delete obj.componentDVListener;
delete obj.attributes.class;
delete obj.toolbar;
delete obj.traits;
Expand Down Expand Up @@ -1789,6 +1817,11 @@ export default class Component extends StyleableModel<ComponentProperties> {
return this;
}

destroy(options?: ModelDestroyOptions | undefined): false | JQueryXHR {
this.componentDVListener.destroy();
return super.destroy(options);
}

/**
* Move the component to another destination component
* @param {Component} component Destination component (so the current one will be appended as a child)
Expand Down
Loading

0 comments on commit 21f51ae

Please sign in to comment.