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>
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: {
}
})
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 }
]
}
<div id="app">
<h1>{{title}}:</h1>
</div>
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 }
]
}
We want to show the TODO text and the state on a list.
<ul>
<li class="flex"></li>
</ul>
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>
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>
Adding TODOs
To add TODOS we need an input field.
<input type=“text” class=“nes-input” placeholder="Add todo…">
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">
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 = ''
}
}
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>
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>
removeTodo(id) {
this.todos = this.todos.filter(todo => todo.id !== id)
}
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> </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>
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)">
And we add the check
method:
check(todo){
todo.done = !todo.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}
]
}
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">
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:
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> </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>
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”>
It is the same as this:
<input
v-bind:value="searchText"
v-on:input="searchText = $event.target.value"
>
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">
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)
NES css is a really cool css library. Good post.
Thanks!
Did a version with vuex. With same layout.
codesandbox.io/embed/vue-todo-with...
Wow! That's pretty cool! You should do a post about it for people who are learning Vuex