JavaScript: Building a To-Do App (Part 4: Final)
Tags: javascript, tutorial, webdev, database
Published: 2020-08-03 22:16:00

## 📖 Introduction
Welcome to the last part of the "Introduction to the IndexedDB API" series. In the last post, we added an important feature to our application, the ability to _see_ the tasks on the page; we also did a little bit of refactoring as we needed to perform some actions for when the connection to the database is established. In this last part, we are going to finish our app by adding the ability to **delete** any given task.

### Goals
- Create a `delete` method on the `Database` class that deletes a task given its _id_
- Complete the `removeTask` function on the `index.js` file so that effectively delete a task when needed

### Initial Setup 
If you want to code along (which is **_highly recommended_**), then go to the following GitHub repository:

{% github DevTony101/js-todo-app-indexed_db no-readme %}

Once there, go to the `README.md` file and search for the link labeled `Starting Code` for the third (last) part. It will redirect you to a commit tagged as `starting-code-part-three` that contains all we have done so far plus the new `delete` function on the `Database` class.

## 🛠 Completing the `delete` Function
This will be pretty straight forward, the process is exactly the same as the one we did back in part two:

1. Create a `transaction` object from the database
2. Get a reference to the `objectStore`
3. Create a request based on the action you need to perform, which in this case, is to _delete_ something

In code, it would look something like this:

```javascript
delete(id) {
  // Skipping validation of the id ...
  // Step 1
  const transaction = this.indexedDB.transaction([this.name], "readwrite");
  // Step 2
  const objectStore = transaction.objectStore(this.name);
  // Step 3
  const request = objectStore.delete(id);
}
```
The key difference here, in contrast to the `persist` function, is that we are only going to take care of the `complete` event emitted by the transaction because, if the task was successfully deleted, we would want to remove it from the `DOM` but, we will get to that later. 

Now we have to ask ourselves an important question, how we should handle see `oncomplete` callback? There are two ways:

1. We _could_ return the `transaction` object and handle it in the `index.js` file
2. We _could_ pass a function as a parameter to the `delete` function

Both are equally valid but, just because it _looks_ fancier, let's choose to do the second one, in which case, we need to change the signature of the `delete` function so that accepts a second named argument. The rest is pretty much the same as in the `persist` function:

```javascript
delete(id, success) {
  // ...
  if (typeof success === "function") transaction.oncomplete = success;
}
```

That's all! Our `delete` function is complete, a piece of cake 🍰, right?

## 👨‍🔧 Assigning an `ID` to our Tasks
In order to use the `delete` function, we need to have the `id` of the task we want to delete but, because we don't know in advance which task the user is going to remove, the `id` needs to be dynamically retrieved.

A great way to accomplish this is by storing the `id` of the task in a `data-*` attribute inside the `message` component. To do it, we just need to use the `setAttribute()` method on the `message` object in the `showTasks` function, like this:

```javascript
function showTasks() {
  // ...
  const request = database.getOpenCursor();
  request.onsuccess = event => {
    const cursor = event.target.result;
    if (cursor) {
      // The 'id' is the key, it's stored in the value property of the cursor object alongside the other fields
      const {key, title, description} = cursor.value;
      const message = document.createElement("article");
      message.classList.add("message", "is-primary");
      // Here we store the key in a data attribute called data-id
      message.setAttribute("data-id", key);
      // ...
      cursor.continue();
    } else {
      // ...
    }
  }
}
```
That's it! Now we are all set to start deleting stuff.

## 👨‍💻 Using the `delete` Function
On the `index.js` file, let's create a new function called `removeTask` with the following signature:

```javascript
function removeTask(event) {
  // The event will be the message component representing the task
}
```

