Nesta
Nesta

Reputation: 1008

Filter todo items in vanilla javascript todo list

I'm building a todo list and now I'm stuck on trying to implement a HTML-select dropdown to filter between the completed and uncompleted todo items. I'm using switch statements to select the respective blocks to be executed however I keep getting the following error "Uncaught TypeError: Cannot set property 'display' of undefined" but I don't understand what element is undefined.

const input = document.getElementById('input')
const addTodoButton = document.getElementById('addTodoButton')
const todoUL = document.getElementById('todoUL')
const filterOptions = document.querySelector('.filter-todos')

addTodoButton.addEventListener('click', addTodo)
todoUL.addEventListener('click', remove);
filterOptions.addEventListener('click', filterTodos);

// Add todo
function addTodo(e) {
    e.preventDefault()
    const todoText = input.value
    const todoEl = `<li><span>${todoText}</span> <button class="delete" id="deleteTodoButton"><i class="far fa-trash-alt"></i>Delete</button> <button class="complete" id="completeTodoButton"><i class="fas fa-check"></i>Completed</button></li>`
    input.value = ""
    input.focus()

    if (!todoText) {
        alert('You must type a todo')
    } else {
        todoUL.insertAdjacentHTML("beforeend", todoEl)
    }
}

// Remove/Complete todo
function remove(e) {
    if (e.target.id == 'deleteTodoButton') {
        e.target.parentElement.remove()
        input.focus()
    } else {
        e.target.previousElementSibling.previousElementSibling.classList.toggle('completed')
        input.focus()
    }
}

function filterTodos(e) {
    const todos = todoUL.childNodes
    todos.forEach(function (todoEl) {
        switch (e.target.value) {
            case "all":
                todoEl.style.display = "flex"
                break;

            case "completed":
                if (todoEl.classList.contains("completed")) {
                    todoEl.style.display = "flex"
                } else {
                    todoEl.style.display = "none"
                }
                break;
        }
    })
}
ul {
  list-style: none
}

.completed {
  text-decoration: line-through
}
<div class="form-container">
    <h1>Todo List App</h1>
    <form id="form">
      <input type="text" id="input" autocomplete="off" placeholder="Enter your todo">
      <button type="submit" class="add-todo" id="addTodoButton">Add</button>
      <select name="todos" class="filter-todos">
        <option value="all">All</option>
        <option value="completed">Completed</option>
        <option value="uncompleted">Uncompleted</option>
      </select>
    </form>
    <ul id="todoUL">
    </ul>
  </div>

Upvotes: 0

Views: 2706

Answers (2)

Nitheesh
Nitheesh

Reputation: 20006

I have made this working in my on way of interpretation. Hope this will help you in some ways.

I had identified some issues with the code you have written

First thing the click event to the select should be changed to change event

Second in case of remove function, you have to check the e.target.id == "completeTodoButton" before marking it completed, else our node selection wont work.

Third, in case of filterTodos function you have to ensure the node type is li before toggling the display. I used todoEl.nodeName === "LI" comparison for this.

Inside the switch case, you cannot use todoEl.classList.contains("completed") because the completed class is not bibded to li instead it in bonded to the span tag inside the li. You could either move this the class to li node and stle the elements accordingly or if you want to continue with the current structure you should use todoEl.children[0].classList.contains("completed") because the first child node of the li is the span with class completed.

Also you missed the case uncompleted in the switch.

ul {
    list-style: none;
}

.completed {
    text-decoration: line-through;
}
<div class="form-container">
    <h1>Todo List App</h1>
    <form id="form">
    <input
        type="text"
        id="input"
        autocomplete="off"
        placeholder="Enter your todo"
    />
    <button type="submit" class="add-todo" id="addTodoButton">Add</button>
    <select name="todos" class="filter-todos">
        <option value="all">All</option>
        <option value="completed">Completed</option>
        <option value="uncompleted">Uncompleted</option>
    </select>
    </form>
    <ul id="todoUL"></ul>
</div>

