I am making a small to-do list web app. I found a bug where after I create three todos and start editing them, the second edit starts changing not only the to-do in question, but also another one – the first one.
I have tried everything possible, and I am certain the problem lies within the todoId variable in the toDoCreator.js editTodo method. Also, it looks like there is something weird with the event listeners on the modal’s save button – I tried to solve that with another function resetting the event listeners but I still get two of them after the second edit.
I am using webpack, so I am not sure how to paste this here…
Thanks!
// from my UI.js
import checkmarkLogo from './assets/checkmark.svg';
import plusSvg from './assets/plus.svg';
import plusWhiteSvg from './assets/plus-white.svg';
import trashSvg from './assets/trash.svg';
import editSvg from './assets/edit.svg';
import todoManager from './toDoCreator.js';
export default function init() {
getAssets();
appendAssets();
bindEvents();
}
export function getDom() {
return {
headerLogo: document.querySelector('.header-logo'),
addProjectBtn: document.querySelector('#add-project-btn'),
addTodoBtn: document.querySelector('#add-todo-btn'),
todoModal: document.querySelector('.todo-modal'),
todoModalSaveBtn: document.querySelector('#todo-modal-save-btn'),
todoModalCloseBtn: document.querySelector('#todo-modal-close-btn'),
todoContainer: document.querySelector('.todo-container')
}
}
function bindEvents() {
getDom().addTodoBtn.addEventListener('click', e => openTodoModal(e.target));
getDom().addProjectBtn.addEventListener('click', openNewProjectModal);
getDom().todoModalCloseBtn.addEventListener('click', () => getDom().todoModal.close())
}
export function getForm() { // does this really go here? It is DOM stuff after all....
return {
title: document.querySelector('#title'),
description: document.querySelector('#description'),
dueDate: document.querySelector('#dueDate'),
project: document.querySelector('#project'),
priority: document.querySelector('input[name="priority"]:checked') // is not getting the value!!
}
}
export function renderTodos() {
getDom().todoContainer.textContent = "";
for (let i = 0; i < todoManager.todoManager.todos.length; i++) {
const currentTodo = todoManager.todoManager.todos[i];
const newTodoDiv = document.createElement('div');
const todoTitle = document.createElement('p');
const todoDate = document.createElement('p');
const checkbox = document.createElement('input');
const detailsButton = document.createElement('button');
const todoEditIcon = getAssets().editIcon;
const todoTrashIcon = getAssets().trashIcon; // why doesn't it work if I don't take the trashIcon at this point and store it in a variable (i.e, I pass getAssets().trashIcon as a parameter when calling bindTodoButtons)
newTodoDiv.classList.add('todo');
newTodoDiv.setAttribute("id", i);
todoTitle.textContent = currentTodo.title;
todoDate.textContent = currentTodo.dueDate;
checkbox.setAttribute("type", "checkbox");
detailsButton.textContent = 'DETAILS';
detailsButton.setAttribute("id", "details-btn");
newTodoDiv.appendChild(checkbox);
newTodoDiv.appendChild(todoTitle);
newTodoDiv.appendChild(detailsButton);
newTodoDiv.appendChild(todoDate);
newTodoDiv.appendChild(todoEditIcon);
newTodoDiv.appendChild(todoTrashIcon);
getDom().todoContainer.appendChild(newTodoDiv);
bindTodoButtons(detailsButton, todoEditIcon, todoTrashIcon);
}
}
export function removeSaveBtnEventListeners() {
getDom().todoModalSaveBtn.removeEventListener('click', todoManager.todoManager.createTodo);
getDom().todoModalSaveBtn.removeEventListener('click', () => todoManager.todoManager.editTodo(caller.id));
}
function openTodoModal(caller) { //can I actually put this in the create to do fn?
// Check where do I call it from, if class = todo then it means I'm editing, else it means I'm creating
if(caller.classList.contains('todo')) {
getDom().todoModalSaveBtn.addEventListener('click', () => todoManager.todoManager.editTodo(caller.id));
getDom().todoModal.showModal();
} else {
getDom().todoModalSaveBtn.addEventListener('click', todoManager.todoManager.createTodo);
getDom().todoModal.showModal();
}
}
function updateModal(todoId) {
const todoBeingEdited = todoManager.todoManager.todos[todoId];
getForm().title.value = todoBeingEdited.title;
getForm().description.value = todoBeingEdited.description;
getForm().dueDate.value = todoBeingEdited.dueDate;
getForm().project.value = todoBeingEdited.project;
// falta priority
}
function openNewProjectModal() {
console.log("opening")
}
function getAssets() {
// logo
const logo = new Image();
logo.src = checkmarkLogo;
// plus sign
const plusSign = new Image();
plusSign.src = plusSvg;
// white plus
const plusWhiteSign = new Image();
plusWhiteSign.src = plusWhiteSvg;
// edit
const editIcon = new Image();
editIcon.src = editSvg;
editIcon.classList.add('todo-icon');
// trash
const trashIcon = new Image();
trashIcon.src = trashSvg;
trashIcon.classList.add('todo-icon');
return {
logo, plusSign, plusWhiteSign, editIcon, trashIcon
}
}
function appendAssets() {
getDom().headerLogo.appendChild(getAssets().logo);
getDom().addProjectBtn.prepend(getAssets().plusSign);
getDom().addTodoBtn.prepend(getAssets().plusWhiteSign);
}
function bindTodoButtons(detailsButton, todoEditIcon, todoTrashIcon) {
// detailsButton.addEventListener('click', openDetailsModal);
todoEditIcon.addEventListener('click', e => openTodoModal(e.target.parentNode));
todoTrashIcon.addEventListener('click', e => deleteTodo(e.target.parentNode));
}
function deleteTodo(todo) {
getDom().todoContainer.removeChild(todo);
}
// from my toDoCreator.js
// There are also project (categories) for the to-dos.
import { getForm, getDom, renderTodos, removeSaveBtnEventListeners } from "./UI"
const todoManager = {
todos: [],
createTodo: function() {
const todo = {
title: getForm().title.value,
description: getForm().description.value,
dueDate: getForm().dueDate.value,
project: getForm().project.value
}
// check if the div about to be created already exists
if(todoManager.todos.some(el => el.title === todo.title)) {
console.log("already exists");
// alert the user if it already exists.
} else {
todoManager.todos.push(todo); // can I substitute todoManager with "this"?
renderTodos();
removeSaveBtnEventListeners(); // could maybe be changed with another function that toggles the add/remove event list
}
},
editTodo: function(todoId) {
console.log(todoId);
const newData = {
title: getForm().title.value,
description: getForm().description.value,
dueDate: getForm().dueDate.value,
project: getForm().project.value
}
todoManager.todos[todoId] = newData;
renderTodos();
removeSaveBtnEventListeners();
},
};
export default { todoManager };
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Chicle&family=Josefin+Sans&family=Montserrat&family=Poppins&family=Varela+Round&display=swap" rel="stylesheet">
<title><%= htmlWebpackPlugin.options.title%></title>
</head>
<body>
<div class="container">
<div class="header-container">
<div class="header-logo"></div>
<h1>LISTO!</h1>
</div>
<div class="grid">
<div class="sidebar-container">
<nav class="sidebar-links">
<ul>
<li id="inbox">Inbox</li>
</ul>
<h2 class="sidebar-title">Projects</h2>
<ul>
<li id="gym">Gym</li>
<li id="study">Study</li>
<li id="work">Work</li>
<button id="add-project-btn">Add project</button>
</ul>
</nav>
</div>
<div class="main">
<div class="todo-container"></div>
<button id="add-todo-btn"></button>
</div>
</div>
<dialog class="todo-modal"><!--Any way to uncheck all radio buttons by default-->
<p id="errorMsg"></p>
<form method="dialog" id="addTodoForm">
<label for="title">Title</label>
<input type="text" id="title" placeholder="Clean the house">
<label for="description">Description</label>
<textarea id="description" placeholder="Vacuum even the smallest little gap, then wash floors."></textarea>
<label for="dueDate">Due date</label>
<input type="date" id="dueDate">
<label for="project">Does it belong to an existing project?</label>
<select id="project" name="project">
</select>
<label for="priority" id="label-priority">Priority</label>
<fieldset id="priority">
<input type="radio" id="low" name="priority">
<label for="low" id="label-low">LOW</label>
<input type="radio" id="medium" name="priority">
<label for="medium" id="label-medium">MEDIUM</label>
<input type="radio" id="high" name="priority">
<label for="high" id="label-high">HIGH</label>
</fieldset>
<div class="form-buttons">
<button type="submit" id="todo-modal-save-btn">Save</button>
<button type="submit" id="todo-modal-close-btn">Close</button>
</div>
</form>
</dialog>
</div>
</body>
</html>

