Home > Back-end >  Toggles class only on the first loop element
Toggles class only on the first loop element

Time:07-05

Why this code toggles the class only on the first rendered forEach element? I want to toggle classes on the card on which i press the button but the toggle happens only on the first element.

const generateCards = (data) => {
  data.results.forEach((data) => {
    const userCard = `
    <div >
    <div >
    <div  id="myDIV">
    <p><b>Name</b>: ${data.name.first} ${data.name.last}</p>
</div>

      <img
        src="${data.picture.medium}"
        alt=""
      />
      <p><b>Name</b>: ${data.name.first} ${data.name.last}</p>
      <p><b>Country</b>: ${data.location.country}</p>
      <p><b>City</b>: ${data.location.city}</p>
      <button  onclick="myFunction()">Connect</button>
    </div>
  </div>
</div>
 `;
    document.getElementById("results").innerHTML  = userCard;
  });
};
function myFunction() {
  let element = document.getElementById("myDIV");
  element.classList.toggle("mystyle");
}

CodePudding user response:

One way you could do it is to alter your arrow function from:

(data) => {

To this:

(data, index) => {

which will then give you what array index you're at and you can use an if statement to execute your desired logic.

CodePudding user response:

First, each element's ID attribute should be unique over the complete DOM, so instead, consider adding myDiv as a class.

Now to get your function working you need to select just that element, and there are a couple of ways you can do it with different levels of jankiness.

Jank

If you pass this, into your onclick, it passes the button element being clicked. Assuming the div you want to manipulate is its parent, you can do something like this:

<div >
    <!-- Content -->
    <button  onclick="myFunction(this.parentElement)">Connect</button>
</div>

function myFunction(element) {
  element.classList.toggle('mystyle');
}

Or a nicer looking:

<div >
    <!-- Content -->
    <button  onclick="myFunction(this)">Connect</button>
</div>

function myFunction(button) {
  button.parentElement.classList.toggle('mystyle');
}

But this assumes that the element you want to edit will always be the same heirarchal distance form the button, so if you put your button in a container, this'll no longer work. That's why it's janky.

Less Jank

You can pass the event itself into the function, and events have a useful method composedPath() (read about it here) which returns an array of (simplified for the sake of this use case) each parent object outwards from the target to the window. Then we can loop over the array, and toggle the class according to a condition. Since we moved myDIV into the classList, let's look for that.

<div >
    <!-- Content -->
    <button  onclick="myFunction(event)">Connect</button>
</div>

function myFunction(event) {
  const eventPath = event.composedPath();
  eventPath.forEach((element) => {
    if element.classList?.contains('myDIV') {
      element.classList.toggle('mystyle');
    }
  }
}

Note here that the ?. after classList is an optional chaining operator which is useful if you're trying to call a function that may not exist. It's used here because the outermost objects in our path are the document and window objects, which don't have a classList property. So trying to access their classlist returns undefined, and if we trying to call undefined.contains() we get an error since that function doesn't exist.

Good

One disadvantage to both the previous methods is that they're localized to the container of the button being pressed. This means that if you want to other stuff in your code that has to do with the data of that specific card, it'll be tough to single it out since we're dealing with just HTML elements.

What you can do is set a custom data attribute to your myDIV element, and make its value something specific to the user data, like the user ID if it's available in your model. This lets us generalize the function to take user ID as an input, and search the DOM according to it (while doing anything else with our code using that ID).

`<div  data-user-id="${data.user.ID}">
    <!-- Content -->
    <button  onclick="myFunction(${data.user.ID})">Connect</button>
</div>
`
function myFunction(userID) {
  const userCard = document.querySelector(`[data-user-id = "${userID}]"`);
  userCard.classList.toggle('mystyle');
}

That way, you're working with ID's instead of elements and you can also call myFunction(userID) anywhere in your code and it'll work.

  • Related