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

[BUG] Text components don't behave correctly after creating custom components #2831

Closed mustahsanmustafa closed 4 years ago

mustahsanmustafa commented 4 years ago

Hi artf,

After creating custom components, the text component does not behave like before. It removes all the naked nodes after changing some code in import modal. e.g

<div>Insert your text 
        <a data-cke-saved-href="http://abc" href="http://abc">here</a>
</div>

After editing something in import modal it gets change to:

<div>
        <a data-cke-saved-href="http://abc" href="http://abc">here</a>
</div>

As you can see it removed the Naked text node i.e Insert your text. It should be noted that this behavior doesnt happen when there is no custom component added to the editor.

An example of custom component that i created:

export const NavbarMenu = editor => {
    var defaultType = editor.DomComponents.getType("default");
    var defaultModel = defaultType.model;
    editor.DomComponents.addType('menu-nav', {
        isComponent: el => el.tagName === 'menu-nav',
        model: defaultModel.extend({
            // Extend default properties
            defaults: Object.assign({}, defaultModel.prototype.defaults, {
                tagName: 'menu-nav',
                removable: true,
                copyable: false,
                droppable: false,
                resizable: false,
                editable: false,
                traits: [
                    {
                        type: 'text',
                        name: 'menuName',
                        label: 'Menu Name'
                    }
                ],
                attributes: {
                    'menuName': 'Header Nav'
                },
                propagate: ['removable', 'droppable', 'copyable', 'editable'],
                components: `
<div data-gjs-selectable="false" class="container-fluid bg-menu">
  <div class="row" data-gjs-selectable="false">
    <div class="col-lg-12" data-gjs-selectable="false">
      <div class="main-nav-center" data-gjs-selectable="false">
        <nav id="main-nav" class="prvidr-navbar-menu text-center" data-gjs-selectable="false">
          <ul id="main-menu" class="sm pixelstrap sm-horizontal catch-menu" data-gjs-selectable="false">
            <li data-gjs-selectable="false">
              <a data-gjs-selectable="false" href="javascript:void(0)">
                Menu Item 1
              </a>
            </li>
            <li data-gjs-selectable="false">
              <a data-gjs-selectable="false" href="javascript:void(0)">
                Menu Item 2
              </a>
            </li>
            <li data-gjs-selectable="false">
              <a data-gjs-selectable="false" href="javascript:void(0)">
                Menu Item 3
              </a>
            </li>
            <li data-gjs-selectable="false">
              <a data-gjs-selectable="false" href="javascript:void(0)">
                Menu Item 4
              </a>
            </li>
          </ul>
        </nav>
      </div>
    </div>
  </div>
</div>
                `
            }),
            init(){
                var props = this.getAttributes().props;
                if (props) {
                    props = JSON.parse(props);
                    this.addAttributes({'menuName':props['menuName']});
                }
            },
            toHTML: function () {
                let props:any = {};
                props['menuName'] = this.getAttributes().menuName;
                return `<menu-nav data-gjs-type="menu-nav" props='${JSON.stringify(props)}'></menu-nav>`
            }

        })
    });

    editor.BlockManager.add('menu-nav', {
        label: 'Header Menu',
        attributes: {
            class: 'fa fa-bars'
        },
        category: {
            label: "Dynamic Menu"
        },
        content: {
            type: 'menu-nav'
        }
    });
}
mustahsanmustafa commented 4 years ago

UPDATE:

If i just extend my custom components from textnode instead of default, then the issue gets resolved somehow:

export const NavbarMenu = editor => {
    var defaultType = editor.DomComponents.getType("textnode"); //<-- changed to textnode
    var defaultModel = defaultType.model;
    editor.DomComponents.addType('menu-nav', {
        isComponent: el => el.tagName === 'menu-nav',
        model: defaultModel.extend({
                ......

but it will be better if the issue is resolved at default as well. cheers

artf commented 4 years ago

You're mixing the old API with the new one for the custom component definition. With model: defaultModel.extend({ was required an inner static isComponent (you're using one outside, like in the new API) and without it, you're breaking the Component Type Stack

You should just change model: defaultModel.extend({...}) to model: {...} but in your case, I'd also suggest reading carefully https://grapesjs.com/docs/modules/Components.html