skip to Main Content

I want to build a todo list.

I managed to get all eventListeners to work for the first task, but for each subsequent task these listeners are removed or not assigned at all. I tried it with a forEach loop, but either I used it wrong or it didn’t work. Now I don’t understand exactly what I can change so that all further tasks also listen to these eventListeners.

I tried using a forEach loop on the close eventListener but then the whole closing eventListener stopped working on every task.

const FORM = document.getElementById("form");
let input = document.getElementById("text");
const CLOSE = document.getElementById("close");
const WRAPPER = document.querySelector(".newWrapper");
const TASK = document.getElementById("task");

FORM.addEventListener("submit", (evt) => {
  evt.preventDefault();

  let div = document.createElement("div");
  div.classList.add("newWrapper");
  let p = document.createElement("p");
  let renderedText = document.createTextNode(input.value);
  p.setAttribute("id", "task");
  p.setAttribute("class", "w-96 bg-white text-center py-3");
  let i = document.createElement("i");
  i.setAttribute("id", "close")
  i.setAttribute("class", "fa fa-x fa-2x h-12 p-2 cursor-pointer");

  p.appendChild(renderedText);
  div.appendChild(p);
  div.appendChild(i)
  document.body.appendChild(div);


});


CLOSE.addEventListener("click", () => {
  document.body.removeChild(WRAPPER)
});

TASK.addEventListener("click", () => {
  WRAPPER.setAttribute("class", "flex justify-center");
  if (TASK.className !== "checked w-96 py-3 text-center") {
    TASK.setAttribute("class", "checked w-96 py-3 text-center");
  } else {
    TASK.setAttribute("class", "w-96 bg-white text-center py-3");
  }

});
<script src="https://cdn.tailwindcss.com"></script>
  
<div class="flex justify-center ">
  <h1 class="text-3xl">Todos</h1>
</div>
<form id="form" class="flex justify-center items-center">
  <input id="text" type="text" class="w-80 bg-white rounded-md py-3" placeholder="Essen gehen...">
  <i type="submit" class="fa-solid fa-check hover:cursor-pointer"></i>
</form>
<br>
<br>
<div class="newWrapper">
  <p id="task" class="w-96 bg-white text-center py-3">get food</p>
  <i id="close" class="fa fa-x fa-2x h-12 p-2 cursor-pointer"></i>
</div>
<div class="newWrapper">
  <p id="task" class="w-96 bg-white text-center py-3">get food</p>
  <i id="close" class="fa fa-x fa-2x h-12 p-2 cursor-pointer"></i>
</div>

3

Answers


  1. ID values are required to be unique in any given document. getElementById only returns a single element. (In an invalid document reusing the same id value, it’ll return the first one, though I don’t know that that’s specified anywhere. Don’t do it.)

    Instead, use a class. Then, either:

    1. Hook the event on an element that all of these elements are inside (the body element if there’s nothing more specific), and then take action if the event passed through one of these elements. This is called "event delegation."

      document.body.addEventListener("click", (event) => {
          // Did the event pass through an element with `class="task"`?
          const task = event.target.closest(".task");
          if (task /* && this.contains(task) */) {
              // Yes, handle it:
              const wrapper = task.closest(".newWrapper");
              // ...do something with `wrapper`, probably via `wrapper.classList`'s `toggle`, `add`, and/or `remove` methodss...
          }
      });
      

      One of the keys to that is the closest method on an element, which finds the first element in the element’s ancestry (starting with the element itself) that matches a given CSS selector.

      The commented-out call to contains there isn’t needed if you’re doing this with document.body, but you may need it if using delegation deeper in the DOM tree, to avoid matching elements that are ancestors to the one where you’re handling the event. It’s relatively rare, but worth noting.

    2. Look up all the matching elements (for instance, via document.querySelectorAll) and hook up the handler to each of them.

      function taskClickHandler(event) {
          const wrapper = this.closest(".newWrapper");
          // ...do something with `wrapper`, probably via `wrapper.classList`'s `toggle`, `add`, and/or `remove` methodss...
      }
      for (const task of document.querySelectorAll(".task")) {
          task.addEventHandler("click", taskClickHandler);
      }
      

    There are pros and cons to each. Event delegation is particularly well-suited to situations where you’re adding and removing elements dynamically, though it works just fine if you aren’t, as well. It’s slightly more sensitive to interference from other event handlers for the same event.

    More to explore:

    Login or Signup to reply.
  2. Please note that id can’t be same to many html elements, instead use class.
    Here it is an example, have a look, understand it, then apply the thing to your code.

    <div class="box">Box 1</div>
    <div class="box">Box 2</div>
    <div class="box">Box 3</div>
    

    const boxes = document.querySelectorAll('.box');
    
    boxes.forEach(box => {
      box.addEventListener('click', function handleClick(event) {
        console.log('box clicked', event);
    
        box.setAttribute('style', 'background-color: yellow;');
      });
    });
    

    output

    Login or Signup to reply.
  3. Here is your code using delegation.

    I added relevant libraries too

    I add the todos to its own div to delegate from there

    window.addEventListener("DOMContentLoaded", () => {
      const form = document.getElementById("form");
      const input = document.getElementById("text");
      const todoWrapper = document.getElementById("todoWrapper");
    
      form.addEventListener("submit", (evt) => {
        evt.preventDefault();
        let div = document.createElement("div");
        div.classList.add("newWrapper");
        let p = document.createElement("p");
        let renderedText = document.createTextNode(input.value);
        p.setAttribute("class", "task w-96 bg-white text-center py-3");
        let i = document.createElement("i");
        i.setAttribute("class", "close fa fa-x fa-2x h-12 p-2 cursor-pointer");
        p.appendChild(renderedText);
        div.appendChild(p);
        div.appendChild(i)
        todoWrapper.appendChild(div);
      });
    
    
      todoWrapper.addEventListener("click", (e) => {
        const tgt = e.target; 
        if (tgt.matches("i.close")) {
          tgt.closest("div.newWrapper").remove();
          return
        }
        const wrapper = tgt.closest("p.task");
        if (!wrapper) return;
        wrapper.classList.toggle("checked")
      });
    });
    .newWrapper { border: 1px solid black }
    <script src="https://cdn.tailwindcss.com"></script>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.2/css/all.min.css" integrity="sha512-z3gLpd7yknf1YoNbCzqRKc4qyor8gaKU1qmn+CShxbuBusANI9QpRohGBreCFkKxLhei6S9CQXFEbbKuqLg0DA==" crossorigin="anonymous" referrerpolicy="no-referrer"
    />
    
    <div class="flex justify-center ">
      <h1 class="text-3xl">Todos</h1>
    </div>
    <form id="form" class="flex justify-center items-center">
      <input id="text" type="text" class="w-80 bg-white rounded-md py-3" placeholder="Essen gehen...">
      <i type="submit" class="fa-solid fa-check hover:cursor-pointer"></i>
    </form>
    <div id="todoWrapper"></div>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search