Issue #5379Opened September 12, 2023by Sw33tgt4 reactions

BUG: Class modifications of components are not automatically detected

Question

GrapesJS version

  • I confirm to use the latest version of GrapesJS

What browser are you using?

Chrome Version 116

Reproducible demo link

https://jsfiddle.net/h5b0ndvL/1/

Describe the bug

When a component is re-rendered in the canvas, a new attribute added to the model definition get rendered, but a newly added class doesn't get added when the component is rendered in the canvas. The initialize function of Component.ts add the defaults attributes as well as the component's attribute. this.set('attributes', { ...(result(this, 'defaults').attributes || {}), ...(this.get('attributes') || {}), });

However, when looking at the initClasses() function, I can only see the get('classes') from the component itself, but it doesn't compare with the default list ((result(this, 'defaults').classes) to check if it changed.

How to reproduce the bug?

  1. Create a component with predefined classes
  2. Drag the component on the page
  3. Save the page
  4. Change the classes definition of the component and add a new class to the model
  5. Change the attributes definition of the component and add a new attribute to the model
  6. Refresh the page to load the content again

What is the expected behavior? When a class/attributes for a component type is modified/added, the component must be re-rendered.

What is the current behavior? The newly class is not added to the model class list like it does for the attributes.

If is necessary to execute some code in order to reproduce the bug, paste it here below:

From the JsFiddle link, go to the blocks and drag the "Test Class" block to the canvas. Based on the original definition of the component, the class 'section' and the attributes 'data-type' should be added to the component:

image

I then add a new class and a new attribute to the component's model definition. After running the code again, the new attribute gets added to the component, but the new class doesn't get added to the component:

image

Here's a video of how to reproduce it based on my JsFiddle Link: https://github.com/GrapesJS/grapesjs/assets/144697108/3da7d308-a8f2-4405-8e81-fa1644bb6c9e

Code of Conduct

  • I agree to follow this project's Code of Conduct

Answers (3)

Sw33tgtSeptember 12, 20232 reactions

For the moment, as a workaround, I added this in a editor.on('component:mount', (component) => {}); function until any feedback is received. It seems to be working well. In a case where a class is removed from the definition, the removed class will still be available on the saved component's class list, but I guess there's no workaround around this.

editor.on('component:mount', (component) => {  
    const cls = component.getClasses() || [];
    const clsArr = __isString(cls) ? cls.split(' ') : cls;
    // Default Class
    const dCls = component.defaults.classes || [];
    // Temporary class array for comparison
    const tClsArr = clsArr.map((cl) => __isString(cl) ? cl : cl.name);
    // New class that is not in current tClsArray 
    const nClsArr = dCls.filter((x) => tClsArr.indexOf(x) === -1);

    if(nClsArr.length > 0) {
      // Found a missing class, add it to the component
      component.addClass(nClsArr.toString());
    }

    function __isString(x) {
      return typeof x === 'string';
    }
  });

For now, I have achieved my expected result, but any feedback would be appreciated. Thanks.

Sw33tgtSeptember 20, 20232 reactions

Yeah, this is intentional with classes, with your approach you won't be able to remove defined classes from the component, they will keep being added on component init.

Which is fine for when a class has been forgotten on a component and we want to add it in another version of the plugin on render. For us, we've set these classes to private, so they are not visible to the user and can't be removed manually.

I believe, as a workaround, I could store old classes definition to a new property and compare them before rendering the new class to see which one needs to be removed. I can work on a workaround that will work for us.

Thanks for your reply @artf,

Sw33tgtSeptember 12, 20230 reactions

Something like this in the initClasses() function of Component.ts would add the new classes to the element on render. Let me know what you think :

    const cls = this.get('classes') || attrCls || [];
    
    // changing from const to let
    let clsArr = isString(cls) ? cls.split(' ') : cls;

    /* Compare with default class  */
    const dCls = result(this, 'defaults').classes || [];
    const tClsArr = clsArr.map((cl: any) => isString(cl) ? cl : cl.name);
    const nClsArr = dCls.filter((x: any) => tClsArr.indexOf(x) === -1);
    if(nClsArr.length > 0) {
      clsArr = [].concat(clsArr, nClsArr);
    }

    this.stopListening(...toListen);
    const classes = this.normalizeClasses(clsArr);
    const selectors = new Selectors([]);
    this.set('classes', selectors, opts);
    selectors.add(classes);
    selectors.on('add remove reset', this.__upSymbCls);
    // Clear attributes from classes
    attrCls && classes.length && this.set('attributes', restAttr);
    // @ts-ignore
    this.listenTo(...toListen);
    return this;
  }

Related Questions and Answers

Continue research with similar issue discussions.

Paid Plugins That Match This Issue

Curated by issue keywords and label relevance to help you ship faster.

View all plugins

Loading paid plugin recommendations...

Browse Plugin Categories

Jump directly to plugin category pages on the marketplace.