This function will be called from within a `button` element that will be placed inside the `header` of the `message` component (according to **Bulma´s** official [documentation](https://bulma.io/documentation/components/message/))<sup>1</sup>.

Inside there, let's first retrieve the `article` block itself from the `event` object (remember that the `event` only carries the node that fired up the action, in this case, the `button` element) and then, get the actual `id` from the `data-id` attribute. Next, pass the `id` to the `delete` function we created earlier, like this:

```javascript
function removeTask(event) {
  const header = event.target.parentElement;
  const task = header.parentElement;
  const id = Number(task.getAttribute("data-id"));
  database.delete(id, () => {
    // Success callback
  });
}
```
Now all that's left is to figure out what to put inside the callback function. 

If the task is successfully deleted, then we must remove it from the `DOM`, else the user is going to be very confused. On top of that, if there was only one task, we need to let the user know that there are no more tasks left, this is exactly what we did in the `showTasks` function for when the `cursor` object is not defined.

So, putting it together:

1. Remove the task from the `DOM`, that is, remove the message component (the `article` block) from the `tasksContainer` div

3. If there are no more tasks inside the div i.e. the are no more tasks left to show, we need to add a message to let the user know

In code, we would have something like this:

```javascript
function removeTask(event) {
  // ...
  database.delete(id, () => {
    // Step 1
    tasksContainer.removeChild(task);

    // Step 2
    if (!tasksContainer.firstChild) {
      const text = document.createElement("p");
      text.textContent = "There are no tasks to be shown.";
      tasksContainer.appendChild(text);
    }

    // Optional Step 3: Console log for debugging purposes
    console.log(`Task with id ${id} deleted successfully.`);
  });
}
```

Last but not least, let's put the actual delete `button` element inside the `message` component in the `showTasks` function, like this:

```javascript
function showTasks() {
  // ...
  const request = database.getOpenCursor();
  request.onsuccess = event => {
    const cursor = event.target.result;
    if (cursor) {
      const {key, title, description} = cursor.value;
      const message = document.createElement("article");
      // ...
      // Creating the delete button element
      const deleteButton = document.createElement("button");
      deleteButton.classList.add("delete");
      deleteButton.setAttribute("aria-label", "delete");
      deleteButton.onclick = removeTask;
        
      // Adding it to the div message header
      message.firstChild.nextSibling.appendChild(deleteButton);
      tasksContainer.appendChild(message);
      cursor.continue();
    } else {
      // ..
    }
  }
}
```
And... we are done 👏! Now let's test it out, start your local development server and go the `index` page of our application. If there are no tasks there, create some and when they render on the page you will see that a little `X` button appears on top on the task description, like so:

![Screenshot Tasks](https://dev-to-uploads.s3.amazonaws.com/i/wxlboonrk2u4pxkledij.png)

If everything went right, when you click on the `X` button, the task should disappear. We can confirm this by looking up in the console:

![Screenshot Console](https://dev-to-uploads.s3.amazonaws.com/i/amenzlq0kjej5kb1lo9x.png)

Awesome 🎉! Everything works as expected.

## 🕵️‍♂️ Let's Recap
Whew! What a journey, we sure learned a lot since part one, so let's do a quick review of all:

- We learned what `IndexedDB` API is and how we can create our own database in the browser

- We learned how to perform `CRUD` operations using `transactions` and `request`

- We learned how we can create a `Database` class with custom methods that let us seamlessly use the ones exposed by the **API**, effectively enclosing the business logic we could need

- And finally, we learned how we can use the `Database` class in a real-world project by creating our own to-do application.

The complete code of the project can be found at **GitHub** on the `master` branch. The specific changes we made for this part are under the commit tagged as `finished-code-part-three`. I invite you to fork the project so you can experiment with it.

Also, the project is up and running on **Netlify** at the following URL:

- https://indexed-todo-app.netlify.app/

Thank you so much for reading, I hope all of this is useful to you if you have comments, questions or suggestions please leave them down below.

See you next time 👋.

<hr/>

<small>1: This is relevant because the project uses <b>Bulma</b> as a CSS framework but it does <i><b>not</b></i> mean you can't call the function in another way. What's important is that the element that calls the function is inside the article block, otherwise, you won't have access to its `id`</small>