Building a To-Do List with jQuery and Local Storage

Shaumik Daityari

We all have those days when we have a lot of things to accomplish, but no way to know if we will complete all of them. In these situations, a to-do list is extremely helpful. There are great mobile apps, online tools, and even WordPress plugins which perform this task, but have you ever wondered how long it would take to create one by yourself?

In this tutorial, we will create a basic to-do list. Tasks will fall into one of three categories – pending, in progress, and completed. Our application will allow new tasks to be created, which go into the pending category by default. Tasks can easily be moved between categories using drag and drop technology. Of course, tasks can also be deleted.

Prerequisites

The following libraries are used in this project.

  • jQuery 1.10.2
  • jQuery UI 1.10.3
  • Bootstrap 2.2.2

Creating the Markup and CSS

Let’s create the basic look and feel of the application before adding the functionality via JavaScript.

<div class="task-list" id="pending">
  <h3>Pending</h3>
  <!-- Sample task added manually to check look -->
  <div class="todo-task">
    <div class="task-header">Sample Header</div>
    <div class="task-date">25/06/1992</div>
    <div class="task-description">Lorem Ipsum Dolor Sit Amet</div>
  </div>
</div>

<div class="task-list" id="inProgress">
  <h3>In Progress</h3>
</div>

<div class="task-list" id="completed">
  <h3>Completed</h3>
</div>

<div class="task-list">
  <h3>Add a task</h3>
  <form id="todo-form">
    <input type="text" placeholder="Title" />
    <textarea placeholder="Descrtipion"></textarea>
    <input type="text" placeholder="Due Date (dd/mm/yyyy)" />
    <input type="button" class="btn btn-primary" value="Add Task" />
  </form>

  <input type="button" class="btn btn-primary" value="Clear Data" />

  <div id="delete-div">Drag Here to Delete</div>
</div>

Next, add some styling to the elements using the following CSS.

.task-list {
  width: 250px;
  float: left;
  margin: 0 5px;
  background-color: #e3e3e3;
  min-height: 240px;
  border-radius: 10px;
  padding-bottom: 15px;
}

.task-list input, .task-list textarea {
  width: 240px;
  margin: 1px 5px;
}

.task-list input {
  height: 30px;
}

.todo-task {
  border-radius: 5px;
  background-color: #fff;
  width: 230px;
  margin: 5px;
  padding: 5px;
}

.task-list input[type="button"] {
  width: 100px;
  margin: 5px;
}

.todo-task > .task-header {
  font-weight: bold;
}

.todo-task > .task-date {
  font-size: small;
  font-style: italic;
}

.todo-task > .task-description {
  font-size: smaller;
}

h3 {
  text-align: center;
}

#delete-div {
  background-color: #fff;
  border: 3px dotted #000;
  margin: 10px;
  height: 75px;
  line-height: 75px;
  text-align: center;
}

Our static to-do page should look like the following image.

Todo list demo

Defining the JavaScript Constants

Throughout this tutorial, we will be referring to certain constants to avoid hard coding values. These constants are shown below.

var defaults = {
  // CSS selectors and attributes that would be used by the JavaScript functions
  todoTask: "todo-task",
  todoHeader: "task-header",
  todoDate: "task-date",
  todoDescription: "task-description",
  taskId: "task-",
  formId: "todo-form",
  dataAttribute: "data",
  deleteDiv: "delete-div"
}, codes = {
  "1" : "#pending", // For pending tasks
  "2" : "#inProgress",
  "3" : "#completed"
};

Creating Tasks

Tasks are created using the following JavaScript function.

// Add Task
var generateElement = function(params) {
  var parent = $(codes[params.code]),
      wrapper;

  if (!parent) {
    return;
  }

  wrapper = $("<div />", {
    "class" : defaults.todoTask,
    "id" : defaults.taskId + params.id,
    "data" : params.id
  }).appendTo(parent);

  $("<div />", {
    "class" : defaults.todoHeader,
    "text": params.title
  }).appendTo(wrapper);

  $("<div />", {
    "class" : defaults.todoDate,
    "text": params.date
  }).appendTo(wrapper);

  $("<div />", {
    "class" : defaults.todoDescription,
    "text": params.description
  }).appendTo(wrapper);
};

The following code sample shows how a single task is generated.

generateElement({
  id: "123",
  code: "1",
  title: "My Uber Important Task",
  date: "5/2/2014",
  description: "I have to do a lot of steps to implement this task!"
});

Deleting Tasks

Removing tasks is fairly simple, and can be accomplished using the following function.

