JavaScript: Local Storage with a Practical Todo List Example

JavaScript: Local Storage with a Practical Todo List Example

HTML5 provides the localStorage and sessionStorage objects that let the web page use JavaScript to store data in name and value pairs. In this tutorial, I will show you how to use the local storage using JavaScript with a practical 🔥Todo List example.

JavaScript localStorage Object Methods and Syntax

The localStorage is retained indefinitely.

localStorage.setItem("itemname", "value") // saves the data in the item
localStorage.getItem("itemname")          // get the data in the item
localStorage.removeItem("itemname")       // removes the item
localStorage.clear()                      // removes all items

JavaScript sessionStorage Object Methods and Syntax

The sessionStorage is lost when the user closes the browser.

sessionStorage.setItem("itemname", "value") // saves the data in the item
sessionStorage.getItem("itemname")          // get the data in the item
sessionStorage.removeItem("itemname")       // removes the item
sessionStorage.clear()                      // removes all items

The shortcut syntax for getting or saving an item

localStorage.itemname                       // saves or gets the data
sessionStorage.itemname                     // saves or gets the data

setItem Method

The setItem method requires two parameters that provide the name of an item and the value for the item. For instance, you can use code like this to add items named "email" and "phone" that store the email address and phone number of the user:

localStorage.setitem("email", "[email protected]");
localStorage.setitem("phone", "+91 111222333");

getItem Method

Now you can use the getItem method with the item name as the parameter to retrieve the data for the phone item with the statement as follows:

localStorage.getItem("phone");

To simplify, you can use the shortcut syntax as shown in the below example:

localStorage.email = "[email protected]"
localStorage.phone = "+91 111222333"

An example to get the local storage value into the variable:

var phone = localStorage.phone;

Todo List Example

Here I am giving an example of a beautiful Todo list created using the JavaScript localStorage object:

HTML Code

<html lang="en">

<head>
  <meta charset="utf-8">
  <title>Todo List</title>
  <link rel="stylesheet" href="style.css">
</head>

<body>
  <section class="todoapp">
    <header class="header">
      <h1>todo list</h1>
      <input id="newTodo" class="new-todo" placeholder="What needs to be done?">
    </header>
    <section id="main">
      <input id="toggleInputAll" class="toggle-all" type="checkbox">
      <label for="toggle-all">Mark all as complete</label>
      <ul id="todoListView" class="todo-list"></ul>
    </section>
    <footer class="footer">
      <span class="todo-count"><strong id="todoCount">0</strong> item left</span>
      <ul class="filters">
        <li>
          <a class="selected" id="allWorks" onclick="changeClass(this)" href="#/">All</a>
        </li>
        <li>
          <a href="#active" id="activedItems" onclick="changeClass(this)">Active</a>
        </li>
        <li>
          <a href="#completed" id="completedTodos" onclick="changeClass(this)">Completed</a>
        </li>
      </ul>
      <button class="clear-completed" id="btnClear">Clear completed</button>
    </footer>
  </section>

  <script src="todo-list.js"></script>
</body>

</html>

CSS Code

html,
body {
	margin: 0;
	padding: 0;
}

button {
	margin: 0;
	padding: 0;
	border: 0;
	background: none;
	font-size: 100%;
	vertical-align: baseline;
	font-family: inherit;
	font-weight: inherit;
	color: inherit;
}

button, input[type="checkbox"] {
  outline: none;
}

body {
	font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
	line-height: 1.4em;
	background: #f5f5f5;
	color: #4d4d4d;
	min-width: 230px;
	max-width: 550px;
	margin: 0 auto;
	font-weight: 300;
}

.hidden {
	display: none;
}

.todoapp {
	background: #fff;
	margin: 130px 0 40px 0;
	position: relative;
	box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
	            0 25px 50px 0 rgba(0, 0, 0, 0.1);
}

.todoapp input::input-placeholder {
	font-style: italic;
	font-weight: 300;
	color: #e6e6e6;
}

.todoapp h1 {
	position: absolute;
	top: -155px;
	width: 100%;
	font-size: 100px;
	font-weight: 100;
	text-align: center;
  text-transform: capitalize;
	color: #cc9a9a;
}

.new-todo,
.edit {
	position: relative;
	margin: 0;
	width: 100%;
	font-size: 24px;
	border: 0;
	outline: none;
	padding: 6px;
	border: 1px solid #999;
	box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
	box-sizing: border-box;
}

.new-todo {
	padding: 16px 16px 16px 60px;
	border: none;
	background: rgba(0, 0, 0, 0.003);
	box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
}

