skip to Main Content

I have a EJS template which gets included on a page by running a for loop. Each template has a link which when clicked should toggle the display/hiding of a number of checkboxes. I am trying to use AJAX and have a javascript file which is linked to the template. In this javascript file I add an eventlistener to the link on that template. However only the link on the first template on the page fires.

The EJS template:

<link rel="stylesheet" href="../css/categorieslist.css">

<article class="post-item" id="<%= user.UserID %>" data-userId="<%= user.UserID %>">
  <p>
      <%= user.Email %>
      <input type="hidden" class="hiddenField" id="userid" name="userid" value="<%= user.UserID %>" required>
      <p><%= user.UserID %></p>
      <a class="btn catbtn" href="/admin-usercategories/<%= user.UserID %>/edit">Edit Call</a> 
      <div class="categorieslist">
         <% for (const cat of categories) { %>
          <p>
            <input type="checkbox" id="<%= cat.CategoryID %>" value="<%= cat.CategoryID %>">
            <label for="<%=cat.CategoryID %>"><%= cat.CategoryName %></label>
          </p>
          <% } %>

        </div>
     </p>
</article>
<script type="text/javascript" src="/javascript/usercategories.js" charset="utf-8"></script>

The javascript file:

const btn = document.querySelector(".catbtn");
const displayElement = document.querySelector(".categorieslist");

btn.addEventListener('click', function() {
    fetchUserCategories();
});

async function fetchUserCategories() {
    if (displayElement.style.display === "none") {
        displayElement.style.display = "block";
    } else {
        displayElement.style.display = "none";
    }
}

2

Answers


  1. However only the link on the first template on the page fires.

    Because a click handler is only being attached to one element. This finds an element:

    const btn = document.querySelector(".catbtn");
    

    Even the variable name implies this. And then the usage of the variable indicates that it’s just one element:

    btn.addEventListener('click', function() {
      fetchUserCategories();
    });
    

    If you want to find multiple elements, you want querySelectorAll:

    const btns = document.querySelectorAll(".catbtn");
    

    Then loop over the results to attach the click handlers:

    for (let btn of btns) {
      btn.addEventListener('click', function() {
        fetchUserCategories();
      });
    }
    

    Of course, this will lead to the next problem… Every button does exactly the same thing. It "fetches user categories" on the first displayElement. You probably want the immediate sibling element of the one which was clicked. For example, you might pass the nextElementSibling to the function:

    for (let btn of btns) {
      btn.addEventListener('click', function() {
        fetchUserCategories(this.nextElementSibling);
      });
    }
    

    And then in fetchUserCategories use that element reference:

    function fetchUserCategories(displayElement) {
      // "displayElement" was passed to the function
    }
    

    If the markup could change then you might want to try other approaches to get the sibling element. For example:

    Techniques often include starting from the current element (this), traversing up the DOM to a common parent element, then selecting the target element from that parent.

    Login or Signup to reply.
  2. As there are more templates that have same structure with button having class catbtn. Then you can update this part of your code:-

    const btns = document.querySelectorAll(".catbtn");
    const displayElement = document.querySelector(".categorieslist");
    
    for(let btn of btns){
        btn.addEventListener('click', function() {
           fetchUserCategories();
        });
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search