javascript - I want to change the style in a item with a button click in the item, but when i click the button only the first item in the forech changes

I called an api from a website to make a list of items in my own website.

I do that with foreach. The list gets imported correctly, but I have a little problem.

Each item in the list has an description with display: none; and each item also has a button. If I click that button I want to make the display: inline;

I tried a lot of things, but every time I click a button only the first item in the foreach list changes.

I am not really good at explaining, but hopefully someone can help me.

        <?php
        foreach ($items as $item):
            echo "<script> 
            
            function hide_post(){
                hideshow = document.querySelector('#showMoreLessId');
                hideshowtext = document.querySelector('#showMoreLess');
                if (hideshow.style.display == 'inline'){
                    hideshow.style.display = 'none';
                    hideshowtext.innerHTML = 'Toon meer';
                }else{
                    hideshow.style.display = 'inline';
                    hideshowtext.innerHTML = 'Toon minder';
                }
            }
            </script>";
        ?>
            <div class="item" id="item">
                <h4>Versie: <?= $item->name;  ?></h4>
                <div id="showMoreLessId">
                    <p>Issues in versie: <?= $res->issuesFixedCount; ?> </p>
                    <p>Afgeronde issues: <?= $roundedIssue;?> </p>
                    <p>Nog te verwerken issues: <?= $uns->issuesUnresolvedCount;?> </p>
                </div>
                <small><?= $item->releaseDate; ?></small>
                <a onclick="hide_post()" id="showMoreLess">Toon meer</a>
                <a>|</a>
                <a href="<?='version/?version=' . $item->name; ?>">Meer info</a>
            </div>
            <?php
        endforeach;?>
    </div>
</div>

Answer

Solution:

Your issue is because you're repeating the same id on multiple elements in the DOM as you loop to create the HTML.

To fix this issue apply class attributes to the repeated content instead, and then use document.querySelectorAll() to retrieve them. Note that you will need to loop through the returned collection as well.

In addition you will need to use DOM traversal to find the elements related to the clicked a element.

In addition, your JS function should only be declared once, outside of your PHP loop.

Here's a working example with the above changes applied. Note that I also changed the class names to better describe the purpose of the elements.

// JS logic only defined once, separate from your PHP code...  

document.querySelectorAll('.toggle').forEach(el => {
  el.addEventListener('click', hide_post);
});

function hide_post(e) {
  e.preventDefault();
  
  const toggle = e.target;
  const item = e.target.closest('.item');
  const details = item.querySelector('.details');
  
  if (details.style.display == 'inline') {
    details.style.display = 'none';
    toggle.innerHTML = 'Toon meer';
  } else {
    details.style.display = 'inline';
    toggle.innerHTML = 'Toon minder';
  }
}
.details { display: none; }
<!-- HTML content generated by your PHP loop... -->

<div class="item">
  <h4>Versie: $item->name1</h4>
  <div class="details">
    <p>Issues in versie: $res->issuesFixedCount1</p>
    <p>Afgeronde issues: $roundedIssue1</p>
    <p>Nog te verwerken issues: $uns->issuesUnresolvedCount1</p>
  </div>
  <small>$item->releaseDate1</small>
  <a href="#" class="toggle">Toon meer</a>
  <a>|</a>
  <a href="<?='version/?version=' . $item->name; ?>">Meer info</a>
</div>

<div class="item">
  <h4>Versie: $item->name2</h4>
  <div class="details">
    <p>Issues in versie: $res->issuesFixedCount2</p>
    <p>Afgeronde issues: $roundedIssue2</p>
    <p>Nog te verwerken issues: $uns->issuesUnresolvedCount2</p>
  </div>
  <small>$item->releaseDate2</small>
  <a href="#" class="toggle">Toon meer</a>
  <a>|</a>
  <a href="<?='version/?version=' . $item->name; ?>">Meer info</a>
</div>

<div class="item">
  <h4>Versie: $item->name2</h4>
  <div class="details">
    <p>Issues in versie: $res->issuesFixedCount2</p>
    <p>Afgeronde issues: $roundedIssue2</p>
    <p>Nog te verwerken issues: $uns->issuesUnresolvedCount2</p>
  </div>
  <small>$item->releaseDate2</small>
  <a href="#" class="toggle">Toon meer</a>
  <a>|</a>
  <a href="<?='version/?version=' . $item->name; ?>">Meer info</a>
</div>

<div class="item">
  <h4>Versie: $item->name2</h4>
  <div class="details">
    <p>Issues in versie: $res->issuesFixedCount2</p>
    <p>Afgeronde issues: $roundedIssue2</p>
    <p>Nog te verwerken issues: $uns->issuesUnresolvedCount2</p>
  </div>
  <small>$item->releaseDate2</small>
  <a href="#" class="toggle">Toon meer</a>
  <a>|</a>
  <a href="<?='version/?version=' . $item->name; ?>">Meer info</a>
</div>

Answer

Solution:

You have defined the hide_post function in a loop, overriding the previous hide_post function. In your case, it's better to define the function outside the loop and pass a unique parameter to the function. Assuming the item name is unique and does not contain spaces, you can use the following.

function hide_post(postId){
     hideshow = document.querySelector('#showMoreLessId_'+postId);
     hideshowtext = document.querySelector('#showMoreLess_'+postId);
     if (hideshow.style.display == 'inline'){
        hideshow.style.display = 'none';
        hideshowtext.innerHTML = 'Toon meer';
     } else {
        hideshow.style.display = 'inline';
        hideshowtext.innerHTML = 'Toon minder';
     }
}

From your loop in PHP define the like as following,

<a onclick="hide_post('<?= $item->name;  ?>')" id="showMoreLess_"<?= $item->name; ?>>Toon meer</a>

Source