mirror of https://github.com/artf/grapesjs.git
Browse Source
* Avoid throwing errors if no condition is passed to a DataCondition * Refactor Condition class * Remove expecting errors for conditions tests * Extend and refactor BaseOperator * Rename and refactor GenericOperator * Rename and refactor logicalOperator to BooleanOperator * Refactor StringOperator * Refactor NumberOperator * Refactor and fix DataResolverListener * Update tests for condition Operators * Rename Condition class to ConditionEvaluator * Add missing types file * Update and refactor DataCondition * Update utils * Refactor StyleableModel class * Update ComponentDataCondition * Refactor ComponentResolverWatcher * Fix conditional styles * Rename LogicalGroupStatement to LogicalGroupEvaluator * Fix tests for DataCondition * Add setter methods for component data variable * Add setters and getter to ComponentDataCondition * Add getters to ComponentDataVariable * Rename test file * Make dynamic components undroppable * Fix collection types * Update collections ( add setters and getter, first item editable, sync styles ) * Update data collection tests * Format tests * Fix some ComponentData collection bugs * Refactor setStartIndex and setEndIndex * Fix getComponentDef test * Fix bug with end index = 0 * fix test for setDataSource * fix test for HTML updates * Format * Add tests for the new option in getDataValue for conditions ( skipDynamicValueResolution ) * rename Operation to DataConditionOperation * Run __onStyleChange after style changes as before * Format * Up BooleanOperator * Avoid raising errors for wrong dynamic value types * Refactor DataCondition * Format * Refactor how component data variable works internally * Symbols fix * Add docs * Refactor componentDatacondition * Add types for Datacondition output ( result ) types * Refactor DataCondition * Refactor ComponentDataCollection * Refactor styleableModel * Add missing files and utils * Up tests for ComponentDataCondition * Add new tests for component data condition * Fix quality check * Move ComponentDataVariable path and default value to dataResolver property * Update component data condition structure * Refactor data condition * Fix ifTrue and ifFalse position in ComponentDataCondition * Fix typescript * Change setIfTrueContent to be setIfTrueComponents * Fix memory leak for binding to this.postRender * Cleanup ComponentDataConditionView * Up tests --------- Co-authored-by: Artur Arseniev <artur.catch@hotmail.it>pull/6460/head
committed by
GitHub
27 changed files with 789 additions and 490 deletions
@ -1,61 +1,123 @@ |
|||||
import Component from '../../../dom_components/model/Component'; |
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 { toLowerCase } from '../../../utils/mixins'; |
||||
import { DataCondition, DataConditionProps, DataConditionType } from './DataCondition'; |
import { DataCondition, DataConditionOutputChangedEvent, DataConditionProps, DataConditionType } from './DataCondition'; |
||||
import { ConditionProps } from './DataConditionEvaluator'; |
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 { |
export default class ComponentDataCondition extends Component { |
||||
dataResolver: DataCondition; |
dataResolver: DataCondition; |
||||
|
|
||||
constructor(props: DataConditionProps, opt: ComponentOptions) { |
get defaults(): ComponentDefinitionDefined { |
||||
const dataConditionInstance = new DataCondition(props, { em: opt.em }); |
return { |
||||
|
// @ts-ignore
|
||||
super( |
...super.defaults, |
||||
{ |
droppable: false, |
||||
...props, |
type: DataConditionType, |
||||
type: DataConditionType, |
dataResolver: { |
||||
components: dataConditionInstance.getDataValue(), |
condition: { |
||||
droppable: false, |
left: '', |
||||
|
operator: StringOperation.equalsIgnoreCase, |
||||
|
right: '', |
||||
|
}, |
||||
}, |
}, |
||||
opt, |
components: [ |
||||
); |
{ |
||||
this.dataResolver = dataConditionInstance; |
type: DataConditionIfTrueType, |
||||
this.dataResolver.onValueChange = this.handleConditionChange.bind(this); |
}, |
||||
|
{ |
||||
|
type: DataConditionIfFalseType, |
||||
|
}, |
||||
|
], |
||||
|
}; |
||||
} |
} |
||||
|
|
||||
getCondition() { |
constructor(props: ComponentDataConditionProps, opt: ComponentOptions) { |
||||
return this.dataResolver.getCondition(); |
// @ts-ignore
|
||||
|
super(props, opt); |
||||
|
|
||||
|
const { condition } = props.dataResolver; |
||||
|
this.dataResolver = new DataCondition({ condition }, { em: opt.em }); |
||||
|
|
||||
|
this.listenToPropsChange(); |
||||
} |
} |
||||
|
|
||||
getIfTrue() { |
isTrue() { |
||||
return this.dataResolver.getIfTrue(); |
return this.dataResolver.isTrue(); |
||||
} |
} |
||||
|
|
||||
getIfFalse() { |
getCondition() { |
||||
return this.dataResolver.getIfFalse(); |
return this.dataResolver.getCondition(); |
||||
} |
} |
||||
|
|
||||
private handleConditionChange() { |
getIfTrueContent(): Component | undefined { |
||||
this.components(this.dataResolver.getDataValue()); |
return this.components().at(0); |
||||
} |
} |
||||
|
|
||||
static isComponent(el: HTMLElement) { |
getIfFalseContent(): Component | undefined { |
||||
return toLowerCase(el.tagName) === DataConditionType; |
return this.components().at(1); |
||||
|
} |
||||
|
|
||||
|
getOutputContent(): Component | undefined { |
||||
|
return this.isTrue() ? this.getIfTrueContent() : this.getIfFalseContent(); |
||||
} |
} |
||||
|
|
||||
setCondition(newCondition: ConditionProps) { |
setCondition(newCondition: ConditionProps) { |
||||
this.dataResolver.setCondition(newCondition); |
this.dataResolver.setCondition(newCondition); |
||||
} |
} |
||||
|
|
||||
setIfTrue(newIfTrue: any) { |
setIfTrueComponents(content: ComponentAddType) { |
||||
this.dataResolver.setIfTrue(newIfTrue); |
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) { |
toJSON(opts?: ObjectAny): ComponentProperties { |
||||
this.dataResolver.setIfFalse(newIfFalse); |
const json = super.toJSON(opts); |
||||
|
const dataResolver = this.dataResolver.toJSON(); |
||||
|
delete dataResolver.type; |
||||
|
delete dataResolver.ifTrue; |
||||
|
delete dataResolver.ifFalse; |
||||
|
|
||||
|
return { |
||||
|
...json, |
||||
|
dataResolver, |
||||
|
}; |
||||
} |
} |
||||
|
|
||||
toJSON(): ComponentDefinition { |
static isComponent(el: HTMLElement) { |
||||
return this.dataResolver.toJSON(); |
return toLowerCase(el.tagName) === DataConditionType; |
||||
} |
} |
||||
} |
} |
||||
|
|||||
@ -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)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,2 @@ |
|||||
|
export const DataConditionIfTrueType = 'data-condition-true-content'; |
||||
|
export const DataConditionIfFalseType = 'data-condition-false-content'; |
||||
@ -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(); |
|
||||
} |
|
||||
@ -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); |
||||
|
}; |
||||
@ -1,4 +1,46 @@ |
|||||
import ComponentView from '../../dom_components/view/ComponentView'; |
import ComponentView from '../../dom_components/view/ComponentView'; |
||||
import ComponentDataCondition from '../model/conditional_variables/ComponentDataCondition'; |
import ComponentDataCondition from '../model/conditional_variables/ComponentDataCondition'; |
||||
|
import DataResolverListener from '../model/DataResolverListener'; |
||||
|
|
||||
export default class ComponentDataConditionView extends ComponentView<ComponentDataCondition> {} |
export default class ComponentDataConditionView extends ComponentView<ComponentDataCondition> { |
||||
|
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(); |
||||
|
} |
||||
|
} |
||||
|
|||||
Loading…
Reference in new issue