I’ve decided to learn Javascript and after watching a few tutorials, I’m training by doing challenges. One of the iconic challenge is doing a to do list. I know that there are examples with answers but I would like to know why my code doesn’t work…
The issue seems to be that I can only delete the last task but not the others. And I think that for the same reason when I check any other boxes than the last one, the text decoration doesn’t appear (line in the middle).
PS: if you have any commentary on my solution don’t hesitate, I’m eager to learn.
Thank you in advance
const newTaskInput = document.getElementById("newTask");
const tasks = document.querySelector(".tasks");
let tasksCheckboxes = tasks.querySelectorAll("input[type='checkbox']");
let taskCount = 0;
//To add the new task in the content section
newTaskInput.addEventListener("keypress", (e) => {
if (e.key == "Enter" && newTaskInput.value != "" && newTaskInput.value != undefined) {
tasks.innerHTML += `
<li id="${taskCount}-section">
<input type="checkbox" id="${taskCount}chk">
<label class="label-for-${taskCount}" for="${taskCount}chk">${newTaskInput.value}</label>
<input type="button" id="${taskCount}btn" value="x">
</li>`;
addListenerToCheckbox(taskCount);
addListenerToButton(taskCount);
newTaskInput.value = "";
++taskCount;
}
})
const addListenerToCheckbox = (aTaskCount) => {
const checkbox = document.getElementById(aTaskCount + "chk");
const checkLabel = document.querySelector(`.label-for-${aTaskCount}`);
checkbox.addEventListener("click", () => {
if (checkbox.checked) {
checkLabel.style.textDecoration = "line-through";
} else {
checkLabel.style.textDecoration = "none";
}
})
}
const addListenerToButton = (aTaskCount) => {
console.log(taskCount);
const taskBtn = document.getElementById(aTaskCount + "btn");
const task = document.getElementById(aTaskCount + "-section");
taskBtn.addEventListener("click", () => {
console.log("gg");
task.remove();
})
}
<h1>To Do List:</h1>
<div class="box">
<input type="text" id="newTask">
<div class="content">
<ul class="tasks"></ul>
</div>
</div>
2
Answers
When you modify the innerHTML of an element, you lose all of the event handlers that were previously associated with it. So when you write
You are effectively doing
But the event handlers are not stored in tasks innerHTML.
The reason why your delete button only works on the last element is because you add the handler after modifying the HTML of the element.
You could avoid this by avoiding direct HTML manipulation of the container and using appendChild in conjunction with createElement.
Your revised code would look like this:
In terms of other commentary your HTML file has a rogue
</div>
which is not valid HTML.See also: addEventListener to innerHTML
The issue is because within the
addListenerToX()
methods you’re re-assigining the value ofcheckbox
andtaskBtn
to the last instance of those elements. Those variables are then used within the event handlers at a later point, so any pre-existing handlers now point to the last created elements.You can fix this, and improve your code quality, by using a single delegated event handler for each element. This will enable you to also remove the dynamically generated id/class attributes, which are an anti-pattern.
You can also use a
template
element to hold the HTML instead of putting it in to the JS, which should be kept separate as much as possible.Lastly, if you wrap the checkbox in the
label
it removes the need for the dynamicfor
attribute.Here’s an updated working example containing the above improvements: