Search code examples
javascripthtmlgoogle-chrome-extension

Accordion in a chrome ext using vanilla JS


I am looking to create a simple accordion within my chrome ext to display data. I am using the following JS tutorial but I seem to be struggling to register a click.

I have returned some data using the following:

//background.js

...

 // Looping through object's key value pair to place into divs
  for (const [key, value] of Object.entries(params)) {
    queryParams += `
      <div class="text-sm my-1">
        <span class="font-bold uppercase mr-1">${key}: </span><span class="font-normal font-mono capitalizev c-word-wrap">${value}</span>
      </div>
    `;
  }

return (completeData += `
    <div class="element my-3">

      <div id="click-parent" class="question flex justify-between px-6 py-4 bg-purple-500">
        <span class="text-base text-white font-bold">${pixelType}</span>
        <button id="click-btn">
          <i class="fas fa-plus-circle"></i>
        </button>
      </div>

<!-- OR -->
<div  class="question flex justify-between px-6 py-4 bg-purple-500">
        <span class="text-base text-white font-bold">${pixelType}</span>
        <button id="click-parent">
          <i id="click-btn" class="click-btn fas fa-plus-circle"></i>
        </button>
      </div>
<!-- end of other example -->

      <div class="answer hideText">
        <span id="pixel-url" class="c-word-wrap text-sm font-mono">${pixelUrl}</span>
          <span id="query-params">${queryParams}</span>
      </div>

    </div>
 
  `);
...

I then have my logic in a separate file

//popup.js

document.addEventListener('DOMContentLoaded', function () {
  // get data
  document.getElementById('getCurrentURL').addEventListener('click', function () {
    chrome.runtime.sendMessage({}, function (response) {
      document.getElementById('data').innerHTML = response;
    });
  });

  // accordion code
  const elements = document.querySelectorAll('.element');

  elements.forEach((element) => {
    let btn = element.querySelector('.question button');
    let icon = element.querySelector('.question button i');
    var answer = element.lastElementChild;
    var answers = document.querySelectorAll('.element .answer');

    btn.addEventListener('click', () => {
      alert('clicked ');
      console.log('clicked');

      answers.forEach((ans) => {
        let ansIcon = ans.parentElement.querySelector('button i');
        if (answer !== ans) {
          ans.classList.add('hideText');
          ansIcon.className = 'fas fa-plus-circle';
        }
      });

      answer.classList.toggle('hideText');
      icon.className === 'fas fa-plus-circle'
        ? (icon.className = 'fas fa-minus-circle')
        : (icon.className = 'fas fa-plus-circle');
    });
  });
});

I am loading my scrips in like so...

popup.html

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="../main.css"/>

    <!-- TailWind CSS -->
    <link href="https://unpkg.com/tailwindcss@^2/dist/tailwind.min.css" rel="stylesheet">
  </head>
  <body class="flex flex-col min-h-screen">

    <main class="main flex-grow">

        <h1 class="text-center py-2 text-xl font-bold">HCP365 Debugger</h1>

        <section>
          <div id="data" class=""></div>
        </section>

    </main>

    <footer class="mb-4">
      <div class="flex justify-center">
        <button id="getCurrentURL" type="button" class="text-center text-base py-4 px-14 text-white font-bold drop-shadow bg-purple-500 capitalize outline-none focus:outline-none mr-1 mb-1 ease-linear transition-all duration-150 hover:bg-purple-800">get url</button>
      </div>
    </footer>

    <!-- Scripts -->
    <script src="popup.js"></script>
    <script src="../background.js"></script>
    <!-- Accordion -->
    <script src="../fa.js"></script>
    <script src="../accordion.js"></script> 
  </body>
</html>

If anyone can guide me in where I am going wrong here that would be great. I have also tried replicating the tutorial outside of a chrome ext and it works perfectly

File Structure

.
├── README.md
├── accordion.js
├── background.js
├── fa.js
├── images
│   ├── icon_128.png
│   ├── icon_16.png
│   ├── icon_32.png
│   └── icon_48.png
├── main.css
├── manifest.json
└── popup
    ├── popup.html
    └── popup.js

Update

I have found a repo where someone has created a simple calc. Within their popup.js they had something similar to

document.getElementById('clickBtn').addEventListener('click', () => {
    document.getElementById('textChange').innerHTML = 'hello i was clicked';
  });

I had a div in popup.html as well as the button. This worked fine when I clicked it.

I went on to add it like so...

document.addEventListener('DOMContentLoaded', function () {
  document.getElementById('getCurrentURL').addEventListener('click', function () {
    chrome.runtime.sendMessage({}, function (response) {
      document.getElementById('data').innerHTML = response;
    });
  });

  // new code here --------------------------------
  document.getElementById('btn').addEventListener('click', () => {
    document.getElementById('textChange').innerHTML = 'hello i was clicked';
  });
  // end ---
});

Using the dynamic button...and it didn't work! This is beyond frustrating now...at least we know it's a issue with the dynamic content.


Solution

  • WOOHOO!! Done it!

    I realised something, when I was posting the function inside

    document.addEventListener('DOMContentLoaded', function () { ...
    

    I was getting undefined, then I realsed the .btn only appears on once the content has loaded which is within...

    document.getElementById('getCurrentURL').addEventListener('click', function () {
        chrome.runtime.sendMessage({}, function (response) {
          document.getElementById('data').innerHTML = response;
    

    So I placed it as it is below.

    document.addEventListener('DOMContentLoaded', function () {
      document.getElementById('getCurrentURL').addEventListener('click', function () {
        chrome.runtime.sendMessage({}, function (response) {
          document.getElementById('data').innerHTML = response;
    
          // ----------------------------------------------------------------
          const elements = document.querySelectorAll('.element');
    
          elements.forEach((element) => {
            let btn = element.querySelector('.question button');
            let icon = element.querySelector('.question button i');
            var answer = element.lastElementChild;
            var answers = document.querySelectorAll('.element .answer');
    
            btn.addEventListener('click', () => {
              answers.forEach((ans) => {
                let ansIcon = ans.parentElement.querySelector('button i');
                if (answer !== ans) {
                  ans.classList.add('hideText');
                  ansIcon.className = 'fas fa-plus-circle';
                }
              });
    
              answer.classList.toggle('hideText');
              icon.className === 'fas fa-plus-circle'
                ? (icon.className = 'fas fa-minus-circle')
                : (icon.className = 'fas fa-plus-circle');
            });
          });
          // end ---
        });
      });
    });
    

    and everything works as intended. Although I had to use let btn = element.querySelector('.question button'); instead of getting the button via it's ID as it would only work once