Issue #639Opened December 14, 2017by z1lk6 reactions

Re-render Component on Canvas when tagName has changed

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)

artfDecember 14, 20175 reactions

@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, 20171 reactions

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, 20170 reactions

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

Browse Plugin Categories

Jump directly to plugin category pages on the marketplace.