Issue #639✓ SolvedOpened December 14, 2017by z1lk6 reactions

Re-render Component on Canvas when tagName has changed

快速解答by artf5

@z1lk I already added such a thing for the next release

Read full answer below ↓

Question

I'm trying to build a basic Header component that lets you select H1 to H6 with a trait. But when an option is selected, the canvas doesn't update. The change is visible in the code view, and if I move the element in the canvas with the drag tool, the tag then changes. I have been reading the API docs as well as the source, but I can't make the component automatically re-render. I suspect that Grapes is not listening for a change of the component tag name. What is the appropriate way to force a re-render in this case?

var comps = editor.DomComponents;
var blocks = editor.BlockManager;
var textType = comps.getType('text');
var textModel = textType.model;
var textView = textType.view;

comps.addType('header', {
  model: textModel.extend({
    defaults: Object.assign({}, textModel.prototype.defaults, {
      'custom-name': 'Header',
      tagName: 'h1',
      traits: [
        {
          type: 'select',
          options: [
            {value: 'h1', name: 'One (largest)'},
            {value: 'h2', name: 'Two '},
            {value: 'h3', name: 'Three '},
            {value: 'h4', name: 'Four '},
            {value: 'h5', name: 'Five '},
            {value: 'h6', name: 'Six (smallest)'},
          ],
          label: 'Size',
          name: 'header-size',
          changeProp: 1
        }
      ]
    }),

    init() {
      this.listenTo(this, 'change:header-size', this.changeTagName);
    },

    changeTagName() {
      // view.tagName is a fn that returns model.tagName
      this.set('tagName', this.get('header-size'));
    }

  }, {
    isComponent: function(el) {
      if(el && ['H1','H2','H3','H4','H5','H6'].includes(el.tagName)) {
        return {type: 'header'};
      }
    }
  }),
  view: textView
});

blocks.add('header', {
  label: 'Header',
  category: 'Basic',
  attributes: {class:'fa fa-header'},
  content: {
    type:'header',
    content:'Insert your header text here',
    activeOnRender: 1
  }
});

Answers (3)

👍 Most helpfulartfDecember 14, 2017

@z1lk I already added such a thing for the next release

// Model
// inside init
this.listenTo(this, 'change:tagName', this.tagUpdated);
// ...
tagUpdated() {
    const coll = this.collection;
    const at = coll.indexOf(this);
    coll.remove(this);
    coll.add(this, { at });
},
z1lkDecember 20, 2017

I'm not sure why el would be a string, but see the commit that closed the issue: https://github.com/artf/grapesjs/commit/e450cb98855d16ad819f1214350825a50e45e910

If you're using the latest Grapes version, the Component listens for a change of tagName and does the node replacement itself. So you should be able to remove your init and tagUpdated functions. The trait will update tagName and the Component will do the rest.

Edit:

Another thing I thought of is: sometimes the object passed to isComponent doesn't have the method that I call on it, and there will be an error thrown in that case. You could do a safety check first:

if(el && el.tagName && ['H1','H2','H3','H4','H5','H6'].includes(el.tagName)) {
  ...
artfDecember 14, 2017

Yeah this is because you can't change the tag name of an existing DOM element. Probably the solution would be, on tagName change, remove and replace the node (at the same position)

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...

Free option

Check the open-source GrapesJS plugins on GitHub or run a quick search in our free catalog.

Browse free plugins →
Premium option

Premium plugins ship with support, regular updates, and production-ready features — save days of integration work.

Browse premium plugins →

Related tutorials

In-depth guides on the same topic.

All tutorials →

Browse Plugin Categories

Jump directly to plugin category pages on the marketplace.