Reputation: 1008
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
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
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