grapesjs

Creating accordion or collapsible block in grapesjs


I want to create an accordion in grapesjs like the below image.

enter image description here

enter image description here

I have tried the below code but its not working.

 editor.BlockManager.add('collapse-block', {
  label: 'Collapse',
  content: `
    <div id="accordion">
      <div class="card">
        <div class="card-header" id="headingOne">
          <h5 class="mb-0">
            <button class="btn btn-link" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
              Collapsible Group Item #1
            </button>
          </h5>
        </div>
        <div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordion">
          <div class="card-body">
            Any content can go here, including widgets like forms or containers.
          </div>
        </div>
      </div>
    </div>
  `,
  category: 'Components',
  attributes: { class: 'gjs-block-section' },
});

editor.DomComponents.addType('collapse', {
  isComponent: el => el.classList && el.classList.contains('collapse'),
  model: {
    defaults: {
      tagName: 'div',
      classes: ['collapse'],
      traits: [
        {
          type: 'text',
          label: 'Target',
          name: 'data-target',
          changeProp: 1
        },
        {
          type: 'checkbox',
          label: 'Expanded',
          name: 'aria-expanded',
          changeProp: 1
        }
      ],
      script: function() {
        var btn = this.closest('.card').querySelector('button');
        var target = this.getAttribute('data-target');
        btn.setAttribute('data-target', target);
        btn.addEventListener('click', function() {
          $(target).collapse('toggle');
        });
      }
    }
  }
});

It creates the panel but doesn't work for open and close. How to replicate this feature in grapesjs. This can be a group of sections or an individual collapsible. Also any image or text or any media object should also be available to be added in the content section.


Solution

  • First you will have to create a custom block with collapsible

     // Define a new component type for the collapsible
    gjs.Components.addType('collapsible', {
      model: {
        defaults: {
          tagName: 'div',
          classes: ['collapsible-block'],
          components: [
             {
              type: 'div',
              tagName: 'div',
              classes: ['collapsible-header'],
              editable: true,  // Allows editing the div text
              components: [
                {
                  type: 'text',
                  tagName: 'div',
                  content: 'Open Collapsible',
                  editable: true,  // Allows editing the inner div text
                  style: {
                    color: '#fff',  // White text color
                    flex: '1',  // Ensures text div takes up full space within the header
                    'text-align': 'center'
                  }
                }
              ]
            },
            {
              type: 'div',
              classes: ['collapsible-content'],
              components: [
                {
                  type: 'text',
                  tagName: 'p',
                  content: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.',
                },
              ],
              droppable: true,  // Allows adding more blocks inside the content
            }
          ],
          // Define traits for div text to ensure it stays editable
          traits: [
            {
              type: 'text',
              label: 'Header Text',
              name: 'headerText',
              changeProp: 1
            }
          ],
        }
      }
    });
    
    // Add the custom block to the BlockManager
    gjs.BlockManager.add('bootstrap-collapsible', {
      label: 'Collapsible',
      category: 'Bootstrap Components',
      activate: true,
      content: { type: 'collapsible' }
    });
    

    Then you need to inject css and js files

    // Ensure the editor is fully loaded before injecting stylesheets
    gjs.on('load', function() {
        injectStylesheets(gjs, [
            '{% static "libs/css/bootstrap-4-5-3.min.css" %}',
            '{% static "libs/css/jquery-ui.css" %}',
            '{% static "libs/css/bootstrap.min.css" %}',
            '{% static "libs/fontawesome/css/all.css" %}',
            '{% static "libs/fontawesome4/css/font-awesome.css" %}',
            '{% static "css/style.css" %}?v={{ DRAVOKA_APP_VERSION }}',
        ]);
    
        injectScripts(gjs, [
            '{% static "libs/js/jquery.min.js" %}',
            '{% static "libs/js/jquery-3.5.1.min.js" %}',
            '{% static "libs/js/bootstrap.bundle.min.js" %}',
            '{% static "libs/js/bootstrap.min.js" %}',
            '{% static "js/script.js" %}',
        ]);
    });
    
    // Function to inject multiple stylesheets into the canvas
    function injectStylesheets(gjs, urls) {
        const canvasDoc = gjs.Canvas.getDocument();
        const head = canvasDoc.head;
    
        urls.forEach(function(url) {
            const link = canvasDoc.createElement('link');
            link.rel = 'stylesheet';
            link.href = url;
            head.appendChild(link);
        });
    }
    
    // Function to inject multiple JavaScript files into the canvas
    function injectScripts(gjs, urls) {
        const canvasDoc = gjs.Canvas.getDocument();
        const head = canvasDoc.head;
    
        urls.forEach(function(url) {
            const script = canvasDoc.createElement('script');
            script.src = url;
            head.appendChild(script);
        });
    }
    
    const id = "{{ page.id }}";
    if(id){
        const html = `{{ page.temp_html|safe }}`;
        const css = `{{ page.temp_css|safe }}`;
        if (html !== 'None'){
            gjs.setComponents(html);
            gjs.setStyle(css);
        }
    }
    

    Then in script.js you will have to handle collapsible content

     $(document).on("click", ".collapsible-header", function(event){
        // Toggle the 'active' class on the clicked element
        $(this).toggleClass('active');
        // Find the next sibling element (the content) and toggle its display
        const $content = $(this).next();
        if ($content.css('display') === 'block') {
            $content.css('display', 'none');
        } else {
            $content.css('display', 'block');
        }
    });