DEV Community

Cover image for Let's build a Todo App Using Vue.js
Hugo
Hugo

Posted on

Let's build a Todo App Using Vue.js

When you are starting to learn a new programming language or framework, a TODO app is one of the first things you build, many people hate it, others love it, personally I think it’s a great option to give you an idea of how a language/framework works. So now that I'm learning Vue.js I decided to create one.

This is the final result:
TODO Vue Finished - JSFiddle

We will simply use Vue.js and NES.css for styles.

And because it is a simple app we will be using JSFiddle, here is the base project if you want to follow the tutorial:
So let’s start.

As we can see, we have three tabs: HTML, Vue, and CSS.
In the HTML tab, we only have a <link> to get the Press Start font and a div with a header inside. The important thing here is to point out that this is where we will have access to all the variables and methods that we will use in our Vue file.

<link href="https://fonts.googleapis.com/css?family=Press+Start+2P" rel="stylesheet">
<div id="app"> 
    <h1>TODOS:</h1>
</div>
Enter fullscreen mode Exit fullscreen mode

While in the Vue tab we have our Vue instance and this is where all the logic of the app will be.

new Vue({
  el: "#app",
  data: {
    todos: [
      { text: 'todo 1', done: false, id: Date.now() },
      { text: 'todo 2', done: false, id: Date.now() + 1 }
    ]
  },
  methods: {

  }
})
Enter fullscreen mode Exit fullscreen mode

Within the properties that we pass to Vue instance is el and it serves to indicate to Vue in which DOM element it is going to be mounted.
In the same way, we have data and guess what, it is used to store all the data that we use during the app, which in this case will only be an array of objects called todos.

Finally, we have methods, here we will put all the functions of the app.

As mentioned above, we can access all the variables defined in data inside the div and we can check it simply by adding a new variable called title and to access it we use the following syntax inside our HTML: {{variableName}}. Vue.js uses an HTML-based template syntax that allows you to declaratively bind the rendered DOM to the underlying Vue instance's data.

data: {
  title: 'TODOS',
  todos: [
    { text: 'todo 1', done: false, id: Date.now() },
    { text: 'todo 2', done: false, id: Date.now() + 1 }
  ]
}
Enter fullscreen mode Exit fullscreen mode
<div id="app">
  <h1>{{title}}:</h1>
</div>
Enter fullscreen mode Exit fullscreen mode

Before coding the app logic, let’s define what the app should do:

  • It should display a list of TODOs.
  • It should add TODOs.
  • It should delete TODOs.
  • It should mark TODOs as Done.

Showing TODOs

If you remember, we already have an array of objects in data to store TODOs.

data: {
  title: 'TODOS',
  todos: [
    { text: 'todo 1', done: false, id: Date.now() },
    { text: 'todo 2', done: false, id: Date.now() + 1 }
  ]
}
Enter fullscreen mode Exit fullscreen mode

We want to show the TODO text and the state on a list.

<ul>
  <li class="flex"></li> 
</ul>
Enter fullscreen mode Exit fullscreen mode

This is the base structure, now we simply have to show the array data and we can do it using the template syntax {{}}

<ul>
  <li class="flex"> {{todos[0].text}} </li> 
  <li class="flex"> {{todos[1].text}} </li>
</ul>
Enter fullscreen mode Exit fullscreen mode

But that is something repetitive, fortunately, Vue offers a directive called v-for that allows us to iterate over the elements of an array. So we can refactor the previous code in the following:

<ul> 
  <!-- Remember that todos is the name of our array --> 
  <li v-for="todo in todos" class="flex"> 
    <!-- and now, we can access to an item using todo--> 
    {{todo.text}} 
  </li>
</ul>
Enter fullscreen mode Exit fullscreen mode

Showing TODOs

Adding TODOs

To add TODOS we need an input field.

<input type=“text” class=“nes-input” placeholder="Add todo…">
Enter fullscreen mode Exit fullscreen mode

What we want to do is add a TODO every time the enter key is pressed. Vue allows us to listen for events using the syntax of v-on:some-event="doSomething", in this case, what we need is to use v-on:keyup.enter, here, keyup is the event we want to hear and enter the key that we need to trigger addTodo.

<input type="text" class="nes-input" placeholder="Add todo..." v-on:keyup.enter="addTodo">   
Enter fullscreen mode Exit fullscreen mode

Now, we just add our method addTodo

methods: {
  addTodo(event) {
    const text = event.target.value
    this.todos.push({text, done: false, id: Date.now()})
    event.target.value = ''
  }
}
Enter fullscreen mode Exit fullscreen mode

We almost finished, we just need to remove TODOs and mark them as done.

  • [x] It should display a list of TODOs
  • [x] It should add TODOs
  • [ ] It should remove TODOs
  • [ ] It should mark TODOs as Done

Remove TODOs

The first thing we do is add a button to remove TODOs:

