Skip to main content
Code Review

Return to Question

edited tags
Link
Post Reopened by user272752, Toby Speight, Dan Oberlam
Clarify that this code is really the author's; remove edit history section
Source Link
Toby Speight
  • 87.9k
  • 14
  • 104
  • 325

I'm writing JS for the FreeCodeCamp todo list. Thelist; the corresponding HTML and CSS of the projectfor this can be found on their page, heretheir 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 offroadoff-road by coding in another environment with no guidance (except questions, except questions to Google's AI).

I want to double check that a specific AI suggestion was a good idea. 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.

Edit 20/5/2025: Fixed the errors spotted by Jannik:

  1. The taskList array has been changed to a Map and changes have been propagated through the rest of the functions.
  2. The submit button has been changed to just a button, and validation is now done explicitly in JS.
  3. Adding a new task now resets the button text.

Apologies forI want to double check that a specific AI suggestion was a good idea (N.B. I have not spotting these errors in testing earlierused 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();
}
```

I'm writing JS for the FreeCodeCamp todo list. The HTML and CSS of the project can be found on their page, here. 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 offroad 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. 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.

Edit 20/5/2025: Fixed the errors spotted by Jannik:

  1. The taskList array has been changed to a Map and changes have been propagated through the rest of the functions.
  2. The submit button has been changed to just a button, and validation is now done explicitly in JS.
  3. Adding a new task now resets the button text.

Apologies for not spotting these errors in testing earlier.

// 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();
}
```

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();
}
Fixed the bugs raised by Jannik
Added to review
Source Link

I'm writing JS for the FreeCodeCamp todo list. The HTML and CSS of the project can be found on their page, here. I have made one edit to the HTML - the button with type "submit" has been changed to "button".

I also have a question about a specific error. When I clickEdit 20/5/2025: Fixed the "Add Task" button, an error appears saying "An invalid form control with name='' iserrors spotted by Jannik:

  1. The taskList array has been changed to a Map and changes have been propagated through the rest of the functions.
  2. The submit button has been changed to just a button, and validation is now done explicitly in JS.
  3. Adding a new task now resets the button text.

Apologies for not focusable." What is the source of this error? As far as I can tell, none of the controlsspotting these errors in the JS or HTML have a blank nametesting earlier.

