Skip to content

Commit

Permalink
Move out Script generation logic from the Component
Browse files Browse the repository at this point in the history
  • Loading branch information
xQwexx committed Feb 26, 2024
1 parent 639305d commit 9f424d6
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 105 deletions.
36 changes: 19 additions & 17 deletions src/canvas/view/CanvasView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -566,8 +566,7 @@ export default class CanvasView extends ModuleView<Canvas> {
* @param {ModuleView} view Component's View
* @private
*/
//TODO change type after the ComponentView was updated to ts
updateScript(view: any) {
updateScript(view: ComponentView) {
const model = view.model;
const id = model.getId();

Expand All @@ -577,26 +576,29 @@ export default class CanvasView extends ModuleView<Canvas> {
jsEl?.appendChild(view.scriptContainer);
}

view.el.id = id;
view.scriptContainer.innerHTML = '';
// In editor, I make use of setTimeout as during the append process of elements
// those will not be available immediately, therefore 'item' variable
const script = document.createElement('script');
const scriptFn = model.getScriptString();
const scriptFnStr = model.get('script-props') ? scriptFn : `function(){\n${scriptFn}\n;}`;
const scriptProps = JSON.stringify(model.__getScriptProps());
script.innerHTML = `
const scriptComponent = model.scriptSubComp;
if (scriptComponent) {
view.el.id = id;
view.scriptContainer.innerHTML = '';
// In editor, I make use of setTimeout as during the append process of elements
// those will not be available immediately, therefore 'item' variable
const script = document.createElement('script');
const scriptFn = scriptComponent.getScriptString();
const scriptFnStr = model.get('script-props') ? scriptFn : `function(){\n${scriptFn}\n;}`;
const scriptProps = JSON.stringify(scriptComponent.__getScriptProps());
script.innerHTML = `
setTimeout(function() {
var item = document.getElementById('${id}');
if (!item) return;
(${scriptFnStr}.bind(item))(${scriptProps})
}, 1);`;
// #873
// Adding setTimeout will make js components work on init of the editor
setTimeout(() => {
const scr = view.scriptContainer;
scr?.appendChild(script);
}, 0);
// #873
// Adding setTimeout will make js components work on init of the editor
setTimeout(() => {
const scr = view.scriptContainer;
scr?.appendChild(script);
}, 0);
}
}

/**
Expand Down
12 changes: 6 additions & 6 deletions src/code_manager/model/JsGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ export default class JsGenerator extends Model {

mapModel(model: Component) {
let code = '';
const script = model.get('script-export') || model.get('script');
const script = model.scriptSubComp;
const type = model.get('type')!;
const comps = model.get('components')!;
const id = model.getId();
Expand All @@ -28,19 +28,19 @@ export default class JsGenerator extends Model {
let attr = model.get('attributes');
attr = extend({}, attr, { id });
model.set('attributes', attr, { silent: true });
// @ts-ignore
const scrStr = model.getScriptString(script);
const scrProps = model.get('script-props');

const scrStr = script.getScriptString();
const scrProps = script.props;

// If the script was updated, I'll put its code in a separate container
if (model.get('scriptUpdated') && !scrProps) {
if (script.get('scriptUpdated') && !scrProps) {
this.mapJs[type + '-' + id] = { ids: [id], code: scrStr };
} else {
let props;
const mapType = this.mapJs[type];

if (scrProps) {
props = model.__getScriptProps();
props = script.__getScriptProps();
}

if (mapType) {
Expand Down
94 changes: 13 additions & 81 deletions src/dom_components/model/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,10 @@ import CssRule, { CssRuleJSON } from '../../css_composer/model/CssRule';
import Trait from '../../trait_manager/model/Trait';
import { ToolbarButtonProps } from './ToolbarButton';
import { TraitProperties } from '../../trait_manager/types';
import ScriptSubComponent, { ScriptData } from './modules/ScriptSubComponent';

export interface IComponent extends ExtractMethods<Component> {}

const escapeRegExp = (str: string) => {
return str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
};

export const avoidInline = (em: EditorModel) => !!em?.getConfig().avoidInlineStyle;

export const eventDrag = 'component:drag';
Expand Down Expand Up @@ -219,6 +216,7 @@ export default class Component extends StyleableModel<ComponentProperties> {
frame?: Frame;
rule?: CssRule;
prevColl?: Components;
scriptSubComp?: ScriptSubComponent;
__hasUm?: boolean;
__symbReady?: boolean;
/**
Expand Down Expand Up @@ -263,8 +261,7 @@ export default class Component extends StyleableModel<ComponentProperties> {
this.initComponents();
this.initTraits();
this.initToolbar();
this.initScriptProps();
this.listenTo(this, 'change:script', this.scriptUpdated);
this.initScript();
this.listenTo(this, 'change:tagName', this.tagUpdated);
this.listenTo(this, 'change:attributes', this.attrUpdated);
this.listenTo(this, 'change:attributes:id', this._idUpdated);
Expand Down Expand Up @@ -692,8 +689,7 @@ export default class Component extends StyleableModel<ComponentProperties> {
this.__getSymbol() ||
this.__getSymbols() ||
// Components with script should always have an id
this.get('script-export') ||
this.get('script')
this.scriptSubComp
) {
addId = true;
}
Expand Down Expand Up @@ -1078,24 +1074,17 @@ export default class Component extends StyleableModel<ComponentProperties> {
return this;
}

initScriptProps() {
initScript() {
if (this.opt.temporary) return;
const prop = 'script-props';
const toListen: any = [`change:${prop}`, this.initScriptProps];
this.off(...toListen);
const prevProps = this.previous(prop) || [];
const newProps = this.get(prop) || [];
const prevPropsEv = prevProps.map(e => `change:${e}`).join(' ');
const newPropsEv = newProps.map(e => `change:${e}`).join(' ');
prevPropsEv && this.off(prevPropsEv, this.__scriptPropsChange);
newPropsEv && this.on(newPropsEv, this.__scriptPropsChange);
// @ts-ignore
this.on(...toListen);
}

__scriptPropsChange(m: any, v: any, opts: any = {}) {
if (opts.avoidStore) return;
this.trigger('rerender');
let scriptData: ScriptData | string | ((...params: any[]) => any) | undefined =
this.get('script-export') || (this.get('script') as any);
if (!isUndefined(scriptData)) {
if (isString(scriptData) || isFunction(scriptData)) {
scriptData = { main: scriptData, props: this.get('script-props') ?? [] };
}
this.scriptSubComp = new ScriptSubComponent(this, scriptData);
}
}

/**
Expand Down Expand Up @@ -1212,14 +1201,6 @@ export default class Component extends StyleableModel<ComponentProperties> {
return parent ? [parent].concat(parent.parents()) : [];
}

/**
* Script updated
* @private
*/
scriptUpdated() {
this.set('scriptUpdated', 1);
}

/**
* Init toolbar
* @private
Expand Down Expand Up @@ -1774,55 +1755,6 @@ export default class Component extends StyleableModel<ComponentProperties> {
return this.getView(frame);
}

__getScriptProps() {
const modelProps = this.props();
const scrProps = this.get('script-props') || [];
return scrProps.reduce((acc, prop) => {
acc[prop] = modelProps[prop];
return acc;
}, {} as Partial<ComponentProperties>);
}

/**
* Return script in string format, cleans 'function() {..' from scripts
* if it's a function
* @param {string|Function} script
* @return {string}
* @private
*/
getScriptString(script?: string | Function) {
let scr = script || this.get('script') || '';

if (!scr) {
return scr;
}

if (this.get('script-props')) {
scr = scr.toString().trim();
} else {
// Deprecated
// Need to convert script functions to strings
if (isFunction(scr)) {
let scrStr = scr.toString().trim();
scrStr = scrStr.slice(scrStr.indexOf('{') + 1, scrStr.lastIndexOf('}'));
scr = scrStr.trim();
}

const config = this.em.getConfig();
const tagVarStart = escapeRegExp(config.tagVarStart || '{[ ');
const tagVarEnd = escapeRegExp(config.tagVarEnd || ' ]}');
const reg = new RegExp(`${tagVarStart}([\\w\\d-]*)${tagVarEnd}`, 'g');
scr = scr.replace(reg, (match, v) => {
// If at least one match is found I have to track this change for a
// better optimization inside JS generator
this.scriptUpdated();
const result = this.attributes[v] || '';
return isArray(result) || typeof result == 'object' ? JSON.stringify(result) : result;
});
}
return scr;
}

emitUpdate(property?: string, ...args: any[]) {
const { em } = this;
const event = keyUpdate + (property ? `:${property}` : '');
Expand Down
114 changes: 114 additions & 0 deletions src/dom_components/model/modules/ScriptSubComponent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import { isArray, isFunction } from 'underscore';
import { Model } from '../../../common';
import Component from '../Component';
import { ComponentProperties } from '../types';

export interface ScriptData {
main: string | ((...params: any[]) => any);
props: string[];
}
const escapeRegExp = (str: string) => {
return str.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&');
};

export default class ScriptSubComponent extends Model {
//@ts-ignore
get defaults() {
return {
main: '',
props: '',
scriptUpdated: false,
};
}

constructor(component: Component, script: ScriptData) {
super({ component, ...script });
this.listenTo(this, 'change:main', this.scriptUpdated);
}

get component() {
return this.get('component');
}

get props(): string[] {
return this.get('props');
}

initScriptProps() {
const { component } = this;
const prop = 'props';
const toListen: any = [`change:${prop}`, this.initScriptProps];
this.off(...toListen);
const prevProps: string[] = this.previous(prop) || [];
const newProps: string[] = this.get(prop) || [];
const prevPropsEv = prevProps.map(e => `change:${e}`).join(' ');
const newPropsEv = newProps.map(e => `change:${e}`).join(' ');
prevPropsEv && component.off(prevPropsEv, this.__scriptPropsChange);
newPropsEv && component.on(newPropsEv, this.__scriptPropsChange);
// @ts-ignore
this.on(...toListen);
}

__scriptPropsChange(m: any, v: any, opts: any = {}) {
if (opts.avoidStore) return;
this.component.trigger('rerender');
}

/**
* Script updated
* @private
*/
scriptUpdated() {
this.set('scriptUpdated', true);
}

__getScriptProps() {
const modelProps = this.component.props();
const scrProps = this.props || [];
return scrProps.reduce((acc, prop) => {
acc[prop] = modelProps[prop];
return acc;
}, {} as Partial<ComponentProperties>);
}

/**
* Return script in string format, cleans 'function() {..' from scripts
* if it's a function
* @param {string|Function} script
* @return {string}
* @private
*/
getScriptString(script?: string | Function) {
const { component } = this;
let scr: string = script || this.get('main') || '';

if (!scr) {
return scr;
}

if (this.props) {
scr = scr.toString().trim();
} else {
// Deprecated
// Need to convert script functions to strings
if (isFunction(scr)) {
let scrStr = scr.toString().trim();
scrStr = scrStr.slice(scrStr.indexOf('{') + 1, scrStr.lastIndexOf('}'));
scr = scrStr.trim();
}

const config = component.em.getConfig();
const tagVarStart = escapeRegExp(config.tagVarStart || '{[ ');
const tagVarEnd = escapeRegExp(config.tagVarEnd || ' ]}');
const reg = new RegExp(`${tagVarStart}([\\w\\d-]*)${tagVarEnd}`, 'g');
scr = scr.replace(reg, (match, v) => {
// If at least one match is found I have to track this change for a
// better optimization inside JS generator
this.scriptUpdated();
const result = component.attributes[v] || '';
return isArray(result) || typeof result == 'object' ? JSON.stringify(result) : result;
});
}
return scr;
}
}
2 changes: 1 addition & 1 deletion src/dom_components/view/ComponentView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ TComp> {
*/
updateScript() {
const { model, em } = this;
if (!model.get('script')) return;
if (!model.scriptSubComp) return;
em?.Canvas.getCanvasView().updateScript(this);
}

Expand Down

0 comments on commit 9f424d6

Please sign in to comment.