1

I'm doing a todo List and I'm stuck in filtering the list items of the todos list. I'm not even sure where the problem is.

I create a list item and when clicking the check icon I add a class of "completed" to it and in another function I iterate the list childNotes and try to filter the ones that have that completed class but keep getting an error of:

"Uncaught TypeError: Cannot set property 'display' of undefined at app.js:84. at NodeList.forEach () at HTMLSelectElement.filterTodo (app.js:76)" even thought the list has list items

code : https://codepen.io/mullerz/pen/vYNeMEO?editors=1111

if(item.classList[0] === "complete-btn"){
        const todo = item.parentElement;
        todo.classList.toggle("completed"); 
     }  
}


function filterTodo(e){
    const todos = todoList.childNodes;
    //console.log(todos)

    todos.forEach(function(todo, index){
        //if(index === 0){return}

        console.log({todo})
        switch (e.target.value) {

            case "all" :
                console.log(todo.classList)
                todo.style.display = "flex";

            break;

            case "completed" :

                if(todo.classList.contains("completed") ){
                console.log(todo.classList)

                    todo.style.display = "flex";
                }
                else{
                    todo.style.display = "none";
                }
```


  • Please make your runnable examples **here, on-site** using Stack Snippets (the `[<>]` toolbar button), rather than offsite (or in addition to). When you make your runnable example off-site, it's all too easy to leave out some essential part of it in the question text. (But nice one, I think everything essential **is** there in this question.) [Here's how to do a Stack Snippet](https://meta.stackoverflow.com/questions/358992/). – T.J. Crowder May 02 '20 at 10:11

2 Answers2

0

Not all child nodes are elements, some are Text nodes or Comment nodes or similar. Only elements have a style property.

You might want children rather than childNodes. children is just child elements, skipping other kinds of nodes. But note that officially, the HTMLCollection you get from children doesn't have forEach (although it does in some implementations). (The NodeList you get from childNodes is defined to have forEach, and reliably does on modern browsers [only].)

Alternatively, keep using childNodes, but then check node.nodeType === Node.ELEMENT_NODE before looking for the style property.


If you want, you can polyfill forEach on HTMLCollection (and on NodeList in older browsers that don't have it natively); I describe how to do that in this answer.

T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
0

I found several issues in your code. You can check my solution.

I've used the change event to trigger the filter function also used const todos = [...todoList.children]; to get the required children.

console.clear();

const todoInput = document.querySelector(".todo-input");
const todoButton = document.querySelector(".todo-button");
const todoList = document.querySelector(".todo-list");
const filterOption = document.querySelector(".filter-todo");

todoButton.addEventListener("click", addTodo)
todoList.addEventListener("click", deleteCheck)
filterOption.addEventListener("change", filterTodo)


function addTodo(e){


 e.preventDefault();
 
 // create div that will have the line items of each task
 const todoDiv = document.createElement("div");
 todoDiv.classList.add("todo");

 // create line item
 const newTodo = document.createElement("li");
 newTodo.innerText = todoInput.value;
 newTodo.classList.add("todo-item");
 todoDiv.appendChild(newTodo);

 // create completion button
 const completeButton = document.createElement("button");
 completeButton.innerHTML = '<i class="fas fa-check"></i>';
 completeButton.classList.add("complete-btn");
 todoDiv.appendChild(completeButton);

 // create discard button
 const trashButton = document.createElement("button");
 trashButton.innerHTML = '<i class="fas fa-trash"></i>';
 trashButton.classList.add("trash-btn");
 todoDiv.appendChild(trashButton);

 // attach de div we just created to the existing ul in the html
 todoList.appendChild(todoDiv)
 // clear
 todoInput.value = "";

}


// delete item

function deleteCheck(e){

 const item = e.target;

 if(item.classList[0] === "trash-btn"){ 
  const todo = item.parentElement;
  todo.classList.add("fall");
  // check if transition of the class css "fall" has ended and then remove the item
  todo.addEventListener("transitionend", function(){
   todo.remove();
  });
 }

// check mark
 if(item.classList[0] === "complete-btn"){
  const todo = item.parentElement;
   todo.classList.toggle("completed"); 

  }
 
}


function filterTodo(e){
 const todos = [...todoList.children];
 
 todos.forEach(function(todo, index){
  switch (e.target.value) {

   case "all" :
    todo.style.display = "flex";

   break;

   case "completed" :
    
    if(todo.classList.contains("completed") ){

     todo.style.display = "flex";
    }
    else{
     todo.style.display = "none";
    }
   break;

   case "uncompleted" :
    if(!todo.classList.contains("completed")){
     todo.style.display = "flex";
    }
    else{
     todo.style.display = "none";

    }

  }
 })

}
*{
 margin:0;
 padding:0;
 box-sizing: border-box;
}

body{
 background-image: linear-gradient(120deg, #f6d365, #fda0a5);
 color:white;
 font-family: 'Cairo', sans-serif;
 min-height:100vh;

}
header{
 font-size:1.5rem;


}
header, form{
 min-height:20vh;
 display:flex;
 justify-content: center;
 align-items: center;
}

form input, form button{
 padding:0.5rem;
 font-size:2rem;
 border:none;
 background: white;
}

form button{
 background:#d88771;
 color:white;
}
form button:hover{
 background:white ;
 color:#d88771;
}

.todo-container{
 display:flex;
 justify-content: center;
 align-items: center;
}

.todo-list{
 min-width:20%;
 list-style:none;
}

.todo{
 margin: .5rem;
 background: white;
 color:black;
 font-size:1.5rem;
 display:flex;
 justify-content: space-between;
 align-items:center;
 transition: all .5s ease;
}

.todo li{ flex:1; }

.trash-btn, .complete-btn{
 background: #ff6f47;
 color:white;
 border:none;
 padding:1rem;
 cursor:pointer;
 font-size:1rem;
}

.complete-btn{
 background:rgb(73, 204, 73);
 color:white;
 border:none;
 padding:1rem;
 cursor:pointer;
 font-size:1rem;
}

.todo-item{ padding:0rem .5rem; }

.fa-trash, .fa-check{ pointer-events: none; }

.completed{
 text-decoration:line-through;
 opacity:.5;
}

.fall{
 transform: translateY(8rem) rotateZ(20deg);
 opacity:0;
}
select{
 -webkit-appearance: none;
 -moz-appearance: none;
 appearance:none;
 outline:none;
 border:none;
}

.select{
 margin: 1rem;
 position: relative;
 overflow: hidden;
}
select{
 color: #ff6f47;
 width: 10rem;
 cursor: pointer;
 padding: 1rem;
}

.select::after{
 content: "\25BC";
 position:absolute;
 background: #ff6f47;
 top:0;
 right:0;
 padding:1rem;
 pointer-events:none;
 transition: all 0.3s ease;
}

.select:hover::after{
 background: white;
 color:#ff6f47;
}
 <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.0.7/css/all.css">
<header>
  <h1>To do List</h1>
 </header> 
 <form>
  <input type="text" class="todo-input">
  <button class="todo-button" type="submit">
   <i class="fas fa-plus-square"></i>
  </button>
  <div class="select">
   <select name="todos" class="filter-todo">
    <option value="all">All</option>
    <option value="completed">Completed</option>
    <option value="uncompleted">Uncompleted</option>
   </select>
  </div>
 </form>
 <div class="todo-container">
  <ul class="todo-list">
  </ul>
 </div>
Sifat Haque
  • 5,357
  • 1
  • 16
  • 23