// 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 taskIndex=0;taskID=0;
//display each task when the page loads
let taskList = new Map(JSON.parse(localStorage.getItem("taskList")));
if (taskList === null) {taskList=[]}
fortaskList=new Map(let i=0; i<taskList);}
taskList.length;forEach((value, i++key) => {drawTask(ikey)});
// 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.pushset(tmp, task);
 drawTask(taskList.length-1tmp);
 }
 else {
 taskList[taskIndex] =taskList.set(taskID, task;task);
 drawTask(taskIndextaskID, false);
 }
 //update local storage and reset form
 localStorage.setItem("taskList", JSON.stringify(taskList[...taskList]));
 resetTaskForm();
})
// functions ---------------------------------------------------------------------------------------
//go back to task list
function resetTaskForm () {
 //attempted bypass of HTML verification (doesn't work)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 (indexID, newTask=true) {
 //edit existing task (easy)
 if (!newTask) {
 document.getElementById(`title-${indexID}`).innerText=taskList[index]innerText=taskList.get(ID).title;
 document.getElementById(`date-${indexID}`).innerText=taskList[index]innerText=taskList.get(ID).date;
 document.getElementById(`description-${indexID}`).innerText=taskList[index]innerText=taskList.get(ID).description;
 return;
 }
 //create new task (a bit harder)
 const taskElement = document.createElement('div');
 taskElement.class="taskRecord";
 taskElement.id=`taskRecord-${indexID}`
 //single task HTML
 taskElement.innerHTML =
 `<ul>
 <li><b>Title: </b><span id="title-${indexID}">${taskList[index]taskList.get(ID).title}</span></li>
 <li><b>Date: </b><span id="date-${indexID}">${taskList[index]taskList.get(ID).date}</span></li>
 <li><b>Description: </b><span id="description-${indexID}">${taskList[index]taskList.get(ID).description}</span></li>
 </ul>
 <button id="editTask-${indexID}" class="btn">Edit</button>
 <button id="deleteTask-${indexID}" class="btn">Delete</button>`
 //add it to the task container
 taskContainer.appendChild(taskElement);
 //put event listeners on the new buttons
 document.getElementById(`editTask-${indexID}`).addEventListener('click',()=>{
 editTask(indexID);
 })
 document.getElementById(`deleteTask-${indexID}`).addEventListener('click',()=>{
 deleteTask(indexID);
 })
 
}
function editTask(indexID) {
 taskIndex=index;//setup to pass back to addOrUpdateTaskBtn event
 taskID=ID;
 edited=false;
 newRecord=false;

 //initialise the new task form with task values
  titleInput.value=taskList[index]value=taskList.get(ID).title;
 dateInput.value = taskList[index]taskList.get(ID).date;
 descriptionInput.value = taskList[index]taskList.get(ID).description;
 //show the task form and update button text
 taskForm.classList.remove("hidden"); 
 addOrUpdateTaskBtn.innerText="Update Task";
}
function deleteTask(indexID) {
 taskList.splicedelete(index, 1ID);
 localStorage.setItem("taskList", JSON.stringify(taskList[...taskList]));
 document.getElementById(`taskRecord-${indexID}`).remove();
}
```

I'm writing JS for the FreeCodeCamp todo list. The HTML and CSS of the project can be found on their page, here.

I also have a question about a specific error. When I click the "Add Task" button, an error appears saying "An invalid form control with name='' is not focusable." What is the source of this error? As far as I can tell, none of the controls in the JS or HTML have a blank name.

// 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 taskIndex=0;
//display each task when the page loads
let taskList = JSON.parse(localStorage.getItem("taskList"));
if (taskList === null) {taskList=[]}
for (let i=0; i<taskList.length; i++) {drawTask(i)}
// 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;
})
//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", ()=>{
 //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) {
 taskList.push(task);
 drawTask(taskList.length-1);
 }
 else {
 taskList[taskIndex] = task;
 drawTask(taskIndex, false);
 }
 //update local storage and reset form
 localStorage.setItem("taskList", JSON.stringify(taskList));
 resetTaskForm();
})
// functions ---------------------------------------------------------------------------------------
//go back to task list
function resetTaskForm () {
 //attempted bypass of HTML verification (doesn't work)
 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 (index, newTask=true) {
 //edit existing task (easy)
 if (!newTask) {
 document.getElementById(`title-${index}`).innerText=taskList[index].title;
 document.getElementById(`date-${index}`).innerText=taskList[index].date;
 document.getElementById(`description-${index}`).innerText=taskList[index].description;
 return;
 }
 //create new task (a bit harder)
 const taskElement = document.createElement('div');
 taskElement.class="taskRecord";
 taskElement.id=`taskRecord-${index}`
 //single task HTML
 taskElement.innerHTML =
 `<ul>
 <li><b>Title: </b><span id="title-${index}">${taskList[index].title}</span></li>
 <li><b>Date: </b><span id="date-${index}">${taskList[index].date}</span></li>
 <li><b>Description: </b><span id="description-${index}">${taskList[index].description}</span></li>
 </ul>
 <button id="editTask-${index}" class="btn">Edit</button>
 <button id="deleteTask-${index}" class="btn">Delete</button>`
 //add it to the task container
 taskContainer.appendChild(taskElement);
 //put event listeners on the new buttons
 document.getElementById(`editTask-${index}`).addEventListener('click',()=>{
 editTask(index);
 })
 document.getElementById(`deleteTask-${index}`).addEventListener('click',()=>{
 deleteTask(index);
 })
 
}
function editTask(index) {
 taskIndex=index;
 edited=false;
 newRecord=false;
 titleInput.value=taskList[index].title;
 dateInput.value = taskList[index].date;
 descriptionInput.value = taskList[index].description;
 taskForm.classList.remove("hidden"); 
 addOrUpdateTaskBtn.innerText="Update Task";
}
function deleteTask(index) {
 taskList.splice(index, 1);
 localStorage.setItem("taskList", JSON.stringify(taskList));
 document.getElementById(`taskRecord-${index}`).remove();
}

I'm writing JS for the FreeCodeCamp todo list. The HTML and CSS of the project can be found on their page, here. I have made one edit to the HTML - the button with type "submit" has been changed to "button".

Edit 20/5/2025: Fixed the errors spotted by Jannik:

  1. The taskList array has been changed to a Map and changes have been propagated through the rest of the functions.
  2. The submit button has been changed to just a button, and validation is now done explicitly in JS.
  3. Adding a new task now resets the button text.

Apologies for not spotting these errors in testing earlier.

// 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();
}
```
Post Closed as "Not suitable for this site" by user272752, Sᴀᴍ Onᴇᴌᴀ
added 2 characters in body
Source Link
toolic
  • 15.2k
  • 5
  • 29
  • 213
Loading
Source Link
Loading
default

AltStyle によって変換されたページ (->オリジナル) /