DEV Community

Cover image for Build a Todo List App with VueJS
Jarod Peachey
Jarod Peachey

Posted on • Originally published at jarodpeachey.netlify.app

Build a Todo List App with VueJS

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.

If you liked this tutorial, I'd appreciate it if you could buy me a coffee! Or, follow me on Twitter ✌.

Top comments (2)

Collapse
 
ksalvione profile image
Kristel Salvione • Edited

Hello! Thanks for this tutorial.
Under the step "display our to do list" you say to add this line:
li class="list-item" v-for="item in reversedItems"

I notice after adding that line, Chrome devtools is saying that reversedItem is undefined, which it is. Is that definition something you had meant to include earlier in the tutorial? That error seems to be the reason my list in not displaying. I noticed in the Github you linked, reversedItems might be defined around line 55, is this correct? If so, could you expand on what that code is doing? Thanks!

Collapse
 
acao profile image
Rikki Schulte

Good question, I was wondering this myself, it appears in the repo but not in the article, just a computed() property

github.com/jarodpeachey/vue-todo-l...