diff --git a/packages/core/src/canvas/view/CanvasView.ts b/packages/core/src/canvas/view/CanvasView.ts index a0f3f5a883..62132c86aa 100644 --- a/packages/core/src/canvas/view/CanvasView.ts +++ b/packages/core/src/canvas/view/CanvasView.ts @@ -19,6 +19,7 @@ import Frame from '../model/Frame'; import { GetBoxRectOptions, ToWorldOption } from '../types'; import FrameView from './FrameView'; import FramesView from './FramesView'; +import { ComponentsEvents } from '../../dom_components/types'; export interface MarginPaddingOffsets { marginTop?: number; @@ -567,9 +568,10 @@ export default class CanvasView extends ModuleView { * @private */ //TODO change type after the ComponentView was updated to ts - updateScript(view: any) { - const model = view.model; - const id = model.getId(); + updateScript(view: ComponentView) { + const component = view.model; + const id = component.getId(); + const dataToEmit = { component, view, el: view.el }; if (!view.scriptContainer) { view.scriptContainer = createEl('div', { 'data-id': id }); @@ -582,21 +584,23 @@ export default class CanvasView extends ModuleView { // 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()); + const scriptFn = component.getScriptString(); + const scriptFnStr = component.get('script-props') ? scriptFn : `function(){\n${scriptFn}\n;}`; + const scriptProps = JSON.stringify(component.__getScriptProps()); script.innerHTML = ` setTimeout(function() { var item = document.getElementById('${id}'); if (!item) return; var script = (${scriptFnStr}).bind(item); - script(${scriptProps}); + script(${scriptProps}, { el: item }); }, 1);`; - // #873 - // Adding setTimeout will make js components work on init of the editor + + // #873 Adding setTimeout will make js components work on init of the editor setTimeout(() => { + component.emitWithEitor(ComponentsEvents.scriptMountBefore, dataToEmit); const scr = view.scriptContainer; scr?.appendChild(script); + component.emitWithEitor(ComponentsEvents.scriptMount, dataToEmit); }, 0); } diff --git a/packages/core/src/dom_components/model/Component.ts b/packages/core/src/dom_components/model/Component.ts index a627f88136..4920f8ffb1 100644 --- a/packages/core/src/dom_components/model/Component.ts +++ b/packages/core/src/dom_components/model/Component.ts @@ -1785,6 +1785,10 @@ export default class Component extends StyleableModel { }); } + emitWithEitor(event: string, data?: Record) { + [this.em, this].forEach((item) => item?.trigger(event, data)); + } + /** * Execute callback function on itself and all inner components * @param {Function} clb Callback function, the model is passed as an argument diff --git a/packages/core/src/dom_components/model/Components.ts b/packages/core/src/dom_components/model/Components.ts index 964478ca64..36568a8a4c 100644 --- a/packages/core/src/dom_components/model/Components.ts +++ b/packages/core/src/dom_components/model/Components.ts @@ -203,6 +203,10 @@ Component> { if (!removed.opt.temporary) { em.Commands.run('core:component-style-clear', { target: removed }); + removed.views.forEach((view) => { + view.scriptContainer && + removed.emitWithEitor(ComponentsEvents.scriptUnmount, { component: removed, view, el: view.el }); + }); removed.removed(); removed.trigger('removed'); em.trigger(ComponentsEvents.remove, removed); diff --git a/packages/core/src/dom_components/types.ts b/packages/core/src/dom_components/types.ts index aa81abcfbd..2af662023e 100644 --- a/packages/core/src/dom_components/types.ts +++ b/packages/core/src/dom_components/types.ts @@ -60,6 +60,21 @@ export enum ComponentsEvents { select = 'component:select', selectBefore = 'component:select:before', + /** + * @event `component:script:mount` Component with script is mounted. + * @example + * editor.on('component:script:mount', ({ component, view, el }) => { ... }); + */ + scriptMount = 'component:script:mount', + scriptMountBefore = 'component:script:mount:before', + + /** + * @event `component:script:unmount` Component with script is unmounted. This is triggered when the component is removed or the script execution has to be refreshed. This event might be useful to clean up resources. + * @example + * editor.on('component:script:unmount', ({ component, view, el }) => { ... }); + */ + scriptUnmount = 'component:script:unmount', + /** * @event `symbol:main:add` Added new main symbol. * @example diff --git a/packages/core/src/dom_components/view/ComponentView.ts b/packages/core/src/dom_components/view/ComponentView.ts index 610213f81e..d524f1ee5a 100644 --- a/packages/core/src/dom_components/view/ComponentView.ts +++ b/packages/core/src/dom_components/view/ComponentView.ts @@ -13,6 +13,7 @@ import Component, { avoidInline } from '../model/Component'; import Components from '../model/Components'; import { ComponentOptions } from '../model/types'; import ComponentsView from './ComponentsView'; +import { ComponentsEvents } from '../types'; type ClbObj = ReturnType; @@ -515,7 +516,9 @@ TComp> { * Recreate the element of the view */ reset() { - const { el } = this; + const view = this; + const { el, model } = view; + view.scriptContainer && model.emitWithEitor(ComponentsEvents.scriptUnmount, { component: model, view, el }); // @ts-ignore this.el = ''; this._ensureElement();