I'm writing JS for the FreeCodeCamp todo list; the corresponding HTML and CSS for this can be found on their page. I have made one edit to the HTML - the button with type "submit" has been changed to "button".
I did a few FCC lessons before this, and found their coding style to be a bit contrary to mine, so I decided to go off-road by coding in another environment with no guidance, except questions to Google's AI.
I want to double check that a specific AI suggestion was a good idea (N.B. I have not used any AI-generated code). Specifically, it recommended that I render and delete tasks individually, instead of redrawing the entire list each time the page is submitted (FCC's approach). This sounds sane to me, though as a beginner to webpages, I don't know if this has some other side effects.
// vars and init ---------------------------------------------------------------------------------------
const taskForm = document.getElementById('task-form' );
const confirmCloseDialog = document.getElementById('confirm-close-dialog');
const taskContainer = document.getElementById('tasks-container' );
const openTaskFormBtn = document.getElementById('open-task-form-btn' );
const closeTaskFormBtn = document.getElementById('close-task-form-btn' );
const cancelBtn = document.getElementById('cancel-btn' );
const discardBtn = document.getElementById('discard-btn' );
const titleInput = document.getElementById('title-input' );
const dateInput = document.getElementById('date-input' );
const descriptionInput = document.getElementById('description-input' );
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
//determines close button behaviour
let edited=false;
//determines whether a task should be added or edited
let newRecord=true;
//if a task is to be edited, this determines which one
let taskID=0;
//display each task when the page loads
let taskList = new Map(JSON.parse(localStorage.getItem("taskList")));
if (taskList === null) {taskList=new Map();}
taskList.forEach((value, key) => {drawTask(key)});
// events ---------------------------------------------------------------------------------------
//prevent page reload so that the task list doesn't have to get redrawn in full
document.querySelector("form").addEventListener("submit", (e)=>{
e.preventDefault();
})
//this event listener specifically targets the "Add New Task" button
openTaskFormBtn.addEventListener("click", ()=>{
taskForm.classList.remove("hidden");
edited=false;
newRecord=true;
addOrUpdateTaskBtn.innerText="Add New Task";
})
//set edited flag to true to trigger close button dialog
titleInput.addEventListener ("change", () => {edited=true;})
dateInput.addEventListener ("change", () => {edited=true;})
descriptionInput.addEventListener ("change", () => {edited=true;})
//close button behaviour. Either show the close dialog, or if no edits, skip to main page
closeTaskFormBtn.addEventListener("click", ()=>{
if (edited) {
confirmCloseDialog.show();
}
else {
resetTaskForm();
}
})
//no listener for cancel - just allow the dialog to close
discardBtn.addEventListener("click", ()=>{
resetTaskForm();
})
//add task
addOrUpdateTaskBtn.addEventListener("click", ()=>{
//error check
if (!titleInput.checkValidity() || !dateInput.checkValidity() || !descriptionInput.checkValidity()) {
return;
}
//get input values
const task = {
title: titleInput.value,
date: dateInput.value,
description: descriptionInput.value
};
//either push a new record, or just edit the existing task
if (newRecord) {
const tmp = Date.now();
taskList.set(tmp, task);
drawTask(tmp);
}
else {
taskList.set(taskID, task);
drawTask(taskID, false);
}
//update local storage and reset form
localStorage.setItem("taskList", JSON.stringify([...taskList]));
resetTaskForm();
})
// functions ---------------------------------------------------------------------------------------
//go back to task list
function resetTaskForm () {
//bypass HTML validation
titleInput.removeAttribute("required");
titleInput.value="";
titleInput.setAttribute("required", '');
dateInput.value="";
descriptionInput.removeAttribute("required");
descriptionInput.value="";
descriptionInput.setAttribute("required", '');
//hide the input form
taskForm.classList.add("hidden");
}
function drawTask (ID, newTask=true) {
//edit existing task (easy)
if (!newTask) {
document.getElementById(`title-${ID}`).innerText=taskList.get(ID).title;
document.getElementById(`date-${ID}`).innerText=taskList.get(ID).date;
document.getElementById(`description-${ID}`).innerText=taskList.get(ID).description;
return;
}
//create new task (a bit harder)
const taskElement = document.createElement('div');
taskElement.class="taskRecord";
taskElement.id=`taskRecord-${ID}`
//single task HTML
taskElement.innerHTML =
`<ul>
<li><b>Title: </b><span id="title-${ID}">${taskList.get(ID).title}</span></li>
<li><b>Date: </b><span id="date-${ID}">${taskList.get(ID).date}</span></li>
<li><b>Description: </b><span id="description-${ID}">${taskList.get(ID).description}</span></li>
</ul>
<button id="editTask-${ID}" class="btn">Edit</button>
<button id="deleteTask-${ID}" class="btn">Delete</button>`
//add it to the task container
taskContainer.appendChild(taskElement);
//put event listeners on the new buttons
document.getElementById(`editTask-${ID}`).addEventListener('click',()=>{
editTask(ID);
})
document.getElementById(`deleteTask-${ID}`).addEventListener('click',()=>{
deleteTask(ID);
})
}
function editTask(ID) {
//setup to pass back to addOrUpdateTaskBtn event
taskID=ID;
edited=false;
newRecord=false;
//initialise the new task form with task values
titleInput.value=taskList.get(ID).title;
dateInput.value = taskList.get(ID).date;
descriptionInput.value = taskList.get(ID).description;
//show the task form and update button text
taskForm.classList.remove("hidden");
addOrUpdateTaskBtn.innerText="Update Task";
}
function deleteTask(ID) {
taskList.delete(ID);
localStorage.setItem("taskList", JSON.stringify([...taskList]));
document.getElementById(`taskRecord-${ID}`).remove();
}
-
\$\begingroup\$ @Fe2O3 None of the code is AI generated. I asked the AI for how to improve the code, and implemented the suggestions. Save for very basic functionality (such as "How do I add an element to the start of an array?"), none of the code has been copied. \$\endgroup\$ApexPolenta– ApexPolenta2025年05月19日 02:03:40 +00:00Commented May 19 at 2:03
-
\$\begingroup\$ Ulta-trivial review comment (not worthy of being an answer): Noticing mix'n'match of double-quotes and apostrophes marking strings. Consistency inspires confidence. (Voting to reopen based on OP's edit stating code now works.) \$\endgroup\$user272752– user2727522025年05月20日 00:49:22 +00:00Commented May 20 at 0:49
1 Answer 1
Formatting
As stated in the question comments, your formatting is inconsistent.
const taskForm = document.getElementById('task-form' )
const addOrUpdateTaskBtn = document.getElementById("add-or-update-task-btn");
Please decide on using either single quotes or double quotes, and stick with it.
let edited=false;
Assuming you're not minifying your source code, the formatting standard is to have a space around operands.
let edited = false;
taskList.forEach((value, key) => {drawTask(key)});
Please place the body of the lambda on a separate line.
taskList.forEach((value, key) => {
drawTask(key)
});
This also applies to the body of if- and else-statements, loops, and event handlers. If you're writing this inside an IDE, most IDEs have a shortcut that fixes the formatting.
A style thing perhaps, and I know that some people, including the people behind FreeCodeCamp disagree with me, but all your calls to someElement.addEventListener(...)
are missing semicolons, for example
discardBtn.addEventListener("click", () => {
resetTaskForm();
}) // <-- here
As a beginner, I would recommend to always use semicolons, even in places where they can be omitted, in part also so you don't have to go on wild goose chases on why something throws a syntax error.
Naming
I'm a bit iffy about some of the variable names.
edited
should perhaps beformHasUserChangedValues
instead. In this application, it's simple enough, but in larger projects, it might better clarify things, particularly if a backend system is involved.newRecord
could perhaps beformIsNewRecord
instead.drawTask
should IMO be calledrenderTask
instead. You're not (strictly) drawing anything, just updating a few HTML attributes.tmp
(in the event handler foraddOrUpdateTaskBtn
) should benewID
instead.taskID
should beeditedTaskID
or somesuch instead.
Misc points
document.querySelector("form").addEventListener(...)
No need to do a querySelection here, you have that form inside a variable already:taskForm
- the AI suggestion is good, in particular because the way FreeCodeCamp handles the rendering (appending to
.innerHTML
) forces reparsing of existing elements for no reason, and can also be a nasty source of bugs (if the existing elements had event handlers added via.addEventHandler(...)
) - having the default value of
taskID
be zero with numerical task id's seems adventurous to me. Better to use a constant defined as-1
ornull
to denote the id is logically not set. Also,resetTaskForm()
should reset it. - You probably haven't learned about this yet, but you should apply
inert=""
to the task list while the form is open, to aid users with visual impairments. Learn localStorage by Building a Todo App
is a poor title, were this an actual Todo App. Perhaps something likeTodo App
instead.- Unless you're writing a custom error indicator, use
reportValidity()
overcheckValidity()
so as to not let users be confused on why the button isn't working. - FreeCodeCamp isn't consistent in their HTML attribute order, e.g.
<form class="task-form hidden" id="task-form">
versus<button id="add-or-update-task-btn" class="btn large-btn" type="button">
. In practice, you would pick an order and stick with it.
Explore related questions
See similar questions with these tags.