Skip to content

How do I get a Javascript button on second click to change class on self and *sometimes* on other buttons containing same class

I am attempting, with my very limited Javascript knowledge, to write vanilla Javascript to some buttons to achieve the following:

a) Click any button, e.g. button1 and its content is displayed b) Click the same button and its content is hidden c) Click a button again (e.g. button1) and then click another button (e.g. button2) and button2 content is displayed whilst also closing the previously clicked button (button1).

However, I can only achieve two out of those three steps:

For example, I have three buttons:

Scenario 1

In Scenario 1 I can achieve a) but not b) or c)

I can click button1 to show its content and then click button2 to open its content and hide the content of button1. What I can’t do is then click button2 again to hide its content so the content of all buttons is hidden again.

As per this snippet:

let Buttons = document.querySelectorAll(".button");
for (let button of Buttons) {
    button.addEventListener('click', (e) => {
    const et = e.target;
    const active = document.querySelector(".clicked");
    if (active) {
    active.classList.remove("clicked");
    active.classList.add("not-clicked");
    active.nextElementSibling.classList.toggle("hidden");
    }
    et.classList.toggle("clicked");
    et.classList.toggle("not-clicked");
    et.nextElementSibling.classList.toggle("hidden");
 });
}
.hidden {
 display: none;
 ]
            <div>
            <button class="button not-clicked" type="button" >Button 1</button>
            <p class="level-1 hidden" data-number="2">Content of button 1</p>
        </div>
        <div>
            <button class="button not-clicked" type="button" >Button 2</button>
            <p class="level-1 hidden">Content of button 2</p>
        </div>
        <div>
            <button class="button not-clicked" type="button" >Button 3</button>
            <p class="level-1 hidden">Content of button 3</p>
        </div>

Scenario 2 In Scenario 2 I can achieve a) and b) but not c)

In the following snippet, I can get each button to show and hide its own content when clicking it once to show and clicking the same button again to hide, but when clicking another button, when the first is still open, the first one doesn’t hide:

    document.addEventListener("click", function(e) {
    const tgt = e.target;
    if (tgt.type==="button") {
      const level = tgt.className.replace(/[^d]/g,"");
      [...document.querySelectorAll('ul.level-'+level)].forEach(ul => ul.classList.add("hidden"));
      const show = tgt.classList.contains("button")
      tgt.nextElementSibling.classList.toggle("hidden",!show); 
      tgt.classList.toggle("button",!show);
    }
  })
.hidden {
 display: none;
 }
        <div>
            <button class="button not-clicked" type="button" >Button 1</button>
            <p class="level-1 hidden" data-number="2">Content of button 1</p>
        </div>
        <div>
            <button class="button not-clicked" type="button" >Button 2</button>
            <p class="level-1 hidden">Content of button 2</p>
        </div>
        <div>
            <button class="button not-clicked" type="button" >Button 3</button>
            <p class="level-1 hidden">Content of button 3</p>
        </div>

Ideal Scenario 3:

As explained at the beginning, what I’m trying to achieve is a combination of both Scenarios 1 and 2:

a) Click any button, e.g. button1 and its content is displayed b) Click the same button again and its content is hidden c) Click a button again (e.g. button1) and then click another button (e.g. button2) and button2 content is displayed whilst also closing the previously clicked button (button1).

I’ve tried fiddling my JS like below, but I think I’m either getting a double-negative or a double-positive – possibly ‘clicked’ is always true? – or my use of != is invalid (or both).

let Buttons = document.querySelectorAll(".button");
for (let button of Buttons) {
button.addEventListener('click', (e) => {
const x = e.target;
const y = document.querySelector(".clicked"); // this could include x
const z = (y != x); // I'm looking for "include y but not if it is x"
const q = (x = y); // If the target x already contains "clicked"
if (z) {
z.classList.remove("clicked");
}
if (q) {
q.classList.toggle("clicked"); // if x contains 'clicked', this will remove 'clicked'; if x does not contain 'clicked' move onto the default next line.
}
x.classList.toggle("clicked");
})

}

Any help, gratefully received, but no jQuery, thanks.

Answer

You can do something like this:

const buttons = Array.from(document.querySelectorAll(".button"));
buttons.forEach((button) => {
  button.addEventListener('click', () => {
    // Set to not clicked and hide the content of all the other buttons
    buttons.filter(b => b != button).forEach(b => {
      b.classList.remove('clicked');
      b.classList.add('not-clicked');
      b.nextElementSibling.classList.add('hidden')
    });
    // Set to clicked/not-clicked and show/hide the content of the current button
    button.nextElementSibling.classList.toggle('hidden');
    if (button.nextElementSibling.classList.contains('hidden')) {
      button.classList.remove('clicked');
      button.classList.add('not-clicked');
    } else {
      button.classList.add('clicked');
      button.classList.remove('not-clicked');
    }
  });
});
.hidden {
  display: none;
}
.clicked {
  color: red;
}
<div>
  <button class="button not-clicked" type="button">Button 1</button>
  <p class="level-1 hidden" data-number="2">Content of button 1</p>
</div>
<div>
  <button class="button not-clicked" type="button">Button 2</button>
  <p class="level-1 hidden">Content of button 2</p>
</div>
<div>
  <button class="button not-clicked" type="button">Button 3</button>
  <p class="level-1 hidden">Content of button 3</p>
</div>

What this does, when a button is clicked, buttons.filter(b => b != button) creates an array of all the other buttons, sets them to not-clicked and hides their content. Then, the current button is set to clicked and its content’s hidden class is toggled.