No Interactions when dynamically adding items to flowbite accordion component

62 views Asked by At

So I am using Flowbite and vanilla JavaScript to do a UI.

I am dynamically adding items to an accordion by loading them (via PHP) from a DB. So the PHP script returns an id, section name and section text as JSON.

Now this script then generates the HTML to fit in the accordion container:

document.addEventListener('DOMContentLoaded', function() {
    fetch('src/php/getExecSummary.php')
    .then(response => response.json())
    .then(data => {
        const accordionContainer = document.getElementById('accordionExecSummary');
        
        data.forEach((section, index) => {
            const isOpen = index === 0 ? true : false;
            const sectionElement = `
                <h2 id="accordion-heading-${index}">
                    <button type="button" data-accordion-target="#accordion-collapse-body-${index}" class="flex items-center justify-between w-full p-5 font-medium text-left border-b border-gray-200" aria-expanded="${isOpen}" aria-controls="accordion-collapse-body-${index}">
                        <span>${section.SectionName}</span>
                        <!-- Include accordion icon here -->
                    </button>
                </h2>
                <div id="accordion-collapse-body-${index}" class="${isOpen ? '' : 'hidden'}" aria-labelledby="accordion-heading-${index}">
                    <div class="p-5 border-b border-gray-200">
                        <textarea class="w-full h-32 p-2 border border-gray-300">${section.SectionText}</textarea>
                    </div>
                </div>
            `;            
            accordionContainer.innerHTML += sectionElement;
        });

    })
    .catch(error => console.error('Error loading executive summary sections:', error));
});

So I get the sections and the sections are named from data back from the DB but I cannot interact with the accordion; so clicking the section name doesn't expand/collapse the section. No errors in the console and cant find any refresh() method or anything like that.

Not sure what else I can do?

1

There are 1 answers

0
Wongjn On

As per the documentation:

JavaScript Behaviour

Use the Accordion object from Flowbite to create a collection of vertically collapsing heading and content elements using object parameters, options, methods, and callback functions directly from JavaScript.

As per the example, we need to build the list of items that are the accordions within the accordion group. We can turn the .forEach() into a map like so:

const items = data.map((section, index) => {
  // …
  accordionContainer.insertAdjacentHTML('beforeend', sectionElement);
        
  return {
    id: `accordion-collapse-body-${index}`,
    triggerEl: accordionContainer.querySelector(`#accordion-heading-${index} button`),
    targetEl: document.getElementById(`accordion-collapse-body-${index}`),
    active: isOpen,
  };
});

It is important to replace the .innerHTML assignment with the above .insertAdjaccentHTML() call. This is because .innerHTML assignment causes the entire inner HTML of the container to be reparsed, meaning that previous JavaScript object representations don't actually refer to their elements anymore, whereas .insertAdjaccentHTML() prevents this from happening.

After this, initialize a new Accordion object from Flowbite:

new Accordion(accordionContainer, items);

Full example:

const demoFetch = () => new Promise(resolve => {
  setTimeout(
    () => {
      resolve({
        json: () => Promise.resolve([
          {
            SectionName: 'Foo',
            SectionText: 'Foo content',
          },
          {
            SectionName: 'Bar',
            SectionText: 'Bar content',
          },
        ]),
      });
    },
    500,
  );
});

document.addEventListener('DOMContentLoaded', function() {
  demoFetch('src/php/getExecSummary.php')
    .then(response => response.json())
    .then(data => {
      const accordionContainer = document.getElementById('accordionExecSummary');

      const items = data.map((section, index) => {
        const isOpen = index === 0 ? true : false;
        const sectionElement = `
          <h2 id="accordion-heading-${index}">
            <button type="button" data-accordion-target="#accordion-collapse-body-${index}" class="flex items-center justify-between w-full p-5 font-medium text-left border-b border-gray-200" aria-expanded="${isOpen}" aria-controls="accordion-collapse-body-${index}">
              <span>${section.SectionName}</span>
              <!-- Include accordion icon here -->
            </button>
          </h2>
          <div id="accordion-collapse-body-${index}" class="${isOpen ? '' : 'hidden'}" aria-labelledby="accordion-heading-${index}">
            <div class="p-5 border-b border-gray-200">
              <textarea class="w-full h-32 p-2 border border-gray-300">${section.SectionText}</textarea>
            </div>
          </div>
        `;
        accordionContainer.insertAdjacentHTML('beforeend', sectionElement);
        
        return {
          id: `accordion-collapse-body-${index}`,
          triggerEl: accordionContainer.querySelector(`#accordion-heading-${index} button`),
          targetEl: document.getElementById(`accordion-collapse-body-${index}`),
          active: isOpen,
        };
      });

      new Accordion(accordionContainer, items);
    })
    .catch(error => console.error('Error loading executive summary sections:', error));
});
<link href="https://cdnjs.cloudflare.com/ajax/libs/flowbite/2.3.0/flowbite.min.css" rel="stylesheet" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/flowbite/2.3.0/flowbite.min.js"></script>

<div id="accordionExecSummary"></div>