var removeElement = function(params) {
  $("#" + defaults.taskId + params.id).remove();
};

Saving Tasks in Local Storage

The tasks we create could be stored using a database, cookies, or a number of other technologies. However, in this application we’re going to use HTML5′s local storage for its simplicity. In JavaScript, the variable localStorage stores all of this data. The following code sample shows how the to-do list data is retrieved from local storage.

var data = JSON.parse(localStorage.getItem("todoData"));

Each task would be stored within the data variable. An example task object is shown below.

{
  id : id, // Unique ID; timestamp is used here
  code: "1", // Code identifying the category
  title: title, // Title of the task
  date: date, // Due date
  description: description // Description of the task
}

We update the saved data in local storage using the following code.

localStorage.setItem("todoData", JSON.stringify(data));

Submitting the To-Do Form

When the to-do form is submitted, a new task is created and added to local storage, and the contents of the page are updated. The following function implements this functionality.

var addItem = function() {
  var inputs = $("#" + defaults.formId + " :input"),
      errorMessage = "Title can not be empty",
      id, title, description, date, tempData;

  if (inputs.length !== 4) {
    return;
  }

  title = inputs[0].value;
  description = inputs[1].value;
  date = inputs[2].value;

  if (!title) {
    generateDialog(errorMessage);
    return;
  }

  id = new Date().getTime();

  tempData = {
    id : id,
    code: "1",
    title: title,
    date: date,
    description: description
  };

  // Saving element in local storage
  data[id] = tempData;
  localStorage.setItem("todoData", JSON.stringify(data));

  // Generate Todo Element
  generateElement(tempData);

  // Reset Form
  inputs[0].value = "";
  inputs[1].value = "";
  inputs[2].value = "";
};

Implementing Drag and Drop

jQuery UI provides drag and drop functionality. We need to make each task draggable and each of the three categories droppable. To delete a task, we need to hide the delete area by default, and show it during the time an item is being dragged. Therefore, we first modify the generateElement() function slightly to first make the to-do list items draggable, and then make the delete area visible when the item is being drug.

$("." + defaults.todoTask).draggable();

// Add Task
var generateElement = function(params) {
  wrapper.draggable({
    start: function() {
      $("#" + defaults.deleteDiv).show();
    },
    stop: function() {
      $("#" + defaults.deleteDiv).hide();
    }
  });
...
};

Secondly, we need to add the droppable() function to each of the categories as the elements are supposed to be dropped in any one of the three areas.

// Adding drop function to each category of task
$.each(codes, function(index, value) {
  $(value).droppable({
    drop: function(event, ui) {
      var element = ui.helper,
          css_id = element.attr("id"),
          id = css_id.replace(options.taskId, ""),
          object = data[id];

      // Removing old element
      removeElement(object);

      // Changing object code
      object.code = index;

      // Generating new element
      generateElement(object);

      // Updating Local Storage
      data[id] = object;
      localStorage.setItem("todoData", JSON.stringify(data));

      // Hiding Delete Area
      $("#" + defaults.deleteDiv).hide();
    }
  });
});

Thirdly, we need to add some code to delete tasks when they are dropped in the delete area.

// Adding drop function to delete div
$("#" + options.deleteDiv).droppable({
  drop: function(event, ui) {
    var element = ui.helper,
        css_id = element.attr("id"),
        id = css_id.replace(options.taskId, ""),
        object = data[id];

    // Removing old element
    removeElement(object);

    // Updating local storage
    delete data[id];
    localStorage.setItem("todoData", JSON.stringify(data));

    // Hiding Delete Area
    $("#" + defaults.deleteDiv).hide();
  }
});

Conclusion

The final code is available on GitHub. You can also check out the project’s live demo.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

  • abimaelmartell

    Ugly code :S, better use Backbone or something like that.
    BTW, bootstrap 3 is stable…

    • Pardeep Singh Basi

      I was thinking the same regarding the markup somewhat, semantically should be using unordered lists, spans, paragraphs (description) etc and not using a div for everything :/

      • http://shaumikthinks.blogspot.com/ Shaumik Daityari

        Something that can be improved upon. Definitely.

  • http://shaumikthinks.blogspot.com/ Shaumik Daityari

    Yes, we can definitely look forward to that. In fact, I see some forks in the GitHub repository, but no contribution good enough to merge with the code base.

  • RobinHood

    I think Shaumik has done a great job, the project ran immediately without any errors and his approach using JSON and local storage is very interesting. It would be great to see someone else build on this but it is a very good demo, easy to understand and get running.