Home > Blockchain >  Toggle a button with the same classes name on different elements
Toggle a button with the same classes name on different elements

Time:07-14

I'm building a website which has multiple cards with the same class name, each card has an edit button, but when I toggle any random card, the first card is always affected, not the card I'm actually clicking on. I think it is something to with how I'm selecting the elements.

Vanilla JavaScript only.

const optionsButton = Array.from(document.querySelectorAll(".dropdown-toggle"));

const dropdownContent = document.querySelector(".dropdown-content");

optionsButton.forEach((button) => {
  button.addEventListener("click", () => {
    dropdownContent.classList.toggle("show-dropdown");
  });
});
ul {
  display: flex;
  list-style: none;
}

li {
  margin: 0 2em;
}

.dropdown-content {
  display: none;
  flex-direction: column;
}

.show-dropdown {
  display: flex;
}

.dropdown-content a {
  padding: 10px 0
}
<ul>
  <li>
    <h1>Title</h1>
    <p>blah blah blah</p>
    <button >Options
        </button>
    <div >
      <a href="#">Edit</a>
      <a href="#">Delete</a>
    </div>
  </li>
  <li>
    <h1>Title</h1>
    <p>blah blah blah</p>
    <button >Options
        </button>
    <div >
      <a href="#">Edit</a>
      <a href="#">Delete</a>
    </div>
  </li>
  <li>
    <h1>Title</h1>
    <p>blah blah blah</p>
    <button >Options
        </button>
    <div >
      <a href="#">Edit</a>
      <a href="#">Delete</a>
    </div>
  </li>
</ul>

CodePudding user response:

Since the dropdownContent just renders the first element with that class, you would need to get the array of all the elements with that class name, then just run that forEach with the index of that class.

const optionsButton = Array.from(document.querySelectorAll(".dropdown-toggle"));

const dropdownContent = document.querySelectorAll(".dropdown-content");/* querySelector -> querySelectorAll */

optionsButton.forEach((button, index) => {/* add index */
  button.addEventListener("click", () => {
    dropdownContent[index].classList.toggle("show-dropdown");/* index the array */
  });
});
ul {
  display: flex;
  list-style: none;
}

li {
  margin: 0 2em;
}

.dropdown-content {
  display: none;
  flex-direction: column;
}

.show-dropdown {
  display: flex;
}

.dropdown-content a {
  padding: 10px 0
}
<ul>
  <li>
    <h1>Title</h1>
    <p>blah blah blah</p>
    <button >Options
        </button>
    <div >
      <a href="#">Edit</a>
      <a href="#">Delete</a>
    </div>
  </li>
  <li>
    <h1>Title</h1>
    <p>blah blah blah</p>
    <button >Options
        </button>
    <div >
      <a href="#">Edit</a>
      <a href="#">Delete</a>
    </div>
  </li>
  <li>
    <h1>Title</h1>
    <p>blah blah blah</p>
    <button >Options
        </button>
    <div >
      <a href="#">Edit</a>
      <a href="#">Delete</a>
    </div>
  </li>
</ul>

CodePudding user response:

I think this is because your dropdownContent just finds the first dropdown-content, not the one corresponding to your clicked optionsButton.

To fix this, I wrote up this solution:

optionsButton.forEach((button) => {
  let dropdownContent = button.parentElement.querySelector(".dropdown-content");
  button.addEventListener("click", () => {
    dropdownContent.classList.toggle("show-dropdown");
  });
});

This finds the buttons parent, then searches for the parents dropdown-content

CodePudding user response:

This line:

const dropdownContent = document.querySelector(".dropdown-content");

finds the first element that matches the selector, and all your event handlers were toggling the class on that element since it was the first one found.

Also (fyi), all modern browsers support forEach() on the node list returned from .querySelectorAll(), so there's no need for Array.from() on that.

Instead, use "event delegation", which is much simpler than what you were doing and because only one event handler is set up and there is no loop, it's more performant and will scale as you add more cards without any additional code. See comments inline below:

// Set up a single event handler at a common ancestor of all
// the elements you wish to handle clicks of
document.addEventListener("click", (event) => {
  // Check to see if the event originated at an element we care about
  if(event.target.classList.contains("dropdown-toggle")){
    // Access the next sibling element and toggle its class
    event.target.nextElementSibling.classList.toggle("show-dropdown");
  }
});
ul {
  display: flex;
  list-style: none;
}

li {
  margin: 0 2em;
}

.dropdown-content {
  display: none;
  flex-direction: column;
}

.show-dropdown {
  display: flex;
}

.dropdown-content a {
  padding: 10px 0
}
<ul>
  <li>
    <h1>Title</h1>
    <p>blah blah blah</p>
    <button >Options</button>
    <div >
      <a href="#">Edit</a>
      <a href="#">Delete</a>
    </div>
  </li>
  <li>
    <h1>Title</h1>
    <p>blah blah blah</p>
    <button >Options</button>
    <div >
      <a href="#">Edit</a>
      <a href="#">Delete</a>
    </div>
  </li>
  <li>
    <h1>Title</h1>
    <p>blah blah blah</p>
    <button >Options</button>
    <div >
      <a href="#">Edit</a>
      <a href="#">Delete</a>
    </div>
  </li>
</ul>

  • Related