GrapesJS / grapesjs

Free and Open source Web Builder Framework. Next generation tool for building templates without coding
https://grapesjs.com
BSD 3-Clause "New" or "Revised" License
22.37k stars 4.05k forks source link

[FEATURE REQUEST] Promise returning plugins #851

Closed maxtacco closed 6 years ago

maxtacco commented 6 years ago

Hi @artf, I have a case where a custom plugin needs to check user permissions before doing any kind of initialization. The method that does the check returns a Promise, so I have to wait for it to resolve before doing anything. Unfortunately, this creates a raise condition with editor rendering and UI gets into some weird state. In order to solve this issue I have to set 'editor.autorender' to false and then render editor manually once my plugin is initialized. This is fine for now, but I'm not quite sure how I would handle this case if I had multiple custom plugins depending on some async logic.

So, it would be really nice if GrapesJS could check if any of the registered plugins return Promises and only render the editor when all of them are resolved.

Thank you for you awesome project.

maxtacco commented 6 years ago

Also, this may require GrapesJS' init method to return a Promise instead of the editor, but this can be limited only to cases when there are any plugins that return Promises.

artf commented 6 years ago

I have to set 'editor.autorender' to false and then render editor manually once my plugin is initialized. This is fine for now, but I'm not quite sure how I would handle this case if I had multiple custom plugins depending on some async logic

Just use a variable as a flag which counts your custom execution logic and once it's finished you're ready to render it. IMHO, the solution with Promises it's absolutely overwhelmed for such a thing

maxtacco commented 6 years ago

Yes, I can probably make it work but it is going to be kinda hacky.

nashton109 commented 5 years ago

I had a similar requirement. If it helps others, my solution in the end was to create a plugin which then loads my other plugins;

The editor config would just reference the one plugin and the plugin options for that plugin would contain the plugins and options for the others to load.

The plugin code;

export default grapesjs.plugins.add("async-plugins", async (editor, opts) => {
    /**
     * opts = {
     *   plugins: [ ... ]
     *   pluginsOpts: { ... },
     *   render: true | false, // If not set this will default to true.
     *   onFinished: async (editor) => {
     *     // When the async plugin has finished.
     *   },
     * };
     *
     * If using the 'render' here, make sure to set 'autorender' to false in the editor config.
     */
    // Load the specified plugins.
    for (let i = 0, len = opts.plugins.length; i < len; i++) {
        const pluginId = opts.plugins[i];
        if (!opts.pluginsOpts) {
            opts.pluginsOpts = {};
        }
        // Attempt to get the plugin if it has been registered.
        let plugin = grapesjs.plugins.get(pluginId);
        if (!plugin) {
            const wplg = window[pluginId];
            plugin = wplg && wplg.default ? wplg.default : wplg;
        }
        // Attempt to execute the plugin using 'await'.
        if (plugin) {
            await plugin(editor, opts.pluginsOpts[pluginId] || {});
        }
        else if (pluginId instanceof Function) {
            await pluginId(editor, {});
        }
        else {
            console.warn(`Failed to load plugin ${pluginId}.`);
        }
    }
    // Render the editor if this plugin has been configured to do so.
    if (opts.render === undefined || opts.render === true) {
        editor.render();
    }
    // If there is an 'onFinished' callback, call it.
    if (opts.onFinished) {
        await opts.onFinished(editor);
    }
});

The editor init code;

grapesjs.init({
  ...
  autorender: false,
  plugins: [ 'async-plugins' ],
  pluginsOpts: {
    'async-plugins': {
      plugins: [ ... ],
      pluginsOpts: { ... },
      render: true,
      onFinished: async (editor) => {
        console.log("FINISHED");
      },
    },
  },
  ...
});