- 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:
- The taskList array has been changed to a Map and changes have been propagated through the rest of the functions.
- The submit button has been changed to just a button, and validation is now done explicitly in JS.
- 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:
- The taskList array has been changed to a Map and changes have been propagated through the rest of the functions.
- The submit button has been changed to just a button, and validation is now done explicitly in JS.
- 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();
}
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:
- The taskList array has been changed to a Map and changes have been propagated through the rest of the functions.
- The submit button has been changed to just a button, and validation is now done explicitly in JS.
- 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:
- The taskList array has been changed to a Map and changes have been propagated through the rest of the functions.
- The submit button has been changed to just a button, and validation is now done explicitly in JS.
- 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();
}
```