.main {
	position: relative;
	z-index: 2;
	border-top: 1px solid #e6e6e6;
}

label[for='toggle-all'] {
	display: none;
}

.toggle-all:before {
	content: '❯';
	font-size: 22px;
	color: #e6e6e6;
	padding: 10px 27px 10px 27px;
}

.toggle-all:checked:before {
  color: #737373;
}

.toggle-all {
	position: absolute;
	top: 5px;
	left: -12px;
	width: 60px;
	height: 34px;
	text-align: center;
	border: none; /* Mobile Safari */
  -webkit-transform: rotate(90deg);
  transform: rotate(90deg);
  -webkit-appearance: none;
}

.todo-list {
	margin: 0;
	padding: 0;
	list-style: none;
}

.todo-list li {
	position: relative;
	font-size: 24px;
	border-bottom: 1px solid #ededed;
}

.todo-list li:last-child {
	border-bottom: none;
}

.todo-list li.checked {
	color: #979797;
	font-weight: normal;
	text-decoration: line-through;
	
}

.todo-list li.checked input[type="checkbox"]:after {
	border: 1px solid #166B94;
	border-radius: 3px;
	color: #fff;
	content: "";
	display: block;
	height: 16px;
	line-height: 16px;
	position: absolute;
	text-align: center;
	visibility: visible;
	width: 16px;
}

.todo-list li.checked input[type=checkbox]:checked:after  {
	border: 1px solid #979797;
	color: #979797;
	content: "✓";
	font-size: 25px;
  color: green;
}

.todo-list li .itemList {
	text-align: center;
  height: 20px;
	/* auto, since non-WebKit browsers doesn't support input styling */
	position: absolute;
	top: 0;
	bottom: 0;
	margin: auto 0;

}

.todo-list li label {
	white-space: pre-line;
	word-break: break-all;
	padding: 15px 60px 15px 15px;
	margin-left: 45px;
	display: block;
	line-height: 1.2;
	transition: color 0.4s;
}

.todo-list input[type=checkbox] {
	cursor: pointer;
	visibility: hidden;
	margin-left: 20px;
}

.todo-list input[type="checkbox"]:after {
	border: 1px solid #166B94;
	border-radius: 3px;
	color: #fff;
	content: "";
	display: block;
	height: 16px;
	line-height: 16px;
	position: absolute;
	text-align: center;
	visibility: visible;
	width: 16px;
}

.todo-list input[type=checkbox]:checked:after {
	border: 1px solid #979797;
	color: #979797;
	content: "✓";
	font-size: 25px;
  color: green;
}
 
 
.todo-list input[type=checkbox]:checked + label {
	color: #979797;
	font-weight: normal;
	text-decoration: line-through;
}

.todo-list li .remove {
	display: none;
	position: absolute;
	top: 0;
	right: 10px;
	bottom: 0;
	width: 40px;
	height: 40px;
	margin: auto 0;
	font-size: 30px;
	color: #cc9a9a;
	margin-bottom: 11px;
	transition: color 0.2s ease-out;
	cursor: pointer;
}

.todo-list li .remove:hover {
	color: #af5b5e;
}

.todo-list li .remove:after {
	content: '×';
}

.todo-list li:hover .remove {
	display: block;
}


.edit {
	display: none;
}

li.editing {
	display: block;
	width: 430px;
}

li.editing > label {
	display: none;
}

li.editing > input.edit {
	display: block !important;
}

li:hover.editing > button.remove {
	display: none ;
}
.todo-list li.editing .edit {
	display: block;
	width: 506px;
	padding: 13px 17px 12px 17px;
	margin: 0 0 0 43px;
}

.show-all {
	display: block;
}

.todo-list li.editing:last-child {
	margin-bottom: -1px;
}

.footer {
	color: #777;
	padding: 10px 15px;
	height: 20px;
	text-align: center;
	border-top: 1px solid #e6e6e6;
}

.footer:before {
	content: '';
	position: absolute;
	right: 0;
	bottom: 0;
	left: 0;
	height: 50px;
	overflow: hidden;
	box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
	            0 8px 0 -3px #f6f6f6,
	            0 9px 1px -3px rgba(0, 0, 0, 0.2),
	            0 16px 0 -6px #f6f6f6,
	            0 17px 2px -6px rgba(0, 0, 0, 0.2);
}
	
.todo-count {
  float: left;
  text-align: left;
}

.filters {
  margin: 0;
  padding: 0;
  list-style: none;
  position: absolute;
  right: 0;
  left: 0;
}

.filters li {
  display: inline;
}

