2

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.

mrpbennett
  • 1,527
  • 15
  • 40
  • are you adding your imports *after* your HTML? – blurfus Dec 01 '21 at 19:07
  • my scripts are at the bottom of my HTML before the closing `

    ` tag.

    – mrpbennett Dec 01 '21 at 19:09
  • I think your are adding event listeners to dynamically created elements - does this answer your question? https://stackoverflow.com/questions/34896106/attach-event-to-dynamic-elements-in-javascript – blurfus Dec 01 '21 at 19:17
  • 1
    That's right they're dynamically created. I have read through the articles, but my code still isn't working. I have added a listener to the parent element (added into my question) – mrpbennett Dec 01 '21 at 19:49
  • are you waiting for the DOM to be loaded completely before adding the listeners? see my answer below and try it to see if it works for you – blurfus Dec 01 '21 at 22:29

2 Answers2

1

You might need to add the listener to the document only after DOMContent has completely loaded. In the case of dynamic elements, you might have to add the onClick listener on the container of the elements

See example below:

// wait for the DOM to be loaded
document.addEventListener('DOMContentLoaded', function() {

  var checkPageButton = document.getElementById('checkPage');

  // add listener only after DOM loaded
  checkPageButton.addEventListener('click', function() {
    alert('clicked');
  }, false);
}, false);
<!doctype html>
<html>

<head>
  <title>Tester</title>
  <script src="popup.js"></script>
</head>

<body>
  <h1>Tester</h1>
  <button id="checkPage">Test this now!</button>
</body>

</html>
blurfus
  • 13,485
  • 8
  • 55
  • 61
  • Thanks for this, i have tried this to no avail. Since you have placed a `document.addEventListener('DOMContentLoaded'` I went to look through my popup.js file which already has it there, so I placed my accordion code into that. ( I have amended above) but still no luck – mrpbennett Dec 02 '21 at 11:30
  • I am not sure if I need to use the chrome API to get this sorted, I have tried `chrome.runtime.onStartup` but that doesn't work either – mrpbennett Dec 02 '21 at 11:48
1

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

mrpbennett
  • 1,527
  • 15
  • 40
  • nicely done - make sure you mark your answer as the correct one for the benefit of other users in the community :) – blurfus Dec 02 '21 at 17:07
  • thanks...can't accept my own answer :( – mrpbennett Dec 02 '21 at 18:54
  • Maybe not yet, you might have to wait a couple of days before the option becomes available: https://meta.stackexchange.com/questions/5234/how-does-accepting-an-answer-work – blurfus Dec 02 '21 at 18:56