<li v-for="todo in todos" class="flex">
  <!-- and now, we can access to an item using todo-->
  {{todo.text}}
  <div class="space"></div>
  <button class="nes-btn is-error padding">X</button>
</li>
Enter fullscreen mode Exit fullscreen mode

Now, what we have to do is listen for an onclick event, following the syntax for the events v-on:some-event="doSomething", we get this v-on:click ="removeTodo(todo.id)". We will call the removeTodo method and pass it the todo id. After that, we need to create that method.

<ul>
  <li v-for="todo in todos" class="flex">
    {{todo.text}}
    <div class="space"></div>
    <button class="nes-btn is-error padding" v-on:click="removeTodo(todo.id)">X</button>
  </li>
</ul>
Enter fullscreen mode Exit fullscreen mode
removeTodo(id) {
  this.todos = this.todos.filter(todo => todo.id !== id)
}
Enter fullscreen mode Exit fullscreen mode

Marking TODOs as done

Finally, we just need to mark the TODOs as done, for this, we will add a checkbox.

<ul>
  <li v-for="todo in todos" class="flex">
    <label>
      <input type="checkbox" class="nes-checkbox">
      <span>&nbsp</span>
    </label> 
    {{todo.text}}
    <div class="space"></div>
    <button class="nes-btn is-error padding" v-on:click="removeTodo(todo.id)">X</button>
  </li>
</ul>
Enter fullscreen mode Exit fullscreen mode

We put the checkbox inside a label and we add a span with a space because NES.css has a bug, and if you don't do it this way, it doesn't show the checkbox.
Well, here we have to do two things, first, we want that every time the checkbox is pressed, change the todo state from done: false todone: true and vice versa, so what do we need? you guessed, an event listener:

<input type="checkbox" class="nes-checkbox" v-on:click="check(todo)">
Enter fullscreen mode Exit fullscreen mode

And we add the check method:

check(todo){
  todo.done = !todo.done
}
Enter fullscreen mode Exit fullscreen mode

Marking TODOs as done
And that's it, we're done, right? well, not quite, there is a small bug in our app. Let's do this, let's change one of our TODOs from done: false a done: true

data: {
  title: 'TODOS',
  todos: [
    {text: 'todo 1', done: false, id: Date.now()},
    {text: 'todo 2', done: true, id: Date.now() + 1}
  ]
}
Enter fullscreen mode Exit fullscreen mode

Bug
There's the bug, our checkbox is not "synchronized" with the state of our TODO, so we have to use something called v-bind so that the checked property of the input is bind with the done property of our TODO, the syntax is this: v-bind:checked ="todo.done"

<input type="checkbox" class="nes-checkbox" v-on:click="check(todo)" v-bind:checked="todo.done">
Enter fullscreen mode Exit fullscreen mode

Bug Fixed
We're about to finish, we just need to add a little visual detail, we want that if the TODO is marked as done, strike the text like this:
Last details
This is pretty easy to do, we just use v-if and v-else

<ul>
  <li v-for="todo in todos" class="flex">
    <label>
      <input type="checkbox" class="nes-checkbox">
      <span>&nbsp</span>
    </label> 
    <!-- WE DELETE THIS: {{todo.text}} -->
    <del v-if="todo.done">{{ todo.text }}</del>
    <span v-else>{{ todo.text }}</span>
    <div class="space"></div>
    <button class="nes-btn is-error padding" v-on:click="removeTodo(todo.id)">X</button>
  </li>
</ul>
Enter fullscreen mode Exit fullscreen mode

And now we are done.
App finished

Refactoring our App

I know I said that we had finished, but we can refactor the code a little bit, Vue has something called v-model, which allows us to replace the v-bind and the v-on:click in our checkbox. According to the vue documentation this:

<input v-model=“searchText”>
Enter fullscreen mode Exit fullscreen mode

It is the same as this:

<input
  v-bind:value="searchText"
  v-on:input="searchText = $event.target.value"
>
Enter fullscreen mode Exit fullscreen mode

So let’s refactor our app. We replace v-on:click="check(todo)" and v-bind:checked="todo.done" for v-model="todo.done", and now we can also delete the check method.

<input type="checkbox" class="nes-checkbox" v-model="todo.done">
Enter fullscreen mode Exit fullscreen mode

Now we've really finished.

As you can see, Vue it's very simple and easy to learn. To get started, all you need is familiarity with HTML and some JavaScript. If you have experience with other frameworks such as React or Angular the process of learning Vue is much simpler.

I hope you liked it.

Top comments (4)

Collapse
 
shubhambattoo profile image
Shubham Battoo

NES css is a really cool css library. Good post.

Collapse
 
hugoliconv profile image
Hugo

Thanks!

Collapse
 
shubhambattoo profile image
Shubham Battoo

Did a version with vuex. With same layout.

codesandbox.io/embed/vue-todo-with...

Thread Thread
 
hugoliconv profile image
Hugo

Wow! That's pretty cool! You should do a post about it for people who are learning Vuex