In this task, we will implement the core functionality of our ToDo app with a focus on structured and procedural programming. These programming paradigms aim to improve the clarity, quality, and development time of a computer program by making extensive use of subroutines (also known as procedures or functions), block structures, loops for iteration, and conditional statements for decision-making.
Step 1: Initialize the State
Before we start manipulating the DOM, we need to initialize the state of our application. To that end, we’ll use an array to store our todos and a few other variables to manage the app’s state.
In the main.js file, add the following code:
Notice each todo is an object with three properties: id, text, and completed. The id property is a unique identifier for each todo, the text property contains the description of the todo, and the completed property is a boolean that indicates whether the todo is done or not.
The nextTodoId variable is used to generate unique id values for new todos. We start with 4 because we already have three todos in the initial state.
Step 2: Render the Todos
We’ll create a function to render the todos based on the current state.
At a high level, this function does the following:
Clear the existing list of todos by setting the innerHTML of the todoListElement to an empty string. This ensures that we start with a clean slate each time we render the todos.
Loop through the todos and create a new div element for each todo. This element contains the todo text and an input field for editing it based on the samples we have in the index.html file.
Step 3: Initialize the App on Page Load
Instead of directly calling the renderTodos function, we’ll bind it to an event listener that triggers when the entire content of the page is loaded.
This approach ensures that our app initializes only after all the DOM elements are available. This concept will be especially beneficial when you delve into frameworks like React, where understanding component lifecycle methods and their relation to the DOM is crucial.
Run the app and check if the todos are rendered correctly on the page.
Step 4: Handle “Adding a new todo”
When the user types a new todo and presses Enter, we’ll add it to the list. To implement this functionality, we’ll create a function that listens for the keydown event on the input field.
Let’s break down the handleNewTodoKeyDown function:
Event Parameter: The function accepts an event parameter, which is automatically passed by the browser when an event listener triggers this function. This event object contains information about the event, such as which element was clicked.
Extracting the Input Value:
We first extract the input element (newTodoInput) from the event object using event.target. We then retrieve the value of the input field and remove any leading or trailing whitespace using the trim() method. This ensures that we don’t add todos that are just empty spaces.
Checking the Key Pressed:
We check if the key pressed by the user is the ‘Enter’ key (event.key === 'Enter') and if the input field is not empty (todoText !== ''). This condition ensures that we only add a new todo when the user has entered some text and pressed the ‘Enter’ key.
Adding the New Todo:
If the conditions are met, we add a new todo to the todos array. We generate a unique id for the new todo using nextTodoId++, set the text property to the trimmed input value, and initialize the completed property to false.
Clearing the Input Field:
Then, we clear the input field to prepare it for the next todo entry.
Re-rendering the Todos:
Finally, we call the renderTodos function to update the displayed todos with the new addition.
Note that every time we change the state of the app, we must call renderTodos to update the UI. One of the advantages of using a UI library like React is that it takes care of these changes automatically (and efficiently), allowing you to focus on managing the state. But more on that in future chapters!
Step 5: Styling completed todos
We’ll add a line-through style to the text of completed todos to visually distinguish them from active todos. You must update the renderTodos function to apply this style based on the completed property of each todo.
Run the app and check if the todos are rendered correctly on the page.
Step 6: Selecting a filter
The anchor elements in the navbar act as filters to display all todos, active todos, or completed todos. We’ll add an event listener to the navbar and create a function to handle the filter selection.
Let’s break down the handleClickOnNavbar function:
Checking the Clicked Element:
We check if the clicked element is an anchor tag (<a>). This condition ensures that the function only runs when the user clicks on one of the filter links.
Extracting the Filter Action:
We extract the href attribute of the clicked anchor element (event.target.href) to get the URL. We then split the URL by the / character and extract the last part of the URL using pop(). This part corresponds to the filter action (e.g., all, active, or completed). If the action is an empty string, we set the filter to 'all'.
Aside: We could have added the click event to each anchor element and handled the filter selection for each. Instead, we chose to add the event listener to the parent element (todoNav) and use event delegation to handle the filter selection.
Step 7: Rendering the filtered todos
We’ll update the renderTodos function to filter the todos based on the selected filter and render only the relevant todos.
We can update the renderTodos function to filter the todos based on the current filter setting like this:
Next we’ll update the loop that renders the todos to use the filteredTodos array instead of the todos array.
Here is the updated renderTodos function:
Make sure to call renderTodos after updating the filter to display the filtered todos.
Run the app and check if the todos are filtered correctly based on the selected filter.
Step 8: Styling the active filter
Currently, there is an underline style applied to the first anchor element in the navbar. This is hardcoded in the HTML file. Let’s create a new function, renderTodoNavBar, to apply this style dynamically based on the selected filter.
This function iterates over the child elements of the todoNav element (the navbar) and applies or removes the underline style based on the href value. The anchor element that matches the href value will have the underline style applied, while the others will have it removed.
To put this function into action, call it after updating the filter in the handleClickOnNavbar function.
Run the app and check if the active filter is styled correctly based on the selected filter.
Step 9: Marking a todo as completed
When a todo is clicked, we want to toggle its completed status. We’ll add an event listener to the todo list and create a function to handle this behavior. First, we’ll update the renderTodos function to add an id attribute to the todo text element. This will help us identify the clicked todo later.
Next, we’ll create a function to handle the click event on the todo list and toggle the completed status of the clicked todo.
Let’s break down the handleClickOnTodoList function:
Identifying the Clicked Todo:
We first check if the clicked element has an id and if it includes the string "todo-text". This condition ensures that the subsequent code only runs if the text of a todo item was clicked, and not any other part of the todo or the page.
Extracting the Todo ID:
If the clicked element is a todo text, we extract the id of the todo from the id attribute of the clicked element. We split the id by the - character and extract the last part, which corresponds to the id of the todo. We convert this value to a number using Number().
Toggling the Completed Status:
We loop through the todos array to find the todo that matches the clicked id. Once found, we toggle the completed status of that todo using !todos[i].completed, which inverts the boolean value.
Re-rendering the App UI:
Finally, we call the renderTodos function to update the displayed todos with the updated completed status.
Note that we added the click event to the todoListElement and utilized event delegation to manage the click event on the todo list. We could have attached the click event to each todo item, but this would necessitate multiple event listeners, possibly causing performance issues as the number of todos increases. Event delegation is advantageous here as it reduces the number of event listeners, saving memory and enhancing performance, especially as the todo list expands. It also streamlines handling dynamic content, since new todos inherit the listener from their parent, removing the need for additional setup.
Step 10: Putting all together
Here is the complete main.js file with all the functions and event listeners we’ve implemented so far. Notice that we’ve structured the code slightly differently to group related statements together.
With this implementation, we’ve set up the basic functionality for our todo app using a structured programming approach. The todos can be added, marked as completed, and filtered. The state of the app is managed using arrays and variables, and the DOM is manipulated using various event listeners and functions. This structured approach is easy to understand and maintain, especially for beginners.
It’s worth noting that we haven’t yet implemented the behavior of the elements in the todo-actions section (as well as some of the other features). This was intentional, as we wanted to focus on the core functionality first. As we progress through different programming paradigms, we’ll gradually enhance the app’s features.