<script>
    const input = document.getElementById("input");
    const addTodoButton = document.getElementById("addTodoButton");
    const todoUL = document.getElementById("todoUL");
    const filterOptions = document.querySelector(".filter-todos");

    addTodoButton.addEventListener("click", addTodo);
    todoUL.addEventListener("click", remove);
    filterOptions.addEventListener("change", filterTodos);

    // Add todo
    function addTodo(e) {
      e.preventDefault();
      const todoText = input.value;
      const todoEl = `<li><span>${todoText}</span>
        <button class="delete" id="deleteTodoButton"><i class="far fa-trash-alt"></i>Delete</button>
        <button class="complete" id="completeTodoButton"><i class="fas fa-check"></i>Completed</button></li>`;
      input.value = "";
      input.focus();

      if (!todoText) {
        alert("You must type a todo");
      } else {
        todoUL.insertAdjacentHTML("beforeend", todoEl);
      }
    }

    // Remove/Complete todo
    function remove(e) {
      if (e.target.id == "deleteTodoButton") {
        e.target.parentElement.remove();
        input.focus();
      } else if (e.target.id == "completeTodoButton") {
        e.target.previousElementSibling.previousElementSibling.classList.toggle(
          "completed"
        );
        input.focus();
      }
    }

    function filterTodos(e) {
      const todos = todoUL.childNodes;
      todos.forEach(function(todoEl) {
        if (todoEl.nodeName === "LI") {
          switch (e.target.value) {
            case "all":
              todoEl.style.display = "flex";
              break;

            case "completed":
              if (todoEl.children[0].classList.contains("completed")) {
                todoEl.style.display = "flex";
              } else {
                todoEl.style.display = "none";
              }
              break;

            case "uncompleted":
              if (todoEl.children[0].classList.contains("completed")) {
                todoEl.style.display = "none";
              } else {
                todoEl.style.display = "flex";
              }
              break;
          }
        }
      });
    }
  </script>

Upvotes: 2

Jaromanda X
Jaromanda X

Reputation: 1

const input = document.getElementById('input')
const addTodoButton = document.getElementById('addTodoButton')
const todoUL = document.getElementById('todoUL')
const filterOptions = document.querySelector('.filter-todos')

addTodoButton.addEventListener('click', addTodo)
todoUL.addEventListener('click', remove);
filterOptions.addEventListener('click', filterTodos);

// Add todo
function addTodo(e) {
    e.preventDefault()
    const todoText = input.value
    const todoEl = `<li><span>${todoText}</span> <button class="delete" id="deleteTodoButton"><i class="far fa-trash-alt"></i>Delete</button> <button class="complete" id="completeTodoButton"><i class="fas fa-check"></i>Completed</button></li>`
    input.value = ""
    input.focus()

    if (!todoText) {
        alert('You must type a todo')
    } else {
        todoUL.insertAdjacentHTML("beforeend", todoEl)
    }
}

// Remove/Complete todo
function remove(e) {
    if (e.target.id == 'deleteTodoButton') {
        e.target.parentElement.remove()
        input.focus()
    } else {
        e.target.previousElementSibling.previousElementSibling.classList.toggle('completed')
        input.focus()
    }
}

function filterTodos(e) {
    const todos = todoUL.querySelectorAll('li > span');
    todos.forEach(function (todoEl) {
        const tgt = todoEl.parentElement;
        switch (e.target.value) {
            case "all":
                tgt.style.display = "flex"
                break;

            case "completed":
                if (todoEl.classList.contains("completed")) {
                    tgt.style.display = "flex"
                } else {
                    tgt.style.display = "none"
                }
                break;
            case "uncompleted":
                if (todoEl.classList.contains("completed")) {
                    tgt.style.display = "none"
                } else {
                    tgt.style.display = "flex"
                }
                break;
        }
    })
}
ul {
  list-style: none
}

.completed {
  text-decoration: line-through
}
<div class="form-container">
    <h1>Todo List App</h1>
    <form id="form">
      <input type="text" id="input" autocomplete="off" placeholder="Enter your todo">
      <button type="submit" class="add-todo" id="addTodoButton">Add</button>
      <select name="todos" class="filter-todos">
        <option value="all">All</option>
        <option value="completed">Completed</option>
        <option value="uncompleted">Uncompleted</option>
      </select>
    </form>
    <ul id="todoUL">
    </ul>
  </div>

Upvotes: 1

Related Questions