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.36k stars 4.05k forks source link

[Question] How to update attribute/properties dynamically from Component Script? #3239

Closed harsh201 closed 3 years ago

harsh201 commented 3 years ago

We have a use case where we want to insert charts dynamically using Highcharts Editor. I have created a component, which on drag and drop, opens a modal based and based on user's selection, returns a SVG image. I am wondering how can I add it to the model's body. Right now I am using innerHTML to insert but the getHTML does not have the SVG image as part of div. Code for component:

domc.addType('charts-component', {
    model: {
      defaults: {
        tagName: 'div',
        script,
        js: '',
        svg: '',
        attributes: {
          class: 'highcharts',
        },
        'script-props': ['js', 'svg'],
      },
      init() {
        this.on('change:js', this.handleJSChange);
        this.on('change:svg', this.handleSVGChange);
        this.on('change:attributes', this.handleAttrChange);
      },
      handleJSChange() {
        console.log('JS Changed');
        this.addAttributes({ js: this.props().js });
      },
      handleSVGChange() {},
      handleAttrChange() {
        console.log(this.getAttributes());
      },
    },
    view: {},
  });

Also I want to set the JS output of highcharts editor as attribute to the div(or, is there a way, I can add it to component but not render it, but be able to retrieve it by getHTML), is there a way I can dynamically add it? Tried listening to the properties on change event but looks like it is not getting triggered if I change it from inside script.

My script method to call Highcharts:

const script = function (properties) {
    try {
      var modal = highed.ModalEditor(
        function (chart) {
          var html = chart.export.svg();
          var elements = document.getElementsByClassName('highcharts');
          elements[0].innerHTML = html;// I understand I have to do things a tad bit differently if I want to add multiple charts.
          elements[0].setAttribute('js', chart.export.js());
          properties.js = chart.export.js();
        }
      );
      modal.show();
    } catch (err) {
      console.log(err);
    }
  };
harsh201 commented 3 years ago

@artf Any help here will be appreciated. I went through the docs again and nothing stands out, which I could be missing.

bgrand-ch commented 3 years ago

https://github.com/artf/grapesjs/issues/3222#issuecomment-758073000 🤔

harsh201 commented 3 years ago

@bgrand-ch Thanks for reply! I also ended up doing something similar:

          const elements = top.grapesEditor.getComponents().models;
          const currentElement = elements.filter((element) => {
            const attributes = element.getAttributes();
            return attributes.class === 'highcharts' && attributes.chartId === '{[chartId]}';
          });
          currentElement[0].addAttributes({
            chartId: '{[ chartId ]}',
            js: chart.export.js(),
          });
          currentElement[0].components(svg);

But this does not feel correct as I need to expose editor variable in document(I am using react and creating GrapesJS Editor as react component and it is hard to access the editor instance from React context in my plugin). There should be a better way for this.

Also for some reason, even though component is selected, getSelected is returning null. @artf your thoughts?

artf commented 3 years ago

Sorry, I'm kind of confused and not totally sure you understand how the script property works... did you read this part: https://grapesjs.com/docs/modules/Components-js.html#important-caveat

Do you really need to trigger highed.ModalEditor on the final HTML (the one end-users will see)?! I think the modal is to show during the editing of the template (with the usage of GrapesJS), not for end-users (which obviously do not load GrapesJS, they just need the output of Highcharts Editor)...

const script = function (properties) {
 // Here you need to put the code which builds/initialize actual charts, not the Highcharts Editor
 // writing on properties like this `properties.js = chart.export.js()` just doesn't make sense
};

This might help you as a reference: https://github.com/jvas28/grapesjs-echarts

harsh201 commented 3 years ago

@artf Thanks for the response. Yeah, I remember going through this. I understand that we cannot access the variables defined outside scope of script, but I assumed that properties passed might be observable so any changes might reflect on component😓 and hence my wain attempt above.

Actually our use case is a little convoluted. We want to open the HighCharts Editor, where users will be able to create a graph and the output of this editor is SVG which we want to show in GrapesJS. We did take a look at echarts, but looks like they have static list of charts and the data is managed via traits. Our use case is more of this.

harsh201 commented 3 years ago

Sorry for spamming. I was not able to upload usecase video from my work laptop.

https://user-images.githubusercontent.com/2502711/106098322-d39c7b80-615e-11eb-8569-cf76ffc7fcc7.mov

artf commented 3 years ago

You don't need components with JS in this case (in your final code you don't execute any JS), is enough for you something like this:

domc.addType('charts-component', {
    view: {
      events: {
        dblclick: 'onActive', // in order to reopen the modal on double click
      },
      onActive() {
         const { model } = this; // model is the component
         const modal = highed.ModalEditor(({ export }) => {
          // use `content` for static stuff, the editor will render it without transforming the HTML/SVG in components
          model.set({ content: export.svg(), js: export.js()  });
         }).show();
         // I don't know the API but I guess you should recall the previous data somehow
         // eg. .show({ data: model.get('js') })
      }
    },
  });