[001.5] Building a To-Do app with Vue.js

Putting together all the peices to build an app

Subscribe now

Building a To-Do app with Vue.js [08.18.2017]

Today we are on the final leg of building our app where we'll bringing everything we've learned together to complete the actual To-Do portion of Taskify. Let's begin by opening Todo.vue and writing some code.

Todo.vue

<template lang="pug">
md-layout(md-gutter)
  md-layout(md-column, md-gutter)
    form(novalidate, @submit.stop.prevent="submit")
      md-input-container(md-inline)
        label New Task
        md-input(v-model="task")
      md-button(@click="createTask", class="md-accent md-raised") Create Task
    md-layout(md-flex, class="taskRegion")
      TaskBox(
        v-for="task in tasks",
        :taskName="task.name",
        :username="username",
        :taskId="task.id",
        :key="task.id")
</template>

<script>
import TaskBox from './Taskbox'
import Store from '../Store'
export default {
  store: Store,
  data () {
    return {
      task: ''
    }
  },
  computed: {
    username () {return this.$store.state.username},
    tasks () {return this.$store.state.tasks}
  },
  components: {
    TaskBox
  },
  methods: {
    createTask () {
      // assemble info for create task request
      const dest = 'http://localhost:3000/task'
      const createTaskInfo = {username: this.username, taskname: this.task}
      const jwtHeader = {headers: {'Authorization': 'Bearer ' + localStorage.getItem('idToken')}}
      // create task
      this.$http.post(dest, createTaskInfo, jwtHeader)
      .then(result => {
        const taskData = result.data.data
        this.$store.dispatch('addTask', {name: taskData.taskname, id: taskData.id})
        this.task = ''
      })
      .catch(err => {
        console.log(err)
      })
    }
  }
}
</script>

<style lang="stylus">
form
  margin-left 1em
.taskRegion
  margin-top 1em
</style>

Let's take a look at the template tag first. At first, there isn't anything new. We initiate a grid with md-layout then we create our form where a user can create a new task. This should look familiar with the use of md-input-container, md-input, and v-model. However, let's shift to the next md-layout which contains an instance of Taskbox. We'll be writing out our Taskbox component soon enough but for now, turn your attention to v-for="task in tasks". To understand what is going on, v-for is a core Vue attribute that will iterate through a list and create a component for each item in that list. In this case, for each item in tasks, we will see another TaskBox appear in our UI. Now to explain what is happening with...

:taskName="task.name",
:username="username",
...

These are what we call props in Vue. This allows you to pass values down to a component so they can be worked with internally for said component. You will see more when we get to TaskBox.vue. You may also be wondering where task.name and task.id came from. In this case, the tasks list that v-for is iterating through comes from our tasks attribute in our Vuex Store. Look in the script tag under the computed property and you will see the actual tasks variable that our v-for is referencing. In this case, tasks is an array of objects that contain the information needed for a task. So going back to our v-for in the template, now you can hopefully see where we're getting task.name and task.id from. The last attribute :key is used by Vue to help remember the order of everything in the list for when things start getting modified.

Now let's turn our attention to the script tag. There shouldn't be anything new or unusual here. We imported the TaskBox and Vuex Store, connected the Store to the component, setup our data and computed values, then created the createTask method which works exactly the same as we have done previously by using Vue-Resource to call the api then dispatch the addTask action to store the new task information in our Vuex Store. That's it.

Let's check out TaskBox.vue now and add some code to it.

TaskBox.vue

<template lang="pug">
md-card#taskbox
  md-layout(md-gutter)
    md-layout(md-row, md-gutter, v-if="taskEditor === false")
      h1 {{taskName}}
      div#buttongroup
        md-button(class='md-fab md-primary md-mini', @click="startEditor")
          md-icon edit
        md-button(class='md-fab md-warn md-mini', @click="deleteTask")
          md-icon delete
    md-layout(md-row, md-gutter, v-else)
      form(novalidate, @submit.stop.prevent="submit")
        md-input-container(md-inline)
          label New Task Name
          md-input(v-model="newTaskName")
        md-button(class="md-primary", @click="editTask") Submit
        md-button(class="md-warn", @click="startEditor") Cancel
</template>

<script>
import Store from '../Store'
export default {
  store: Store,
  data () {
    return {
      taskEditor: false,
      newTaskName: ''
    }
  },
  props: [
    'taskName',
    'username',
    'taskId'
  ],
  methods: {
    startEditor () {
      this.taskEditor === false ? this.taskEditor = true : this.taskEditor = false
    },
    editTask () {
      const jwtHeader = {headers: {'Authorization': 'Bearer ' + localStorage.getItem('idToken')}}
      this.$http.put('http://localhost:3000/task', {
        taskId: this.taskId,
        newTaskName: this.newTaskName
      }, jwtHeader)
      .then(response => {
        this.$store.dispatch('editTask', {id: this.taskId, newTaskName: this.newTaskName})
        this.taskEditor = false
      })
      .catch(err => {
        console.log(err)
      })
    },
    deleteTask () {
      const dest = 'http://localhost:3000/task/' + this.username + '/' + this.taskId
      const jwtHeader = {headers: {'Authorization': 'Bearer ' + localStorage.getItem('idToken')}}
      this.$http.delete(dest, jwtHeader)
      .then(response => {
        this.$store.dispatch('removeTask', this.taskName)
      })
      .catch(err => {
        console.log(err)
      })
    }
  }
}
</script>

<style lang="stylus" scoped>
#taskbox
  padding 1em 1em 1em 1em
  margin-left 1em
  margin-top 1em
#buttongroup
  margin-left 3em
</style>

First, let's check out the script tag. As usual, we imported our Vuex Store then added it to our component. Then we added our data property. Nothing new here, but it's worth noting our taskEditor variable which we'll use to turn our taskEditor on and off using v-if. Next we have our props that you may recognize from our work in Todo.vue. After that we have defined our methods which contains startEditor, editTask and deleteTask. Looking through the methods, there is nothing new going on here. startEditor will be used to flip the taskEditor variable on and off. Meanwhile, editTask and deleteTask follow the previous pattern of calling the api then storing the result in Vuex Store.

Let's turn our attention to the template tag. This time we're using md-card to start off our box. From there, we have 2 different md-layouts that we're going to work with using v-if and v-else. The first contains our taskName and action buttons. The other contains a form that will call editTask when used.

The important thing is that there isn't anything particularly new going on here. We're just seeing everything we've already learned come together.

Conclusion

This should do it for the app! Go ahead, play around with it, modify it on your own and see what you can do with it. This was a lot of content to cover and my hope is that if anything, you have been able to obtain a broad view of how an app could be built with Vue. Please remember that everything shown is intended as merely one way to get things done. Vue is a flexible tool and can be conformed to your needs. Thank you for going through this tutorial and it is my sincere hope that Vue can one day serve you well in whatever project you may require.