.filters li a {
  color: inherit;
  margin: 3px;
  padding: 3px 7px;
  text-decoration: none;
  border: 1px solid transparent;
  border-radius: 3px;
}

.filters li a.selected {
  border-color: rgba(175, 47, 47, 0.2);
}

.clear-completed, html .clear-completed:active {
    float: right;
    position: relative;
    line-height: 20px;
    text-decoration: none;
    cursor: pointer;
}

.clear-completed:hover {
	text-decoration: underline;
}

JavaScript Code

'use strict'

function Todo(id, content, isDone) {
  this.id = id;
  this.content = content;
  this.isDone = isDone;
};

/**
 * Declare a controller
 */
function TodoController() {
  this.todoList = [];
  this.id = 1;
  this.ENTER_KEY = 13;
  this.todoInput = document.getElementById('newTodo');
  this.todoListView = document.getElementById('todoListView');
};

TodoController.prototype = {
  /**
   * @param {argument} key - get to localstorage
   */
  getTodoFromLocalstorage: function (key) {
    var todoList = JSON.parse(localStorage.getItem(key)) || [];
    return todoList;
  },

  /**
   * @param {argument} key - set into localstorage
   */
  setTodoLocalstorage: function (key) {
    localStorage.setItem('todoList', JSON.stringify(key));
  },

  /**
   * @param {sting} value - content todo
   */
  handleTodoItem: function (value) {
    this.isDone = false;
    var mainArray = todoController.getTodoFromLocalstorage('todoList');
    this.id = todoController.idLargestOfLocal(mainArray) + 1;
    var todoItem = new Todo(this.id, value, this.isDone);
    return todoItem;
  },

  /**
   * @param {array} mainArray - find id last in array at localstorage
   */
  idLargestOfLocal: function (mainArray) {
    var lengthArr = mainArray.length;

    if (lengthArr !== 0) {
      return mainArray[lengthArr - 1].id;
    } else {
      return 0;
    }

    return lastId;
  },

  /**
   * Presentation create new a todo item
   * @param {array} list - id for todo
   * @return {object} todo - return todo object
  */
  addNewTodo: function (todo, list) {
    list.push(todo);
    todoController.setTodoLocalstorage(list);
    return todo;
  },

  /**
   * Presentation create new a todo item
   * @param {value attribute} attrs - value attribute for element html 
   * @return {attribute} element - attribute for element html
  */
  setAttributes: function (element, attrs) {
    for (var key in attrs) {
      element.setAttribute(key, attrs[key]);
    }
  },

  /**
   * Create new checkbox input element
   * @param {number} todoId - id checkbox
   */
  checkboxView: function (todoId) {
    var inpCheckbox = document.createElement('input');
    this.setAttributes(inpCheckbox, { type: 'checkbox', class: 'itemList', id: todoId });

    //event check for input checkbox
    inpCheckbox.addEventListener('click', function (e) {
      //get list array from localStorage
      var list = todoController.getTodoFromLocalstorage('todoList');
      var id = e.target.getAttribute('id');
      for (var i = 0; i < list.length; i++) {
        if (list[i].id == id) {
          list[i].isDone = e.target.checked;
        }
      }

      //save list todo to localStorage
      todoController.setTodoLocalstorage(list);
      todoController.countItem();
    });

    return inpCheckbox;
  },

  /**
   * Create new lable element
   * @param {object} todo - item todo from addNewTodo 
   */
  createLableView: function (todo) {
    var lbContent = document.createElement('label');
    this.setAttributes(lbContent, { value: todo.content, class: 'labelContent ' });
    lbContent.innerHTML = todo.content;

    //return node lable
    return lbContent;
  },

  /**
   * Create new li element
   * @param {object} todo - item todo from addNewTodo 
   */
  initTodoITem: function (todo) {
    var item = document.createElement('li');
    item.setAttribute('class', 'todoItem');
    // this.setAttributes(item, { , class: 'todoItem ' });

    //event event double click in node li
    item.addEventListener('dblclick', function (e){
      item.classList.add('editing');
    });

    //return node li
    return item;
  },

  /**
   * Create new input edit element
   * @param {object} todo - item todo from addNewTodo 
   */
  editInputView: function (todo) {
    //get array from localStorage
    var list = todoController.getTodoFromLocalstorage('todoList');
    var inputEdit = document.createElement('input');
    this.setAttributes(inputEdit, {
      id: todo.id,
      class: 'edit',
      value: todo.content,
      type: 'text',
    });
    inputEdit.focus();

    //event onblur get value edit and delete class editing when click outside this input
    inputEdit.onblur = function (e) {
      todoController.handleTodoUpdate(e);
    };

    //event onkeyup get value edit form inputEdit
    inputEdit.onkeypress = function (e) {
      if (event.which == todoController.ENTER_KEY || event.keyCode == todoController.ENTER_KEY) {
        todoController.handleTodoUpdate(e);
      }
    };

    //return node input for edit todo
    return inputEdit;
  },

  handleTodoUpdate: function (event) {
    var list = todoController.getTodoFromLocalstorage('todoList');
    var inputEdit = event.target;
    var todoItem = new Todo(inputEdit.id, inputEdit.value, false);
    todoController.updateTodoEdit(todoItem, list);
    var editing = document.querySelector('.editing');
    editing.classList.remove('editing');
    todoController.renderTodo();
  },

  /**
   * Presentation update todo edit
   * @param {object} todo - get item todo from event get value edit
   * @param {array} list - array in localStorage
   */
  updateTodoEdit: function (todo, list) {
    for (var i = 0; i < list.length; i++) {
      if (list[i].id == todo.id) {
        list[i].content = todo.content;
        todoController.setTodoLocalstorage(list);
        break;
      }
    }

    //return new object have edit
    return todo;
  },

  /**
   * Presentation create new button remove item todo
   * @param {object} todo - get item todo from event get value edit
   */
  removeButtonView: function (todo) {
    var btnRemove = document.createElement('button');
    this.setAttributes(btnRemove, { class: 'remove', id: todo.id });

    //event click mouse into btnRemove a item todo
    btnRemove.addEventListener('click', function (e) {
      var id = e.target.getAttribute('id');
      todoController.removeTodo(id);
      todoController.renderTodo();
      todoController.countItem();
    });

    //return node button
    return btnRemove;
  },

  /**
   * Presentation create new a todo item
   * @param {object} todo - object render to view 
  */
  todoView: function (todo) {
    var item = this.initTodoITem(todo);//create node li
    var inpCheckbox = this.checkboxView(todo.id),//create node input checkbox
      lbContent = this.createLableView(todo),//create node lable
      inputEdit = this.editInputView(todo),//create node input edit
      btnRemove = this.removeButtonView(todo);//create node button remove item todo

    //item append each element
    item.appendChild(inpCheckbox);
    item.appendChild(lbContent);
    item.appendChild(inputEdit);
    item.appendChild(btnRemove);

    //ul append each item
    document.querySelector('#todoListView').appendChild(item);

    //return node li contain inpCheckbox, lbContent, inputEdit, btnRemove
    return item;
  },

  /**
   * Presentation remove a item todo
   * @param {number} id - id button remove item todo
   * @param {array} list - list array get from localStorage
   */
  removeTodo: function (id, list) {
    list = todoController.getTodoFromLocalstorage('todoList');
    for (var i = 0; i < list.length; i++)  {
      if (list[i].id == id) {
        list.splice(i, 1);
        break;
      }
    }

    //set value after remove item to localStorage
    todoController.setTodoLocalstorage(list);
  },

  /**
   * Presentation remove a item todo
   * @param {index} index - index in array object 
   * @param {array} list - list array get from localStorage
   */
  countItem: function (index, list) {
    list = todoController.getTodoFromLocalstorage('todoList');
    index = 0;
    for (var i = 0; i < list.length; i++) {
      if (!list[i].isDone) {
        index++;
      }
    }

    // return index display to UI;
    document.getElementById('todoCount').innerHTML = index;
  },

  /**
   * Presentation the events for todo
   */
  events: function () {
    // Event add todo
    todoController.todoInput.onkeyup = function (event) {
      if (event.which == todoController.ENTER_KEY || event.keyCode == todoController.ENTER_KEY) {
        //get from localStorage
        var todoList = todoController.getTodoFromLocalstorage('todoList');

        //attach value for todo
        var todoItem = todoController.handleTodoItem(todoController.todoInput.value);

        //add new a Todo
        var todo = todoController.addNewTodo(todoItem, todoList);

        //Execute display to UI
        todoController.todoView(todo);

        //clear input
        todoController.todoInput.value = '';
        todoController.countItem();
      }
    };

    //event check all checkbox in list item
    var list = document.getElementsByClassName('itemList');
    var checkAll = document.getElementById('toggleInputAll');
    checkAll.addEventListener('change', function (e) {
      var check;
      for (var i = 0; i < list.length; i++) {
        list[i].checked = this.checked;
        check = e.target.checked;
        todoController.checkAllTodo(check);
      }

      todoController.countItem();
    });

    //Show all items
    var listWork = document.getElementsByClassName('todoItem');
    var showAllItem = document.getElementById('allWorks');
    showAllItem.addEventListener('click', function () {
      for (var i = 0; i < listWork.length; i++) {
        listWork[i].style.display = 'block';
      }
    });

    // Filter todo list with actived items
    var activeItem = document.getElementsByClassName('todoItem');
    var todoActive = document.getElementById('activedItems');
    todoActive.addEventListener('click', function () {
      for (var i = 0; i < list.length; i++) {
        if (!list[i].checked) {
          activeItem[i].style.display = 'block';
        } else {
          activeItem[i].style.display = 'none';
        }
      }
    });

    //Filter completed todo list
    var completeItem = document.getElementsByClassName('todoItem');
    var todoCompleted = document.getElementById('completedTodos');
    todoCompleted.addEventListener('click', function () {
      for (var i = 0; i < list.length; i++) {
        if (list[i].checked) {
          completeItem[i].style.display = 'block';
        } else {
          completeItem[i].style.display = 'none';
        }
      }
    });

    // Added event clear completed items for button
    var clearButton = document.getElementById('btnClear');
    clearButton.addEventListener('click', function () {
      //get from localStorage
      var list = todoController.getTodoFromLocalstorage('todoList');
      todoController.clearCompleted(list);
      todoController.setTodoLocalstorage(list);
      todoController.renderTodo();
    });
  },

  /**
   * Presentation clear all item todo have isDone
   * @param {array} list - get from localstorage
   */
  clearCompleted: function (list) {
    while (list.find(({ isDone }) => isDone)) {
      list.splice(list.indexOf(list.find(({ isDone }) => isDone)), 1);
    }
  },

  /**
   * Presentation set status isDone into localstorage
   * @param {boolean} check - isDone from event checkall 
   * @param {array} todoList - list array get from localStorage
   */
  checkAllTodo: function (check, todoList) {
    todoList = todoController.getTodoFromLocalstorage('todoList');
    for (var i = 0; i < todoList.length; i++) {
      todoList[i].isDone = check;
      todoController.setTodoLocalstorage(todoList);
    }
  },

  /**
   * Presentation set status isDone into localstorage
   * @param {array} list - list array get from localStorage
   */
  renderTodo: function () {
    //get from localStorage
    var list = todoController.getTodoFromLocalstorage('todoList');
    todoController.removeElement();
    for (var i = 0; i < list.length; i++) {
      var element = todoController.todoView(list[i]);
      if (list[i].isDone) {
        element.classList.add('checked');
      }
    }
  },

  removeElement: function () {
    var todoListView = document.getElementById('todoListView');
    while (todoListView.hasChildNodes()) {
      todoListView.removeChild(todoListView.firstChild);
    }

  },
};

