Building the TodoList Class

Defining the TodoList Class

Now that we have defined the Todo class, let’s create a TodoList class that represents a collection of todo items. Create a new file named todo-list.js in the src directory and define the TodoList class as follows:

import Todo from "./todo.js";

export const FILTERS = {
  ALL: "all",
  ACTIVE: "active",
  COMPLETED: "completed",
};

class TodoList {
  #todos = [];

  addTodo(text) {
    this.#todos.push(new Todo(text));
  }

  toggleTodo(id) {
    const todo = this.#todos.find((todo) => todo.id === id);
    if (todo) {
      todo.toggle();
    }
  }

  getTodos(filter) {
    switch (filter) {
      case FILTERS.ALL:
        return [...this.#todos];
      case FILTERS.COMPLETED:
        return this.#todos.filter((todo) => todo.completed);
      case FILTERS.ACTIVE:
        return this.#todos.filter((todo) => !todo.completed);
      default:
        return [...this.#todos];
    }
  }

  getNumberOfActiveTodos() {
    return this.#todos.reduce((acc, todo) => acc + !todo.completed, 0);
  }

  clearCompleted() {
    this.#todos = this.#todos.filter((todo) => !todo.completed);
  }

  markAllCompleted() {
    this.#todos.forEach((todo) => {
      todo.completed = true;
    });
  }
}

export default TodoList;

Let’s break down the code step by step:

  1. We import the Todo class from the todo.js file.

  2. We define a constant FILTERS object that contains three properties: ALL, ACTIVE, and COMPLETED. These properties represent the different filters we can apply to the todo list.

  3. We define the TodoList class with a private field #todos that stores the list of todo items. The #todos field is initialized as an empty array.

  4. We do not define a constructor method for the TodoList class. If a class does not define a constructor, JavaScript provides a default one. The #todos field is already initialized as an empty array, so there is nothing else to set up.

  5. We define several methods in the TodoList class:

    • addTodo: This method adds a new todo item to the list. It creates a new instance of the Todo class with the provided text and pushes it to the #todos array.

    • toggleTodo: This method toggles the completion status of a todo item with the specified ID. It finds the todo item in the list based on the ID and calls the toggle method on the todo item.

    • getTodos: This method returns a filtered list of todo items based on the specified filter. It uses the FILTERS object to determine the filter criteria and returns the filtered list of todo items.

    • getNumberOfActiveTodos: This method calculates the number of active (incomplete) todo items in the list. It uses the reduce method to count the number of todo items with a completion status of false.

    • clearCompleted: This method removes completed todo items from the list. It filters out todo items with a completion status of true and updates the #todos array.

    • markAllCompleted: This method marks all todo items as completed. It iterates over each todo item in the list and sets the completion status to true.

  6. We export the TodoList class as the default export from the todo-list.js file.

Let’s make a few observations about the TodoList class:

  • Making the #todos field private encapsulates the internal state of the TodoList class. Code outside the class cannot access the list of todo items directly.

  • We mix the practices of functional programming, structured programming, and object-oriented programming in the TodoList class. The class uses methods like filter, reduce, and forEach to work with the list of todo items. These methods are common in functional programming. We use them to transform and process data in a declarative way. On the other hand, we push directly to the #todos array in the addTodo method, and we directly toggle the completion status of a todo item in the toggleTodo method. Those actions are closer to structured programming. And the class as a whole is object-oriented programming: we define a class with properties and methods that operate on those properties.

  • We did not use the special getter method syntax with the getTodos method. This is because the method takes a parameter (filter) and performs a transformation on the data. A getter method usually returns a property value without any additional processing, and a getter cannot take parameters.

  • We used the export keyword with the declaration of the FILTERS object to make it available for import in other modules. This is an example of a named export, where we export a specific value from the module. If we were to import the FILTERS object in another module, we would use the following syntax: import { FILTERS } from './todo-list.js';.

Checkpoint: Commit your progress.

git add .
git commit -m "todos-18: Create TodoList class with encapsulation"
git push