Build a Todo List App with VueJS
Tags: vue, javascript, html, tutorial
Published: 2020-09-11 18:54:50

VueJS is a modern Javascript framework that makes it easy to handle data flow, simply by including attributes in your HTML tags.

In this guide, we'll be building a simple todo list app to get up and running with VueJS.

## Setup and Installation

There are two ways to setup Vue: through a NodeJS project, or by including a script inside of your HTML file. Since we're just starting out, we'll use a script inside of our index.html file.

We can set up our index.html file like this.

```
<!DOCTYPE  html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Todooey - A Simple Todo List App</title>
    <link rel="stylesheet" href="style.css">
    <script src="https://unpkg.com/vue"></script>
  </head>
  <body>
    <div id="app">
    </div>
  </body>
</html>
```

In order to use Vue in our app, we need to create a  new instance of Vue. We can do this using another `script` tag before the closing `body` tag.

```
<script>
  new Vue( {
    el: '#app',
  });
</script>
```

Now, we're able to use Vue in our app!

## Creating Our App

Before we add the functionality to our app with Vue, we'll create the basic HTML/CSS structure with static content.

Inside of our HTML file, we'll create the Add Todo input, as well as the Todo list and each item
```
<div class="container">
  <h1 class="">My Todo List</h1>
  <div class="card">
    <div class="flex">
      <input placeholder="Add new todo" />
        <button>Add</button>
    </div>
  </div>
  <div class="card">
    <div class="card-inner">
      <h2>Todo</h2>
      <ul class="list">
        <li class="list-item">
          <div class="list-item-toggle"></div><span>Wash the car</span>
          <div class="list-item-delete">X</div>
        </li>
      </ul>
    </div>
  </div>
</div>
```

Then, we'll add some basic styling to our app inside our `style.css` file.

```
html,
body {
  margin: 0;
  padding: 0;
  background: #faffff;
  font-size: 16px;
}

* {
  box-sizing: border-box;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
        Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
  color: #3d4855;
}

h1,
h2,
h3,
h4,
h5,
h6 {
  margin-top: 0;
}

.container {
  padding: 24px 0;
  max-width: 700px;
  width: 100%;
  margin: 0 auto;
}

.card {
  border-radius: 4px;
  box-shadow: 1px 1px 40px -10px #31505f30, 0px 1px 2px 0px #31505f30;
  background: white;
  margin-bottom: 24px;
}

.card-inner {
  padding: 16px 24px;
}

.flex {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

input {
  border-radius: 4px;
  background: transparent;
  border: none;
  width: 100%;
  padding: 14px;
  font-size: 16px;
  border: 1px solid transparent;
  height: 100%;
  display: block;
  outline: none;
}

button {
  background: #4fc08d;
  padding: 10px 22px;
  border: none;
  color: white;
  border-radius: 4px;
  margin: 8px;
  font-size: 16px;
  cursor: pointer;
  box-shadow: 1px 1px 15px -2px #212c4430;
  transition: 0.15s;
}

button:hover {
  background: #42aa7b;
}

button:disabled {
  background: #e8e8e8;
  color: #555;
  box-shadow: none;
}

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

.list-item {
  padding: 12px 16px 12px 16px;
  border: 1px solid #e8e8e8;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: flex-start;
  margin-bottom: 6px;
  border-radius: 4px;
}

.list-item:first-child {
  border-top: 1px solid #e8e8e8;
}

.list-item-toggle {
  border: 1px solid #e8e8e8;
  border-radius: 999px;
  height: 21px;
  width: 21px;
  margin-right: 16px;
}

.list-item-delete {
  margin-left: auto;
  color: tomato;
  margin-top: -2px;
  font-weight: bold;
  text-decoration: none !important;
}

.list-item.completed {
  border: 1px solid #4fc08d;
}

.list-item.completed span {
  text-decoration: line-through;
}

.list-item.completed .list-item-toggle {
  background: #4fc08d;
  border: #4fc08d;
}
```


## Using Vue to Add Functionality

Great! Now that our app is styled, we can begin using Vue to create a dynamic todo list.

### Displaying Our Todo List
To display our todo list, we'll take advantage of Vue's 2-way data flow. Inside of our `script` tag, we'll use Vue's `data` object to create an array that will contain all our todo items.

```
<script>
  new Vue( {
    el: '#app',
    data: {
      items: [
		 {
		   id: 1,
		   name: 'Clean the fridge'
		 },
		 {
		   id: 2,
		   name: 'Walk the dogs'
		 },
      ]
    }
  });
</script>
```

Each todo item has a name and an ID, which will be used for removing items from the list later on.

Now that we have our data, we can display it in our list using the `v-for` attribute, which is basically a `forEach` loop that Vue uses.

