Learn Vue.js by building a to-do list app

Updates

  • October 21, 2016: Updated for Vue.js 2.0

Vue.js is a JavaScript Library that makes building web applications easy and reactive. In simple terms, it helps you convert data objects (model) in your application to HTML (view) and binds the DOM to the underlying data so that if your data changes, the page is updated accordingly.

Target Audience

This tutorial is targeted at developers who have a reasonable understanding of JavaScript and the DOM but do not have any experience with a framework.

People who could fit into this category are:

  • Anyone who can do some basic apps with HTML/CSS/JavaScript/jQuery
  • Beginners who have completed the Front-End section of Free Code Camp
  • Those who rely on jQuery exclusively for DOM manipulation

This tutorial is not for

  • Complete newbies to JavaScript. Please take the time to understand the core concepts first before learning abstractions. Free Code Camp is a great place to start.

  • People who already have in-depth knowledge of Vue.js, Angular.js, Ember.js, React or similar frameworks.

What we’ll be building

We will build a simple to-do list app with the following user stories:

  1. User can add a single to-do item at a time.
  2. User can delete a single item.
  3. User can clear the whole list.
  4. User can mark single items as completed.
  5. User can mark all items as completed.
Vue.js Beginners Tutorial

You can view the code and a live demo of the app on JSFiddle

I’m going to explain how each feature was implemented step-by-step so hopefully you should have a good understanding of how everything works together at the end of this tutorial.

If you get stuck while working through this tutorial…

Drop a comment below or send me an email. I’ll respond to your questions as soon as I can.

Getting Started

I will be using JSFiddle for this tutorial and I will embed snapshots of each step on this page. I highly recommend that you follow along, typing each step out all the way to the end.

Adding Vue.js to the page

First, we need to grab the Vue.js file and add it to our page. There are many ways to do this but the simplest way is to load the script from a Content Delivery Network (CDN).

<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.0.3/vue.js">
</script>

If you’re using JSFiddle like I am, paste the link to the script under External Resources and click the plus icon.

Vue.js Beginners Tutorial

Creating the Structure

Now that we’ve added Vue to the page, it’s time to setup our HTML. We will create a main container div with an id of events and a new Vue instance and then bind the two together.

Setting up the panel

In our section element with the class panel, we will insert three new elements:

  • A checkbox to mark all to-do items as completed.
  • An input field for adding new to-do items.
  • A button to clear all the items from the list.

The autofocus property on our input field focuses the element automatically when the page loads so that the user can begin to add items immediately without having to focus the input manually.

Setting up the list

Now that we have the skeleton for our top panel, let’s do the same for the list area where our tasks will be rendered.

Basically, each to-do item added to the list will be placed in the li tag with the label containing the text. The checkbox is to mark a single task as completed while the delete button is to remove an item from the list.

Adding the Styles

Since this article is not about CSS, I’m not going to dwell too much on this section. Feel free to copy and paste the styles.

Getting to the nitty-gritty

We can start demonstrating the awesome capabilities of Vue.js by giving our to-do app some functionality.

Before we start building our app, let’s go over the concepts we need to understand about Vue.js so that you can follow along.

The Vue Instance

We can create a new Vue instance by using the Vue() constructor function which lets you bind the data in your model to the view (HTML).

Setting the el key to #todo makes all the elements inside div#todo available to the Vue instance.

Two-way data binding using v-model

Directives are special attributes in Vuejs that you add to HTML elements. They are prefixed with v-. From the official documentation:

A directive’s job is to reactively apply side effects to the DOM when the value of its expression changes.

v-model is one such directive that is used to bind data from an input field to a value registered in our data object and vice versa. Let’s look at a quick demo of how this works:

The v-model attribute on the input field in the HTML is set to the name key we registered in the data object of our Vue instance. Now switch to the result tab and change the name in the input field. Notice that the name key is updated instantly, just like magic!

Note: I added <pre> {{ $data }}</pre> in the HTML to display the contents of the data object just below our input. The benefit of this is that it allows you to see how things are working under the hood which is helpful when debugging.

Listen to DOM Events with v-on

Vue provides another directive v-on which we can use to listen to DOM events and invoke a method when a specified event is triggered on an element.

If you use jQuery, you may have been used to doing something like this:

$("element").on("event", function () {
  //whatever
};

In Vue, the logic is similar but the syntax is far different.

Following on from the previous example, let’s add a button that will call a function that alerts the name in the input field when the button is clicked.

I added the v-on:click directive to listen for click events and call the alertName method when a click is triggered on the button. If you switch to the JavaScript tab, you will see that I have already registered alertName in the methods object.

Displaying data in Vue

Mustache-style bindings are used to display the data from a Vue instance on the page (the View). For example:

new Vue({
  el: "#demo",
  data: {
    value: "Hello, how are you?"
  }
});

To display the value property of our model in our view we need to add the ` {{ value }} ` string in our view like this:

<p> {{ value }} </p>

Here’s a proper demo:

Go ahead, change the text in the input field. You will see that the text on the screen is updated immediately. This is because the Vue links your data and the DOM together and the two are kept in sync.

Rendering lists with v-for

We can use the v-for directive to render a list of items based on an array in our Model. A special syntax is required in the form of item in names where names is the array in question while item is the current array element being iterated on.

Here’s an example:

If you update the contents of the names array, the list will also update accordingly. No need for DOM manipulation.

What is this?

this in the context of event handlers refers to the Vue instance invoking the handler and it is used to access the items in the data object.


new Vue({
  data: {
    lang: "JavaScript"
  },

  methods: {
    eventHandler: function() {
      //this refers to the Vue instance and it is used to access the lang key in the data object
      console.log(this.lang); //Javascript
    }
  }
});

Hopefully, all that makes sense. It’s time to combine the concepts we’ve learnt above to add some functionality to our application.

Feature 1 - Add a to-do item and display it on the page

The first feature we will implement in our app involves adding a single to-do item to the list through the input field in the top panel.

You will see that I’ve added two new properties to our data object:

  • newTask: This is where we will bind data from our input.
  • taskList: This is an array where our tasks will be stored.

Now, we will bind the value of the input field to the newTask key by setting a v-model directive on the input field to newTask. Secondly, we will add a v-on listener to the input to watch for the enter key and call a method called addTask when it is triggered. The syntax is below:

<input type="text" placeholder="What do you need to do?" autofocus class="text-input" v-model="newTask" v-on:keyup.enter="addTask">

Finally, let’s setup the addTask method. We will register it inside the methods object as previously alluded to.

methods: {
  addTask: function() {
    //trim() is used to remove whitespace from both ends of a string
    var task = this.newTask.trim();
    //if task is not an empty string
    if (task) {
    //Push an object containing the task to the taskList array
    this.taskList.push({
    text: task,
    checked: false
    });
    //Reset newTask to an empty string so the input field is cleared
    this.newTask = "";
    }
  }
}

I hope the comments were explanatory enough. When the addTask method is called, we will get the value of newTask, trim() it and save it in a new variable called task.

If task evaluates to true, which it would except if task is an empty string, push an object to the taskList array with two properties: text is set to the task variable while checked is set to false. checked is what we will use to track the “done” state of each task.

By setting this.newTask to an empty string, the value of our input will be cleared since they are bound together using the v-model directive.

You can switch to the results tab and add a few to-dos to see this in action. Notice that once you press the enter key, a new object containing the to-do item is pushed to the taskList array except if your to-do item is in fact an empty string.

Displaying our to-do items

Now that we can add add tasks to our taskList array conveniently, we need a way to render the each list item in the app. Remember we have already setup the skeleton for how each task will look like. All we need to do now is render the our list based on the contents of the taskList array.

The first step is to use the v-for directive to render the items in our taskList array.

<li v-for="task in taskList"></li>

In the earlier part of this tutorial, when we were setting up the skeleton of our app, I mentioned that the label element is where we will insert the text for each to-do item. So this is where we will place our mustache binding.

...
<label for="checkbox">{{ task.text }}</label>
...

All together now:

<ul class="list">
  
  <li v-for="task in taskList">
   
    <input type="checkbox" class="checkbox">
    <label for="checkbox">{{ task.text }}{% raw %}</label>
    <button class="delete">X</button>
  
  </li>
  
</ul>

What this means is that each task in our taskList array will be rendered inside an li element that contains a checkbox for marking the task as completed, a label for holding the text and a button for deleting items.

Here’s the current state of our app. Add some tasks to see it in action.

Feature 2 - Delete a single to-do item

We have already setup a button for deleting tasks which is displayes only when hovering on a task. Now, let’s register a new method that will be invoked when the button is clicked.

...
<button class="delete" v-on:click="removeTask(task)">X</button>
...
...
removeTask: function(task) {
  //$remove is deprecated in Vue.js 2.0. Do not use.
  this.taskList.$remove(task);
  
  //Do this instead.
  var index = this.taskList.indexOf(task); 
  this.taskList.splice(index, 1);
}
...

Our button element triggers the removeTask method which takes one argument, task, which is the current array element being operated on.

Inside removeTask we used a built-in array extension in Vue called $remove which helps us remove an element from an array and trigger view updates.

Update: $remove is deprecated in Vue 2.0.

We grab the index of this task and use the splice() array method to delete it from the array.

Try it out, add some tasks and click the X button that appears when hovering over an item.

Feature 3 - Clear the whole list

This next feature is quite straightforward to implement. We need a way to clear the whole list once the Clear List button on the top panel is clicked.

<button v-on:click="clearList">Clear List</button>

Now let’s register the clearList method.

...
clearList: function() {
  //Setting taskList to an empty array clears the whole list
  this.taskList = [];
}
...

Remember taskList is where our to-dos are stored so all we need to do to clear our list is to set taskList to an empty array.

Feature 4 - Mark a single item as completed

Marking tasks as done is a common feature in any half-decent to-do app and ours is not going to be any different.

Remember we already set the checked property on each task to false when we add a new to-do item. We need a way to toggle it to true when a task is checked and vice versa.

We can do this by binding our checkbox to the checked property using the v-model directive.

<input type="checkbox" class="checkbox" v-model="task.checked">

That’s it. When a checkbox is selected, the checked property is toggled to true and vice versa.

Try it out:

Now, we need to provide visual feedback in our app indicating that a task has been completed. Most to-do apps strike out the task and grey out the text so we’ll do something similar here.

I have added some rules in the css to grey out the label element when the to-do item in question has a class done on it’s li element.

...
.list li.done label {
  color: #d9d9d9;
  text-decoration: line-through;
}
...

So all we need to do is to add the done class to the li element when a task is checked and remove it when the task is unchecked. We will use another Vue directive to achieve this: v-bind.

Manipulating an element’s class is commonplace in Front-End development and with v-bind, we can dynamically toggle classes on an element based on computed properties in an object.

Let’s go ahead and toggle the done class on our li element by finding out if a task is checked or not.

<li v-for="task in taskList" v-bind:class="{done: task.checked}">
...

...
</li>

If task.checked returns true, the done class is enabled on the li element and vice versa.

Feature 5 - Mark all items as completed

For our final user story, we need a way to mark all to-do items as completed with one click. We will use the checkbox with the id of mark-all in the top panel to mark or unmark all items. Additionally, when we select all the checkboxes one by one, the #mark-all checkbox will also get selected.

Before we can select or unselect all items, we need a way to set the value of the #mark-all checkbox. We can do this by checking if there is at least one to-do item in our taskList array and if so, we will check if all the tasks are selected or not

We will watch for changes in our Vue instance using computed properties. Basically, they are used to change some data in the Vue instance based on some other data.

Let’s create a new property areAllSelected in our computed object that will continually watch for changes in our taskList array and change the value of #mark-all based on whether all the checked properties in the array return true.

...
computed: {
  areAllSelected: function() {
    //Check if every checked property returns true and if there is at least one to-do item
    return this.taskList.every(function(task) {
      return task.checked;
    }) && this.taskList.length > 0;
  },
}
...

The every() method tests whether all the elements in the taskList array passes the test implemented by the provided function and returns true if a truthy value is returned by the callback function otherwise it returns false.

Next, we will bind the checked property on the #mark-allto the value returned by areAllSelected like this:

<input type="checkbox" id="mark-all"  v-bind:checked="areAllSelected">

What this means that if areAllSelected returns true, the checked property will be enabled on the element, otherwise it will be disabled.

Try it out. Add a few tasks and mark them as done one by one. You will see that once all the items have been marked as completed, the #mark-all checkbox will also be selected.

The final thing we need to implement is to select or unselect all items with the #mark-all checkbox.

To do this, let’s register a new method called selectAll:

...
selectAll: function(task) {
  //targetValue is set to the opposite of areAllSelected
  var targetValue = this.areAllSelected ? false : true;
  //we use a for loop to set the checked state of all items to the target value
  for (var i = 0; i < this.taskList.length; i++) {
    this.taskList[i].checked = targetValue;
  }
}
...

Finally, let’s use the v-on directive on our #mark-all element to summon selectAll.

<input type="checkbox" id="mark-all" v-bind:checked="areAllSelected" v-on:click="selectAll">

That concludes my tutorial. I hope it has helped you learn the basics of Vue.js and how you can use it to build web interfaces. Would love to hear some feedback so please say what you think.

If you liked this article, please share with folks who might benefit as well. This will ensure that it reaches as many people as possible.

Also, consider subscribing to my email newsletter so you do not miss future tutorials.

Thanks for reading.