Issue #3512💬 AnsweredOpened June 2, 2021by Palash-Mandal1 reactions

Child elements not draggable ,droppable highlightable. also the CSS of all child elements not pushed in style section while develop a banner component in as plugin

Quick answerby artf1

In your model component definition, you're using content: config.template but it should be components: config.template

Read full answer below ↓

Question

Hello @artf ,

I am trying to develop a simple banner plugin which will be include a heading , subheading and link. and every element can be draggable ,droppable highlightable, but based on below code some portion of code not working. Please Help

image

only banner class CSS pushed but another all not go in the CSS section image

Code SnippetTEXT
------------ index.js----------

![image](https://user-images.githubusercontent.com/17553816/120481948-46985600-c3ce-11eb-8239-7f1d28242320.png)


import loadComponents from './components';
import loadBlocks from './blocks';

export default (editor, opts = {}) => {
    const options = {... {
            // default options
            bannerBlock: {},
            classBanner: "banner",
            template: `
            <div><h2 class="banner__heading">Banner Heading</h2></div>
            <div> <h3 class="banner__subheading">Banner Sub heading</h3></div>
            <div> <a class="banner__link" href="#">Get Started</a></div>
            `,
            style: `
          .banner {
            background-color: #c9efff;
            background-repeat: no-repeat;
            background-position: center center;
            background-size: cover;
            position: relative;
            padding: 80px 20px;
            border-radius:0;
            display: flex;
            align-items: center;
            justify-content: center;
            flex-direction: column;
            min-height: 370px;
          }

          .banner__heading {
            font-weight: 700;
            color: #333;
            font-size: 40px;
            margin-bottom: 16px;
            text-align: center;
            line-height: 1.2;
          }
          .banner__subheading {
            font-size: 26px;
            text-align: center;
            margin-top: 16px;
            font-weight: 500;
            color: #333;
          }
          .banner__link {
            display: block;
            padding: 10px 20px;
            background: none;
            margin: 5px;
            font-size: 14px;
            color: #fff !important;
            background-color: #005A9E;
            transition: all 0.2s ease-in;
            border-radius: 4px;
            font-weight: 500;
            border: none !important;
            text-align: center;
            min-width: 160px;
            letter-spacing: 1px;
          }
        `,
        },
        ...opts
    };

    // Add components
    loadComponents(editor, options);
    // Add blocks
    loadBlocks(editor, options);
};

------------- Block JS-------------
![image](https://user-images.githubusercontent.com/17553816/120482131-70ea1380-c3ce-11eb-9624-700c200e45dc.png)

export default (editor, config = {}) => {
    const bm = editor.BlockManager;
    const bannerBlock = config.bannerBlock;
    const style = config.style;
    const type = "bannerblock";
    const content = `<div data-gjs-type="${type}"></div>
  ${style ? `<style>${style}</style>` : ""}`;

  bannerBlock &&
  bm.add(type, {
    label: "Banner",
    category: 'Components',
    attributes: { class: "fa fa-list" },
    activate: 1,
    select: 1,
    removable: true, // Can't remove it
    draggable: true, // Can't move it
    copyable: true, // Disable copy/past
    content,
    ...bannerBlock,
  });
}

----------- Component JS------------
![image](https://user-images.githubusercontent.com/17553816/120482266-9414c300-c3ce-11eb-9132-2dad65d2d392.png)
export default (editor, config = {}) => {
    const domc = editor.DomComponents;
    const type = 'bannerblock';
    const classbanner = config.classBanner;
    domc.addType(type, {
        model: {
            defaults: {
                attributes: { class: classbanner },
                content: config.template,
                draggable: true,
                droppable: true,
                copyable: true,
                removable: true,
                selectable: true,
            },
        },
        view: {

        },
    });
};

Answers (3)

artfJuly 6, 2021

In your model component definition, you're using content: config.template but it should be components: config.template

Palash-MandalJune 15, 2021

Hello @artf ,

I have made the solution but there are some issue arise the component. the child component h2,h3 and link pushed for all other component. If I drop one column component those child element also pushed inside there. How Do I restrict the conflict. Below here is the codepen example where I made a demo. https://codepen.io/coderdesigners/full/XWMoNbZ

If I drop one column the below com up. which is not expected. it should as per default. image

When drop the banner component. image

Please Please help this. I know I have some silly mistake which is I unable to under stand. I am sorry for bother you.

Thanks in Advance.

And here is my plugin details

image export default { // Object to extend the default banner block, eg. { label: 'Banner', attributes: { ... } } // Pass a falsy value to avoid adding the block bannerBlock: {},

// Object to extend the default Banner properties, eg. `{ name: 'My Banner', droppable: false, ... }`
bannerCreateProps: {},

// Object to extend the default heading properties
bannerHeadingProps: {},

// Object to extend the default Sub heading properties
bannerSubHeadingProps: {},

// Object to extend the default button properties
bannerButtonProps: {},


// Tabs component id
typeBanner: 'banner',

// TabContainer component id
typeBannerHeading: 'Banner-Heading',

// Tab component id
typeBannerSubHeading: 'Banner-SubHeading',

// TabContent component id
typeBannerButton: 'Banner-Button',

// Default class to use on Banner
classBannerContainer: 'zslbanner',

// Default class to use on Banner
classHeading: 'zslbanner__heading',
Code SnippetTEXT
// Default class to use on Banner
    classSubHeading: 'zslbanner__subheading',

    // Default class to use on Banner
    classLink: 'zslbanner__link',

    // Default style for tabs
    style: (config) => `
        .${config.classBannerContainer} {
        font-family: 'Montserrat', Arial, sans-serif;
        background-color: #c9efff;
        background-repeat: no-repeat;
        background-position: center center;
        background-size: cover;
        position: relative;
        padding: 80px 20px;
        border-radius:0;
        display: flex;
        align-items: center;
        justify-content: center;
        flex-direction: column;
        min-height: 370px;
      }
      .${config.classHeading} {
        font-weight: 700;
        color: #333;
        font-size: 40px;
        margin:0;
        margin-bottom: 16px;
        text-align: center;
        line-height: 1.2;
      }
      .${config.classSubHeading} {
        font-size: 26px;
        text-align: center;
        margin:0;
        margin-bottom: 16px;
        font-weight: 500;
        color: #333;
      }
      .${config.classLink} {
        display: block;
        padding: 10px 20px;
        background: none;
        margin: 20px 5px 5px 0 5px;
        font-size: 14px;
        color: #fff !important;
        background-color: #005A9E;
        transition: all 0.2s ease-in;
        border-radius: 4px;
        font-weight: 500;
        border: none !important;
        text-align: center;
        min-width: 160px;
        letter-spacing: 1px;
      }
      .${config.classLink}:hover {
        background-color: #037ede;  
       }
 `
}
![image](https://user-images.githubusercontent.com/17553816/122001932-b6471180-cdce-11eb-822f-f81f75af60ef.png)

![image](https://user-images.githubusercontent.com/17553816/122001962-c3640080-cdce-11eb-877a-56108e2adbba.png)

![image](https://user-images.githubusercontent.com/17553816/122001992-cfe85900-cdce-11eb-8b1b-6c3fe60ee60f.png)

**Subheading.js**
export const role = 'SubHeading';

export default (dc, config) => {

    dc.addType(config.typeBannerSubHeading, {
        model: {
            isComponent: function(t) {
                return ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'p'].indexOf(t.tagName) >= 0
            },
            defaults: {
                name: 'Sub Heading',
                draggable: true,
                copyable: true,
                removable: true,
                highlightable: true,
                editable: true,
                attributes: { role },
                classes: config.classSubHeading,
                tagName: 'h3',
                content: 'Sub Heading',
                traits: [
                    'id',
                    'title',
                    {
                        type: "select",
                        options: [{
                            value: "h1",
                            name: "Heading 1"
                        }, {
                            value: "h2",
                            name: "Heading 2"
                        }, {
                            value: "h3",
                            name: "Heading 3"
                        }, {
                            value: "h4",
                            name: "Heading 4"
                        }, {
                            value: "h5",
                            name: "Heading 5"
                        }, {
                            value: "h6",
                            name: "Heading 6"
                        }, {
                            value: "p",
                            name: "Paragraph"
                        }],
                        label: "Heading Or paragraph",
                        name: "tagName",
                        changeProp: 1
                    }
                ],
                ...config.bannerSubHeadingProps
            },
        },
        view: {
            events: {
                dblclick: 'onActive',
                focusout: 'onDisable',
            },
            onActive() {
                this.el.contentEditable = true;
            },
            onDisable() {
                const { el, model } = this;
                // el.contentEditable = false;
                model.set('content', el.innerHTML)
            },
        }
    });
}
![image](https://user-images.githubusercontent.com/17553816/122002066-e55d8300-cdce-11eb-8216-e91659cfaaad.png)

![image](https://user-images.githubusercontent.com/17553816/122002133-fad2ad00-cdce-11eb-9aab-d3f02d37c78b.png)

**Heading.js**

export const role = 'Heading';

export default (dc, config) => {

    dc.addType(config.typeBannerHeading, {
        model: {
            isComponent: function(t) {
                return ['H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'p'].indexOf(t.tagName) >= 0
            },
            defaults: {
                name: 'Heading',
                draggable: true,
                copyable: true,
                removable: true,
                highlightable: true,
                editable: true,
                attributes: { role },
                classes: config.classHeading,
                tagName: 'h2',
                components: 'Heading',
                traits: [
                    'id',
                    'title',
                    {
                        type: "select",
                        options: [{
                            value: "h1",
                            name: "Heading 1"
                        }, {
                            value: "h2",
                            name: "Heading 2"
                        }, {
                            value: "h3",
                            name: "Heading 3"
                        }, {
                            value: "h4",
                            name: "Heading 4"
                        }, {
                            value: "h5",
                            name: "Heading 5"
                        }, {
                            value: "h6",
                            name: "Heading 6"
                        }, {
                            value: "p",
                            name: "Paragraph"
                        }],
                        label: "Heading Or paragraph",
                        name: "tagName",
                        changeProp: 1
                    }
                ],
                ...config.bannerHeadingProps
            },
        },
        view: {
            events: {
                dblclick: 'onActive',
                focusout: 'onDisable',
            },
            onActive() {
                this.el.contentEditable = true;
            },
            onDisable() {
                const { el, model } = this;
                el.contentEditable = false;
                model.set('content', el.innerHTML)
            },
        }
    });
}

**Button.js**
export default (dc, config) => {

    dc.addType(config.typeBannerButton, {
        model: {
            extend: 'link',
            defaults: {
                editable: true,
                name: 'Button',
                draggable: true,
                copyable: true,
                removable: true,
                highlightable: true,
                attributes: {
                    href: '#'
                },
                classes: config.classLink,
                content: 'Get Started',
                tagName: 'a',
                traits: [
                    'id',
                    'title',
                    'href',
                    'target',
                ],
                ...config.bannerButtonProps
            },
        },
        view: {
            events: {
                dblclick: 'onActive',
                focusout: 'onDisable',
            },
            onActive() {
                this.el.contentEditable = true;
            },
            onDisable() {
                const { el, model } = this;
                el.contentEditable = false;
                model.set('content', el.innerHTML)
            },
        }
    });
}


**BannerCreate.js**
export default (dc, {
    typeBanner,
    typeBannerHeading,
    typeBannerSubHeading,
    typeBannerButton,
    style,
    ...config
}) => {
    const type = config.typeBanner;
    dc.addType(type, {
        model: {
            defaults: {
                name: 'Banner',
                classes: config.classBannerContainer,
                components: [
                    { type: typeBannerHeading },
                    { type: typeBannerSubHeading },
                    { type: typeBannerButton },
                    style && `<style>${style(config)}</style>`
                ],
                ...config.bannerCreateProps
            },
        },
    });
}

Please Help.
ClaudeCodeMay 17, 2026

Thanks for reporting this, @Palash-Mandal.

Great question about Child elements not draggable ,droppable highlightable. also the CSS of all child elements not pushed in style section while develop a banner component in as plugin. The recommended approach with ProseMirror is to use the event-driven API.

Start here:

  1. Check the GrapesJS documentation for your specific module
  2. Look for the on() event listener method
  3. Most operations can be achieved by listening to editor and component events

Common patterns:

// Listen for changes
editor.on('change', () => console.log('something changed'));

// Component lifecycle
editor.on('component:mount', (c) => console.log('component ready', c));
editor.on('component:update', (c) => console.log('component updated', c));

If you're still stuck:

  • Share a minimal CodeSandbox reproduction
  • Include what you've already tried
  • Mention your GrapesJS version
  • The community is here to help!

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.