```
<ul class="list">
  <li class="list-item" v-for="item in reversedItems">
    ...
    <span>{{ item.name }}</span>
    ...
  </li>
</ul>
```
Using the `v-for` attribute allows us to acces the `item` property. We can display the name by using the double handlebars syntax: `{{ item.name }}`.

### Adding Todo Items

Now that our items display properly, we can work on adding new items to the list. Using Vue's `methods` property, we can create a method that adds a new todo to the list.

First, let's create a new property inside our `data` object, called `newItem`.

```
<script>
  new Vue( {
    el: '#app',
    data: {
      newItem: '',
      items: [...]
    }
  });
</script>
```

This will be the value that we enter into the Add Todo input.

In order to make sure that what we type in our input updates the `newItem` value, we can take advantage of Vue's 2-way data flow, using the `v-model` attribute. This means that whatever value we enter into the input will be persisted to the `data` object.

```
<input v-model="newItem" placeholder="Add new todo"  />
```

Since we now have our `newItem` value stored, we can create a method to add that item to the list.

Beneath the `data` object, we'll create a new `methods` object with a function, `addItem`.

```
<script>
  new Vue( {
    el: '#app',
    data: {...},
    methods: {
      addItem: function() {
        this.items.push({
          id: this.items.length + 1,
          name: this.newItem,
          completed: false,
        });
        this.newItem = '';
      },
    },
  });
</script>
```
Basically, when this function is called, we're taking the `newItem` value and pushing it to the `items` array. The, we're clearing out the `newItem` value, which clears our Add Todo input.

Now, all we need to do is call the function when we click the Add button. We can use the `v-on` attribute, or the `@` symbol for short.

```
<button @click="addItem">Add</button>
```

Now, Vue will know to call the `addItem` function when this button is clicked.

As something a little extra, we can also disable the button is there is no value in the input, using the `:disabled` attribute. This tells Vue to apply the disabled attribute only if the expression inside the qoutes is true.

```
<button @click="addItem" :disabled="newItem.length === 0">Add</button>
```

### Marking Items as Complete

The final thing that we need to do is add the ability to mark our items as complete.

To do this, we'll add a new property to each item in our array: the `completed` property.

```
<script>
new Vue({
  el: '#app',
  data: {
    items: [{
      id: 1,
      name: 'Clean the fridge',
      completed: true,
    },
    {
      id: 2,
      name: 'Walk the dogs',
      completed: false,
    }]
  }
});
</script>
```

Vue once again provides us with an attribute to dynamically change the class of an element, based on data in the Vue instance.

So, we can go to our list item and add the `:class` attribute.

```
<li class="list-item" :class="{completed: item.completed}" v-for="item in reversedItems">
  ...
</li>
```

This tells Vue that it should apply the `completed` class to the `<li>` only if the item is completed (which we can tell by accessing the `item.completed` property.

Now, our completed items should have a green outline. However, we still need to be able to mark them complete if they are not.

To do this, we'll create another method, called `toggleComplete`.

```
<script>
  new Vue( {
    el: '#app',
    data: {...},
    methods: {
      addItem: function() {...},
      toggleComplete: function (item) {
        item.completed = !item.completed;
      }
    },
  });
</script>
```

Once we have our method, we can call it using the `@click` attribute that Vue provides.

```
<li class="list-item" :class="{completed: item.completed}" v-for="item in reversedItems">
  <div class="list-item-toggle" @click="toggleComplete(item)"></div>
  ...
</li>
```

Once again, we can pass in the `item` object as a prop to the function, because Vue allows us to access it via the `v-for` attribute.

Now, we can toggle each todo item between complete and uncomplete.

### Deleting Todo Items

The final thing we need to do is allow ourselves to delete todo items. Once again, we'll use a method to accomplish this.

```
<script>
  new Vue( {
    el: '#app',
    data: {...},
    methods: {
      addItem: function() {...},
      toggleComplete: function (item) {...},
      removeItem: function (itemID) {
        this.items = this.items.filter((item) => newItem.id!== itemID);
      } 
    },
  });
</script>
```

In this function, we're accessing the `itemID` prop (which is passed from the delete element) and setting the `items` property to a new array, without the item we just deleted.

Now, we can call the function from our delete element.

```
<li class="list-item" :class="{completed: item.completed}" v-for="item in reversedItems">
  ...
  <div class="list-item-delete" @click="removeItem(item.id)">X</div>
</li>
```

Tada! Now, we can succesfully delete our todo items!

## Final Thoughts

So that's it! We've just build a functioning todo application using Vue. We learned how to call methods, access data and update data, all without any JS DOM manipulation.

You can find the full code for this app on  [Github](https://github.com/jarodpeachey/vue-todo-list).

If you liked this tutorial, I'd appreciate it if you could  [buy me a coffee](https://www.buymeacoffee.com/jarodpeachey)! Or, follow me on [Twitter](https://twitter.com/jarodpeachey) ✌.