//change class selected
function changeClass(elem) {
  var a = document.getElementsByTagName('a');
  for (var i = 0; i < a.length; i++) {
    a[i].classList.remove('selected');
  };

  //add class selected for element user click
  elem.classList.add('selected');
};

//todoController handle all action add, delete, edit, events
var todoController = new TodoController();

//todo create new object todo
var todo = new Todo();


//performing the events
todoController.events();

// //performing render todo display to UI
todoController.renderTodo();

//performing count all item active
todoController.countItem();

Output

JavaScript local storage example with a Todo list demo.

Related Tutorials:

Reference:

This Post Has 6 Comments

  1. Daniel Larsen

    Wow, great work! 😀

    I hope you can help with a little Firefox issue:
    The checkbox vanishes when running in firefox (78.0.2 (64-bit)), but works fine in chromium browsers and I can't seem to locate the problem.

    1. Vinish Kapoor

      Hi Daniel,

      I tested on the Firefox browser and it was working fine. But I can notice in the checkbox size only that they are small in comparison to Chrome. Rest is fine.

    2. Daniel Larsen

      Thank you for your quick response. It must be something on my machine then 🙂

  2. Daniel Larsen

    Sorry to bother you again, but I was wondering if it is possible to create premade items? And if so, can you please tell me how? 😀

    1. Vinish Kapoor

      The above JavaScript code writing li elements as new items to the object id todoListView so if you want some item predefined then you have to create some li elements. Following is an example:

      <ul id="todoListView" class="todo-list">
        <li>A predefined item-1</li>
      </ul>
      
    2. Dan

      Hi Vinish,

      Great work, looks really nice. Where in the code do you place the predetermined items? I've tired in a few places and it either doesn't come up at all or they come up as bullet points and not checkboxes.

      Thanks

Comments are closed.