Beginning Web Development Project
Intro
- After going through the Beginning JavaScript tutorial, we will put the concepts into practice with a project. We will build a To-Do list using an HTML page to display the list, and add, edit or remove items with JavaScript. To-Do lists are often used as example applications since they are a simple design and can actually be used by the learner if they choose to do so.
- We will create two versions of the To-Do list project:
- A beginner level project called todo-list-beginning that uses concepts from the Beginning JavaScript tutorial. That is covered in this tutorial.
- An intermediate level project called todo-list-intermediate that uses intermediate level JavaScript concepts that you can do after completing the JavaScript tutorial series. That is covered in a separate tutorial.
- If you take out the empty lines then there are around 100 lines of JavaScript code, which is not too much but enough to demonstrate most of the concepts covered in the Beginning JavaScript tutorial.
- Start by downloading the finished code on GitHub at github.com/LearnByCheating/javascript-tutorial. This project contains all the example code for this Learn JavaScript tutorial series. The code for this project is in the todo-list-beginning folder.
- Launch the To-Do App project:
- Open the HTML file: To launch the project all you have to do is open the index.html file in the browser.
- Open the browser, click the file menu, then open the index.html file and it should open in your browser. Or just double click the index.html file in Mac Finder or Windows Explorer.
- Launch with Live Server: Alternatively, if you are using the VS Code text editor:
> Install the Live Server extension if it is not already installed.
> Open the index.html file.
> Right click in the editor and select "Open with Live Server". The index.html page will open in the browser.
- Play around with the app to see it's functionality before we dive into the code.
- The app is seeded with three To-Do list items.
- Clicking the item will mark it as done. Clicking it again will mark it as not done.
- There are three icons to the right. One for showing the To-Do item details, one to edit the item, and one to delete it.
- There is a button at the top to create a new To-Do item.
- The To-Do list does not persist. If you modify the list then restart the app, all changes will disappear. The Intermediate JavaScript project does persist the list items.
- Build the app: Now let's see how the app is built. You can either add a new folder and build your own version per the instructions below, or just follow along with the completed app. Either way is fine. The main thing is that you understand what each line of code is doing.
The HTML document
- Create a folder for the project. The completed code is in folder todo-list-beginning.
- Then add an HTML file to it called index.html and populate it with the below.
index.html
<!DOCTYPE html> <html lang="en"> <head> <title>ToDo App</title> <link rel="icon" type="image/x-icon" href="todo_icon_128x128.png"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css"> <script src="todos.js" defer></script> </head> <body class="bg-light container"> <div class="card my-3"> <h1 class="card-header bg-dark text-light d-flex align-items-center"> Todo List <button id="add-btn" class="btn btn-ls btn-outline-light ms-auto">Add Task</button> </h1> <form id="add-form" class="card-body container text-light bg-secondary d-none"> <div class="my-2"> <label for="task" class="form-label mb-0">Task</label> <input type="text" id="task" class="form-control"> </div> <div class="mb-3"> <label for="details" class="form-label mb-0">Details</label> <textarea id="details" class="form-control" rows="2"></textarea> </div> <div class="mb-3"> <button id="submit-btn" class="btn btn-outline-light col-2">Save</button> <button id="cancel-btn" class="btn btn-outline-light col-2 ms-2 float-end">Cancel</button> </div> </form> <ul id="list" class="list-group"></ul> </body> </html>
- If you are unfamiliar with HTML we will quickly cover the main things to know about the above HTML document.
- The document is divided into two main areas, the head and the body.
- The head contains:
- A link to a favicon image which is displayed on the browser tab.
<link rel="icon" type="image/x-icon" href="todo_icon_128x128.png">
- A link to the popular Bootstrap styling framework that has many CSS classes for styling our application. Our web page elements have class attributes with Bootstrap classes in them.
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
- A link to Bootstrap icons which we are using in our app.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css">
- And link to our JavaScript file which we will create next.
<script src="todos.js" defer>< /script>
- The body contains:
- The text and image content that gets displayed in the browser.
- The content is placed in HTML elements enclosed in brackets with a tagName, id and class attributes. Our page includes:
- An h1 (heading) element.
<h1...>Todo List...</h1>
- A form element holding a form to add a new To-Do item. It is not displayed.
<form id="add-form" class="... d-none">...</form>
- A ul (unordered list) element with id of "list". It has no content yet.
<ul id="list" class="list-group"></ul>
- The Bootstrap classes in the elements are mostly for styling. But the d-none class sets the display CSS property to none. The form element has class d-none so it is not displayed.
The JavaScript file
- Add a JavaScript file called todos.js to the todo-list-beginning folder.
- It will have three sections:
- Section 1: Seed the To-Do list with three initial To-Do items.
- Section 2: Form for adding new To-Do items
- Section 3: Edit or remove To-Do items.
Section 1: Seed the To-Do list with three initial To-Do items
- We will seed the To-Do list with three initial items. Populate the todos.js file with the below.
todos.js
const addBtn = document.getElementById('add-btn'); const addForm = document.getElementById('add-form');
const task = document.getElementById('task'); const details = document.getElementById('details'); const submitBtn = document.getElementById('submit-btn'); const cancelBtn = document.getElementById('cancel-btn'); const list = document.getElementById('list'); // Create list item. const createLi = (todo) => { let doneClass = ''; if (todo.done === true) { doneClass = ' text-decoration-line-through'; } const listItem = `<strong id="task-${todo.id}" class="task-elem${doneClass}" style="cursor: pointer;">${todo.task}</strong> <i class="remove-icon bi bi-x-circle-fill text-danger float-end" role="button"></i> <i class="edit-icon bi bi-pencil-square float-end mx-1 text-dark" role="button"></i> <i class="details-icon bi bi-info-circle-fill text-dark float-end ms-2" role="button"></i> <p id="details-${todo.id}" class="mb-0 d-none">${todo.details}</p>`; return listItem; }; let id; // Seed todos and display const seedTodos = () => { const todos = [ { id: 1, task: 'Create To-Do list using an object literal', details: 'After completing the Beginning JavaScript tutorial do the To-Do list project. Refer to the tutorial for reference.', done: false, }, { id: 2, task: 'Create To-Do list using a Class', details: 'After completing the To-Do list project, modify the JavaScript file to use a Todo Class instead of an object literal.', done: false, }, { id: 3, task: 'Final To-Do list project', details: 'After finishing the full JavaScript tutorial series, complete the To-Do list final version.', done: false, }, ]; // Populate list with To-Do items. let listItems = ''; todos.forEach((todo) => { listItems += `<li id="${todo.id}" class="list-group-item">${createLi(todo)}</li>`; }); list.innerHTML = listItems; id = todos.length; }; seedTodos();
- Let's break this down.
- Import list element: At the top import elements from the HTML document that we will be using in our script. It includes buttons and form elements which we will ignore for now. For seeding the list we only need to get the list element:
const list = document.getElementById('list');
- Seed the ToDo List: Seed the list with a few To-Do items:
let id; const seedTodos = () => { const todos = [ { id: 1, task: '...', details: '...', done: false, }, { id: 2, task: '...', details: '...', done: false, }, { id: 3, task: '...', details: '...', done: false, }, ]; let listItems = ''; todos.forEach((todo) => { listItems += `<li id="${todo.id}" class="list-group-item">${createLi(todo)}</li>`; }); list.innerHTML = listItems; id = todos.length; }; seedTodos();
- Define an arrow function named seedTodos:
const seedTodos = () => {...};
- Then call it:
seedTodos();
- In the function body declare a variable named todos set to an array of three To-Do items.
- Each todo is an object with four properties: an id number property, task and details string (i.e., text) properties, and a done boolean (i.e., true/false) property
- Convert the todos array into HTML list items and insert them into the HTML document.
- To do that we start by declaring a variable listItems set to an empty string:
let listItems = '';
- Then we iterate over the todos array with the forEach iterator:
todos.forEach((todo) => {...});
- We append each item to the listItems string using the += compound assignment operator. For each item we create an li (list item) HTML element as a string using template literal notation:
listItems += `<i id="${todo.id}" class="list-group-item">${createLi(todo)}< /li>`;
- Inside the interpolation we call the createTodo function passing in the todo object which we will explain below. It will return the inner HTML content for the li element which includes the task, details, and a set of icons for editing or deleting the To-Do item.
- When the forEach iterator finishes iterating through the array the listItems variable will contain the HTML for all the To-Do list items. We assign the list to the innerHTML of the list element:
list.innerHTML = listItems;
- There are a couple of lines that declare the id variable:
let id;
- And initialize it to the length of the todos array:
id = todos.length;
- We will use the id variable when creating a new To-Do items. We will get to that in the next section.
- CreateLi function:
const createLi = (todo) => { let doneClass = ''; if (todo.done === true) { doneClass = ' text-decoration-line-through'; } const listItem = `<strong id="task-${todo.id}" class="task-elem${doneClass}" style="cursor: pointer;">${todo.task}</strong> <i class="remove-icon bi bi-x-circle-fill text-danger float-end" role="button"></i> <i class="edit-icon bi bi-pencil-square float-end mx-1 text-dark" role="button"></i> <i class="details-icon bi bi-info-circle-fill text-dark float-end ms-2" role="button"></i> <p id="details-${todo.id}" class="mb-0 d-none">${todo.details}</p>`; return listItem; };
- This function returns the inner HTML of the list item. The function takes in the todo object which contains id, task, and details properties.
- At the top of the function body we get the doneClass. If the done property is true, we set the class to ' text-decoration-line-through'. That will put a line through the text of the To-Do task if it is done.
- Then we create the listItem HTML string. The li item for a To-Do contains:
- The task: A strong HTML element for the task. Strong makes the text bold. The id is "task-" plus the id number. It has a class text-decoration-line-through if the task is done. We used template literal syntax so we can insert the todo.task value in the HTML string. Template literals are enclosed in back ticks and JavaScript expressions are enclosed in ${...}:
`<strong ... >${todo.task}</strong>...`
- Icon buttons: There are a series of icons that will be used as buttons (the buttons will be explained later). The icons are provided by Bootstrap icons. We use i elements with classes that indicate what icon image to use. There are three icons.
- A remove icon that will remove the To-Do item when clicked.
- An edit icon that when clicked will open a form to edit the To-Do item.
- A details icon that will toggle between show and hide the task details when clicked.
- The first section of this app is complete. When you launch the app with Live Server it will open the web page and seed it with the three To-Do list items. Next we will add the code to add a new To-Do item.
Section 2: Form for adding new To-Do items
- The HTML document has a hidden form to add a new To-Do item:
index.html
<button id="add-btn" class="btn btn-ls btn-outline-light ms-auto">Add Task</button> <form id="add-form" class="card-body container text-light bg-secondary d-none"> <div class="my-2"> <label for="task" class="form-label mb-0">Task</label> <input type="text" id="task" class="form-control"> </div> <div class="mb-3"> <label for="details" class="form-label mb-0">Details</label> <textarea id="details" class="form-control" rows="2"></textarea> </div> <div class="mb-3"> <button id="submit-btn" type="submit" class="btn btn-outline-light col-2">Save</button> <button id="cancel-btn" class="btn btn-outline-light col-2 ms-2 float-end">Cancel</button> </div> </form>
- The form is enclosed in a form element. It has id "add-form" and class "d-none" which hides the form.
- The form has two fields which are text input elements with ids "task" and "details".
- At the bottom are two button elements with ids "submit-btn" and "cancel-btn".
- Add the below JavaScript to the todos.js file.
- Get the button and form field elements at the top of the file (we added these in the last section).
- Add the three functions that handle the button clicks to the bottom of the file.
todos.js
const addBtn = document.getElementById('add-btn'); const addForm = document.getElementById('add-form'); const task = document.getElementById('task'); const details = document.getElementById('details'); const submitBtn = document.getElementById('submit-btn'); const cancelBtn = document.getElementById('cancel-btn'); ... // Display form to add a To-Do item. addBtn.addEventListener('click', () => { addForm.classList.remove('d-none'); }); // Hide form when cancel button is clicked. cancelBtn.addEventListener('click', () => { task.value = ''; details.value = ''; addForm.classList.add('d-none'); }); // Handle Add Todo form submission. submitBtn.addEventListener('click', (event) => { event.preventDefault(); id += 1; const newTodo = { id: id, task: task.value, details: details.value, done: false }; const newLi = `<li id="${newTodo.id}" class="list-group-item">${createLi(newTodo)}</li>`; list.innerHTML += newLi; task.value = ''; details.value = ''; addForm.classList.add('d-none'); });
- In this section add the three event listener methods listening for the "click" event on the add button (addBtn), cancel button (cancelBtn), and submit button (submitBtn).
- AddEventListener: Chain the addEventListener to the button elements. It takes two arguments: the event which in this case is "click", and the handler function that performs whatever task is supposed to be done.
- Add button:
- Listen for the click event on the addBtn element:
addBtn.addEventListener('click', handlerFunction);
- Handler function: When clicked the handler function removes the d-none (display: none) class from the addForm. That will cause the Add To-Do Form to be displayed on the web page.
() => { addForm.classList.remove('d-none'); }
- Cancel button:
- Listen for the click event on the cancelBtn element:
cancelBtn.addEventListener('click', handlerFunction);
- Handler function: When clicked the handler function clears the values from the task and details form fields, and adds the d-none (display: none) class to the form which hides it.
() => { task.value = '';
details.value = '';
addForm.classList.add('d-none'); }
- Submit button:
- Listen for the click event on the submitBtn element:
submitBtn.addEventListener('click', handlerFunction);
- Handler function: If the user fills out the form and clicks the submit button, the handler function gets invoked.
submitBtn.addEventListener('click', (event) => { event.preventDefault(); id += 1; const newTodo = { id: id, task: task.value, details: details.value, done: false }; const newLi = `<li id="${newTodo.id}" class="list-group-item">${createLi(newTodo)}</li>`; list.innerHTML += newLi; task.value = ''; details.value = ''; addForm.classList.add('d-none'); });
- Pass in the event as the function's argument.
(event) => {...});
- When a submit button is clicked in an HTML form the default behavior is for the browser to submit the form data to the server. We don't want that in our app. We want to handle the from submission with our JavaScript. So we need to chain the preventDefault() method to the event object.
event.preventDefault();
- Each To-Do item will have a unique id number. We set an id variable when we seeded the list, and set the value to the number of items in the list. So right now id is 3. Every time we create a new To-Do item, we add 1 to the id value.
id += 1;
- Set a todo variable to an object with properties for id, task, details, and done. We got the values for task and details from the form. Done starts out as false.
const newTodo = { id: id, task: task.value, details: details.value, done: false };
- Set variable newLi to the li (list item) element as a string. Use the template literal syntax so we can insert the todo.id value and call the createLi() function passing in the todo object as the argument. The createLi function was defined in the last section for seeding To-Do items. It returns the HTML content of the list item.
const newLi = `<li id="${newTodo.id}" class="list-group-item">${createLi(newTodo)}</li>`;
- Use the compound assignment operator += to add the li (list item) to the end of the list's innerHTML.
list.innerHTML += newLi;
- Now that the form data has been added to the list, clear the from field values.
task.value = ''; details.value = '';
- Finally, hide the form again by adding the d-none class back to the form element.
addForm.classList.add('d-none');
Section 3: Edit or remove To-Do items
- Each To-Do list item is enclosed in li (list item) tags.
- Below is an example of the HTML for To-Do item 3.
<li id="3" class="list-group-item">
<strong id="task-3" class="task-elem" style="cursor: pointer;">Task text.</strong>
<i class="remove-icon bi bi-x-circle-fill text-danger float-end" role="button"></i>
<i class="edit-icon bi bi-pencil-square float-end mx-1 text-dark" role="button"></i>
<i class="details-icon bi bi-info-circle-fill text-dark float-end ms-2" role="button"></i>
<p id="details-3" class="mb-0 d-none">Details text.</p>
</li>
- The li (list item) element's id is the To-Do object's id property.
- Each li element contains five HTML elements:
- A strong (bold) element that contains the task text value. It has class task-elem .
- Three icons used as buttons with ids: details-icon, edit-icon, and remove-icon.
- A p (paragraph) element that contains the To-Do item details. It has class d-none so it is not displayed, and id "details-idNumber". In the above example the id would be "details-3".
- Generally, classes are only used for styling, but here we are using classes to identify what we are using the element for.
- Add the below JavaScript to the bottom of the todos.js file:
todos.js
... let todo; // Create the edit form. const createEdit = (todo) => { const editForm = `<div class="form-floating mb-2"> <input type="text" id="task-${todo.id}" class="form-control mb-2" value="${todo.task}"> <label for="task">Task:</label> </div> <div class="form-floating mb-2"> <textarea id="details-${todo.id}" class="form-control" style="height: 80px">${todo.details}</textarea> <label for="details">Details:</label> </div> <button class="edit-submit-btn btn btn-sm btn-outline-light col-md-2">Save Edit</button> <button class="edit-cancel-btn btn btn-sm btn-outline-light col-md-2 float-end">Cancel</button>`; return editForm; }; // Handle clicks on todo items. list.addEventListener('click', (event) => { const li = event.target.parentElement; const id = li.id; // Handle Done if (event.target.classList.contains('task-elem')) { event.target.classList.toggle('text-decoration-line-through'); // Handle toggle details } else if (event.target.classList.contains('details-icon')) { const details = document.getElementById(`details-${id}`); details.classList.toggle('d-none'); // Handle Edit } else if (event.target.classList.contains('edit-icon')) { let task = document.getElementById(`task-${id}`); task = task.textContent; let details = document.getElementById(`details-${id}`); details = details.textContent; let done = document.getElementById(`task-${id}`); done = done.classList.contains('text-decoration-line-through'); todo = { id, task, details, done }; li.classList.add('bg-secondary'); li.innerHTML = createEdit(todo); // Handle submit edit form. } else if (event.target.classList.contains('edit-submit-btn')) { todo.task = document.getElementById(`task-${id}`).value; todo.details = document.getElementById(`details-${id}`).value; li.classList.remove('bg-secondary'); li.innerHTML = createLi(todo); todo = {}; // Handle cancel edit } else if (event.target.classList.contains('edit-cancel-btn')) { li.classList.remove('bg-secondary'); li.innerHTML = createLi(todo); todo = {}; // Handle remove todo } else if (event.target.classList.contains('remove-icon')) { li.remove(); } });
- This entire section of code handles clicks on a To-Do item.
- Clicking on the task text will toggle between mark it as done by putting a line through it and removing the line.
- Clicking the Details icon will toggle between showing the To-Do item details and hiding them.
- Clicking the Edit icon will open an edit form right on the To-Do item itself.
- Clicking the edit cancel button will close the edit form.
- Filling out the form and clicking the edit submit button will submit the form.
- Clicking the Remove icon will remove the To-Do item from the list.
- We have a listener function on the entire list, listening for a click event anywhere on the list.
list.addEventListener('click', (event) => {...});
The handler function:
(event) => {...}
- The handler function executes when the "click" event is triggered. It first sets the li and id variables, then has a series of if statements that determine which target element was clicked, then handle it based on that.
- Set the li and id variables:
- event.target is the HTML element that was clicked.
- When the user clicks anywhere in the list element, the event.target is the exact element that was clicked.
- event.target.parentElement is the parent of the element that was clicked which will be an li (list item). Event.target and parentElement were not covered in the Beginning JavaScript section, but are covered in the JavaScript DOM tutorial.
- Get the event target's parent element and set it to variable li.
const li = event.target.parentElement;
- Get the list item's id and assign it to variable id.
const id = li.id;
- Mark as Done: If the user clicks the task element (contains class "task-elem"), mark it as done or not done.
- We check if the click was on an element that contains the "task-elem" class. If it does we toggle (add or remove) the class that puts a line through the task element.
if (event.target.classList.contains('task-elem')) {
event.target.classList.toggle('text-decoration-line-through');
}
- Show/Hide Details:
If the user clicks the details icon (contains class "details-icon"), show or hide the details.
- Check if the target element was an element that contains the "details-icon" class. If so, get the element with id="details-id", with id being the id of the list item. Then toggle the class d-none (display: none). That will show or hide the details for that item.
else if (e.target.classList.contains('details-icon')) {
const details = document.getElementById(`details-${id}`);
details.classList.toggle('d-none');
}
- Open the Edit form:
If the user clicks the edit icon (contains class "edit-icon"), change the To-Do item to an edit form.
- Check if the target element was an element that contains the "edit-icon" class. If so, change the li elemnt to an edit form.
else if (event.target.classList.contains('edit-icon')) { let task = document.getElementById(`task-${id}`); task = task.textContent; let details = document.getElementById(`details-${id}`); details = details.textContent; let done = document.getElementById(`task-${id}`); done = done.classList.contains('text-decoration-line-through'); todo = { id, task, details, done }; li.innerHTML = createEdit(todo); li.classList.add('bg-secondary'); }
- Get the task and details textContent.
- Get the done true or false value based on whether the task element as the "text-decoration-line-through" class.
- The todo variable was declared earlier:
let todo;
- Set the todo variable to an object literal, setting the properties to the values from the list item.
todo = { id, task, details, done };
- This is shorthand for
todo = { id: id , task: task, details: details, done: done };
. You can use this shorthand if the property name and value variable are the same. - Replace the li list item with the edit form. Set the li (list item) innerHTML to the result returned from calling the createTodo(todo) function. Pass in the updated todo object as the argument.
li.innerHTML = createEdit(todo);
- The createEdit function returns the edit form HTML.
const createEdit = (todo) => {
const editForm = `<div class="form-floating mb-2">
<input type="text" id="task-${todo.id}" class="form-control mb-2" value="${todo.task}">
<label for="task">Task:</label>
</div>
<div class="form-floating mb-2">
<textarea id="details-${todo.id}" class="form-control" style="height: 80px">${todo.details}</textarea>
<label for="details">Details:</label>
</div>
<button class="edit-submit-btn btn btn-sm btn-outline-light col-md-2">Save Edit</button>
<button class="edit-cancel-btn btn btn-sm btn-outline-light col-md-2 float-end">Cancel</button>`;
return editForm;
};
- The form is populated with the existing task and details values.
- At the bottom are save and cancel buttons.
- Add the "bg-secondary" class to the li (list item) element. That turns the background grey.
li.classList.add('bg-secondary');
- The edit form is now open and populated with the list item values.
- Handle the edit submit: Update the To-Do item with the new data when the form is submitted.
else if (event.target.classList.contains('edit-submit-btn')) { todo.task = document.getElementById(`task-${id}`).value; todo.details = document.getElementById(`details-${id}`).value; li.innerHTML = createLi(todo); li.classList.remove('bg-secondary'); todo = {};
}- Check if the target element is the edit form's submit button.
else if (event.target.classList.contains('edit-submit-btn')) {...}
- If so, get the task and details values from the form and assign the values to the todo object.
todo.task = document.getElementById(`task-${id}`).value;
todo.details = document.getElementById(`details-${id}`).value;
- Set the li (list item) innerHTML to the updated values. Call the createLi() function defined earlier. Pass in the todo object as the argument. It will return the HTML string.
li.innerHTML = createLi(todo);
- Remove the bg-secondary class to remove the grey background color.
li.classList.remove('bg-secondary');
- Reset the todo variable to an empty object.
todo = {};
- Handle cancel edit form: If the user clicks the cancel button on the edit form, close the form and put the list item back to the way it was.
else if (e.target.classList.contains('edit-cancel-btn')) {
li.classList.remove('bg-secondary');
li.innerHTML = createLi(todo);
todo = {};
}- Check if the target element is the edit form's cancel button.
else if (e.target.classList.contains('edit-cancel-btn')) {...}
- If so remove the bg-secondary class from the li element. That will remove the grey background.
li.classList.remove('bg-secondary');
- Set the li element back to the list item by calling the createLi() function passing in the todo object as the argument.
li.innerHTML = createLi(todo);
- Lastly, set the todo variable to an empty object to clear it out.
todo = {};
- The edit form is now closed and the list item is back the way it was.
- Remove a To-Do item:
If the user clicks the remove icon, delete the item from the list.
else if (event.target.classList.contains('remove-icon')) {
li.remove();
}- Check if the target element was the remove icon. If so, remove the li list item.
- The To-Do list application is now complete.
Change object literals to classes
- Before we finish the Beginning JavaScript To-Do list project, let's do one last thing. Instead of creating new object literals for each To-Do item, let's make a Todo class.
- A class lets you create as many objects as you want that have the same properties. It is like a blueprint for objects.
- This requires just a few changes.
- We need to create the class.
class Todo {
constructor(id, task, details, done) {
this.id = id;
this.task = task;
this.details = details;
this.done = done;
}
}
- This simple class consists of a constructor function where we pass in the property values as arguments and assign them to the object properties.
- The keyword this represents the object that is being created.
- A class is a special type of function. To instantiate a new object you call the class with the new keyword in front. Pass in the property values as arguments. Assign the result to a variable.
const newTodo = new Todo(1, "Learn JavaScript Classes", "Put details here", true);
- The above call to the Todo class will instantiate a new object and return:
{ id: 1, task: 'Learn JavaScript Classes', details: 'Put details here', done: true }
- To change the todos.js script to use the Todo class you need to make changes in five places:
- Add the Todo class.
- Instantiate a new Todo instance inside the submitBtn handler function to create a new To-Do item:
- Replace:
const newTodo = { id: id, task: task.value, details: details.value, done: false };
- With:
const newTodo = new Todo(id, task.value, details.value, false);
- Instantiate a new Todo instance when the Edit form is displayed.
- Replace:
todo = { id, task, details, done };
- With:
todo = new Todo(id, task, details, done);
- When the edit form is submitted reset the todo variable to null instead of to an empty object:
- Replace:
todo = {};
- With:
todo = null;
- And the same when the edit form is cancelled.
- Replace:
todo = {};
- With:
todo = null;
- Below is the entire script file with the changes in bold.
todos-2.js
const addBtn = document.getElementById('add-btn'); const addForm = document.getElementById('add-form'); const task = document.getElementById('task'); const details = document.getElementById('details'); const submitBtn = document.getElementById('submit-btn'); const cancelBtn = document.getElementById('cancel-btn'); const list = document.getElementById('list'); // Create list item. const createLi = (todo) => { const listItem = `<strong id="task-${todo.id}" class="task-elem${todo.done ? ' text-decoration-line-through' : ''}" style="cursor: pointer;">${todo.task}</strong> <i class="remove-icon bi bi-x-circle-fill text-danger float-end" role="button"></i> <i class="edit-icon bi bi-pencil-square float-end mx-1 text-dark" role="button"></i> <i class="details-icon bi bi-info-circle-fill text-dark float-end ms-2" role="button"></i> <p id="details-${todo.id}" class="mb-0 d-none">${todo.details}</p>`; return listItem; }; // Set id to 0 let id; // Seed todos and display const setTodos = () => { const todos = [ { id: 1, task: 'Create To-Do list using an object literal', details: 'After completing the Beginning JavaScript tutorial do the To-Do list project. Refer to the tutorial for reference.', done: true, }, { id: 2, task: 'Create To-Do list using a Class', details: 'After completing the To-Do list project, modify the JavaScript file to use a Todo Class instead of an object literal.', done: false, }, { id: 3, task: 'Final To-Do list project', details: 'After finishing the full JavaScript tutorial series, complete the To-Do list final version.', done: false, }, ]; // Populate list with To-Do items. let listItems = ''; todos.forEach((todo) => { listItems += `<li id="${todo.id}" class="list-group-item">${createLi(todo)}</li>`; }); list.innerHTML = listItems; id = todos.length; }; setTodos(); // Display form to add a To-Do item. addBtn.addEventListener('click', () => { addForm.classList.remove('d-none'); }); // Hide form when cancel button is clicked. cancelBtn.addEventListener('click', () => { task.value = ''; details.value = ''; addForm.classList.add('d-none'); }); class Todo { constructor(id, task, details, done) { this.id = id; this.task = task; this.details = details; this.done = done; } } // Handle Add Todo form submission. submitBtn.addEventListener('click', (e) => { e.preventDefault(); id += 1; const newTodo = new Todo(id, task.value, details.value, false);
const newLi = `<li id="${newTodo.id}" class="list-group-item">${createLi(newTodo)}</li>`; list.innerHTML += newLi; task.value = ''; details.value = ''; addForm.classList.add('d-none'); }); let todo; // Function returns edit form populated with existing data. const createEdit = (todo) => { const editForm = `<div class="form-floating mb-2"> <input type="text" id="task-${todo.id}" class="form-control mb-2" value="${todo.task}"> <label for="task">Task:</label> </div> <div class="form-floating mb-2"> <textarea id="details-${todo.id}" class="form-control" style="height: 80px">${todo.details}</textarea> <label for="details">Details:</label> </div> <button class="edit-submit-btn btn btn-sm btn-outline-light col-md-2">Save Edit</button> <button class="edit-cancel-btn btn btn-sm btn-outline-light col-md-2 float-end">Cancel</button>`; return editForm; }; // Handle clicks on todo items. list.addEventListener('click', (e) => { const li = e.target.parentElement; const id = li.id; // Handle Done if (e.target.classList.contains('task-elem')) { e.target.classList.toggle('text-decoration-line-through'); // Handle toggle details } else if (e.target.classList.contains('details-icon')) { const details = document.getElementById(`details-${id}`); details.classList.toggle('d-none'); // Handle Edit } else if (e.target.classList.contains('edit-icon')) { let task = document.getElementById(`task-${id}`); task = task.textContent; let details = document.getElementById(`details-${id}`); details = details.textContent; let done = document.getElementById(`task-${id}`); done = done.classList.contains('text-decoration-line-through'); todo = new Todo(id, task, details, done); li.innerHTML = createEdit(todo); li.classList.add('bg-secondary'); // Handle submit edit form. } else if (e.target.classList.contains('edit-submit-btn')) { todo.task = document.getElementById(`task-${id}`).value; todo.details = document.getElementById(`details-${id}`).value; li.innerHTML = createLi(todo); li.classList.remove('bg-secondary'); todo = null; // Handle cancel edit form } else if (e.target.classList.contains('edit-cancel-btn')) { li.classList.remove('bg-secondary'); li.innerHTML = createLi(todo); todo = null; // Handle remove todo } else if (e.target.classList.contains('remove-icon')) { li.remove(); } });
Conclusion
- Make sure you have a good understanding of both the Beginning JavaScript tutorial and this project before moving on to the Intermediate JavaScript tutorial series.
- There is another version of this project called todo-list-intermediate. It uses the same base code as this app, but incorporates concepts from the intermediate JavaScript tutorial series such as modules, local storage, JSON, errors, regular expressions and more. Do that project after finishing the intermediate JavaScript tutorial series.