DEV Community

loading...
Cover image for Simple To-Do list using localstorage 📝👨🏼‍💻

Simple To-Do list using localstorage 📝👨🏼‍💻

vladimirschneider profile image vladimirschneider ・3 min read

I think everyone has probably faced the To-Do list development. In this post I'd like to share how to build a simple one using localstorage.

Our application will allow:

  1. To store To-Do list
  2. To create, update and delete tasks

Storage

I am going to keep our list inlocalstorage, so I will start with a simple interface for working with tasks and localstorage. I am going to store our tasks in an array of objects.

I will get our data from localstorage and write tothis.tasks. in the interface constructor:

export default class LocalStorage {
  constructor() {
    // if item by key `tasks` is not defined JSON.parse return null, so I use `or empty array`
    this.tasks = JSON.parse(localStorage.getItem('tasks')) || [];
  }
}

Now I need methods to create, update and delete tasks.

Methods are going to be very simple. For example, here is the method for creating a task:

export default class LocalStorage {
  //...

  create(data) {
    this.tasks.push(data);

    localStorage.setItem('tasks', JSON.stringify(this.tasks));
  }
}

In this method I update this.tasks and localstorage item.

To update or delete a task I need to find it in the array first. I will create a getter get token to generate a random token for it and will update our create method. Here I are also adding agetIndexByToken method to search for a task index in this.tasks in order to update or delete it.

export default class LocalStorage {
  //...

  create(data) {
    data.token = this.token;

    //...
  }

  getIndexByToken(token) {
    for (let i = 0; i < this.tasks.length; i++) {
      if (this.tasks[i].token === token) {
        return i;
      }
    }

    return -1;
  }

  get token() {
    return Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15);
  }
}

Now the only thing I need to update a task is to find its index, check if it's not -1, update the object by an index found and rewrite localstorage tasks. Similarly, for delete method I need to find an index, delete a respective object from this.tasks, and rewrite localstorage item.

export default class LocalStorage {
  //...

  update(data) {
    let index = this.getIndexByToken(data.token);

    if (index !== -1) {
      this.tasks[index] = data;

      localStorage.setItem('tasks', JSON.stringify(this.tasks));
    }
  }

  delete(data) {
    let index = this.getIndexByToken(data.token);

    if (index !== -1) {
      this.tasks.splice(index, 1);

      localStorage.setItem('tasks', JSON.stringify(this.tasks));
    }
  }

  //...
}

Application

Now lets build our application.

First, I need to create an instance of our class and get a list of task. I will create, update and delete the tasks using this instance.

import LocalStorage from './LocalStorage.js';

const storage = new LocalStorage();

const tasks = storage.tasks;

Then I will create a html task template with html tag <template>, tasks container, and a task creation form.

<template id="task">
  <li class="task tasks__task">
    <label class="task__label">
      <input class="task__checkbox" type="checkbox" />

      <span class="task__checkbox-custom"></span>
    </label>

    <p class="task__text" contenteditable></p>

    <button class="btn btn--red task__delete" type="button">
      Delete
    </button>
  </li>
</template>

<ul class="tasks"></ul>

<form class="create-task">
  <textarea class="create-task__textarea" placeholder="Task title"></textarea>

  <button class="btn btn--bg-blue create-task__submit" type="submit" disabled>
    Create
  </button>
</form>

Here I will get all these elements:

//...

const container = document.querySelector('.tasks');
const template = document.querySelector('#task');

const createTaskForm = document.querySelector('.create-task');
const createTaskField = document.querySelector('.create-task__textarea');
const createTaskButton = document.querySelector('.create-task__submit');

Now I just need to iterate over task list from LocalStorage.

I will create onCreateTask function that will create tasks and add main events there:

function onCreateTask({data}) {
  const clone = template.content.cloneNode(true);

  const task = clone.querySelector('.task');
  const checkbox = clone.querySelector('.task__checkbox');
  const title = clone.querySelector('.task__text');
  const del = clone.querySelector('.task__delete');

  title.innerHTML = data.value;
  checkbox.checked = data.checked;

  toggleTaskStatusClass({checked: data.checked, task});

  checkbox.addEventListener('input', () => {
    data.checked = checkbox.checked;

    toggleTaskStatusClass({checked: data.checked, task});

    storage.update(data);
  });

  title.addEventListener('input', () => {
    data.value = title.innerHTML;

    storage.update(data);
  });

  del.addEventListener('click', (e) => {
    storage.delete(data);

    task.remove();
  });

  container.appendChild(clone);
}

function toggleTaskStatusClass({checked, task}) {
  task.classList[checked ? 'add' : 'remove']('task--done');
}

The method turned out to be quite simple. I search for objects, set correct data and events, and append the child to container.

Let's look at toggleTaskStatusClass method - usually this is not how I code things like that, and I shared it as example only. What's your take on it? Please don't hesitate to comment.

Now I will iterate over tasks with onCreateTask method and create a form submit event.

tasks.forEach((data) => {
  onCreateTask({data});
});

createTaskForm.addEventListener('submit', (e) => {
  e.preventDefault();

  const value = createTaskField.value;

  if (value) {
    const data = {
      value,
      checked: false
    };

    storage.create(data);

    onCreateTask({data});

    createTaskForm.reset();
  }
});

That's all! Time for a demo.

Demo

Thank you.

Discussion (4)

pic
Editor guide
Collapse
paras594 profile image
Paras 🧙‍♂️ • Edited

Awesome !!
And it is functioning so good, and styles are polished as well !

Collapse
mourad132 profile image
Mourad Sherif

It Is So Cool And Needed Thanks Bro I Was Really Looking For Somwthijg Like This

Collapse
zdev1official profile image
ZDev1Official

Cool!
Nice styling!

Collapse
djmisha profile image
Misha Osinovskiy

Thank you for this, I like your implementation. I also like that it does not use any frameworks. Well done, Vladimir!