Todo Routes and Resources

Ninth Lesson in the Vue-Todo Series

Posted on August 8, 2016 in Laravel PHP Framework, Vue-Todo Series, vue.js

Todo Routes and resources

This post will be the last instalment of this series. We will now be implementing our Todo's routes and resources. Let's first look at the API and implement the routes and the controller's functions.

API

Add the following routes below $api->get('todos', 'TodosController@getUsersTodos'); in your app/Http/routes.php file:

$api->put('todo/toggleCompleted', 'TodosController@toggleCompleted');
$api->post('todo', 'TodosController@destroy');
$api->post('todo/new', 'TodosController@store');

Let's now create these functions in app/Api/Controllers/TodosController.php:

public function store(TodoRequest $request)
{
    $user = JWTAuth::parseToken()->authenticate();

    $todo = new Todo;
    $todo->user_id = $user->id;
    $todo->title = $request->get('title');
    $todo->save();

    return $this->response->created();
}

public function toggleCompleted(Request $request)
{
    $todo = Todo::findOrFail($request->get('id'));
    $todo->completed = !$todo->completed;
    $todo->save();

    return $this->response->noContent();
}

public function destroy(Request $request)
{
    $user = JWTAuth::parseToken()->authenticate();

    $todo = Todo::where('id', '=', $request->get('id'))->where('user_id', '=', $user->id)->first();
    $todo->delete();

    return $this->response->noContent();
}

We now need to create our TodoRequest. Create the new file here app/Api/Requests/TodoRequest.php and add the following:

<?php

namespace Api\Requests;

use Dingo\Api\Http\FormRequest;

class TodoRequest extends FormRequest
{
    public function authorize()
    {
        return true;
    }

    public function rules()
    {
        return [
            'title' => 'required|max:255',
        ];
    }
}

Ensure that the TodoRequest.php is being used within TodosController.php.

Let's now create our Vue Components.

Todo Vue Components

Let's first add the new navigation link. Add the following after the Home link within resources/assets/js/app/Components/Navigation/TopNav.vue:

<li v-link-active v-if="loggedIn">
    <a v-link="{path: '/todos', exact: true, activeClass: 'active'}">Todos</a>
</li>

We now need to create the route in our main.js file. Add the following after the '/auth/register' route config in resources/assets/js/app/main.js:

'/todos': {
    component: TodosPage
}

We will also need to import the file at the top of the page. Add the following to the bottom of the imports in the same main.js file:

import TodosPage from './Components/Todos/TodosPage.vue'

Now create the new Component here resources/assets/js/app/Components/Todos/TodosPage.vue and add the following:

<template>
    <div class="row">
        <div class="col-md-2">
            <div class="panel panel-default">
                <div class="panel-heading">
                    <h3 class="panel-title">Sidebar</h3>
                </div>
                <div class="panel-body">
                    <ul class="nav nav-pills nav-stacked">
                        <li role="presentation" v-bind:class="{'active': filterKey === 'all'}"
                            @click="filterKey = 'all'">
                            <a>All Todo's</a>
                        </li>
                        <li role="presentation" v-bind:class="{ 'active': filterKey === 'active' }"
                            @click="filterKey = 'active'">
                            <a>Active Todo's</a>
                        </li>
                        <li role="presentation" v-bind:class="{ 'active': filterKey === 'completed' }"
                            @click="filterKey = 'completed'">
                            <a>Completed</a>
                        </li>
                    </ul>
                </div>
            </div>
        </div>
        <div class="col-md-7">
            <div class="panel panel-default">
                <div class="panel-heading">
                    <h3 class="panel-title">Your Todo's</h3>
                </div>
                <div class="panel-body">
                    <p v-if="!hasTodos">
                        You currently have nothing Todo. Why not add something using the form on the right?
                    </p>
                    <div v-if="hasTodos">
                        <div class="row">
                            <div class="col-md-4 pull-right">
                                <input type="text" class="form-control" v-model="search" placeholder="Search Todo's" />
                            </div>
                        </div>
                        <hr />
                        <div class="row">
                            <div class="col-md-12">
                                <table class="table table-bordered table-striped">
                                    <thead>
                                        <th>What needs doing</th>
                                        <th>Added</th>
                                        <th>Functions</th>
                                    </thead>
                                    <tbody>
                                        <tr v-for="todo in todoFilter | filterBy search">
                                            <td v-if="todo.completed"><del>{{ todo.title }}</del></td>
                                            <td v-else>{{ todo.title }}</td>
                                            <td>{{ todo.created | moment "Do MMMM YYYY, h:mm:ss a" }}</td>
                                            <td>
                                                <button class="btn btn-xs btn-primary" @click="toggleCompleted(todo)">
                                                    <span class="glyphicon glyphicon-ok"></span>
                                                </button>
                                                <button class="btn btn-xs btn-danger" @click="deleteTodo(todo)">
                                                    <span class="glyphicon glyphicon-trash"></span>
                                                </button>
                                            </td>
                                        </tr>
                                    </tbody>
                                </table>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <div class="col-md-3">
            <div class="panel panel-default">
                <div class="panel-heading">
                    <h3 class="panel-title">New Todo</h3>
                </div>
                <div class="panel-body">
                    <form role="form" v-on:submit="attempt">
                        <div class="form-group">
                            <label for="text">What do you need to remember?</label>
                            <input type="text" class="form-control" id="text" v-model="newTodo.title" />
                        </div>

                        <button type="submit" class="btn btn-primary" :disabled="adding">
                            Add new todo
                        </button>
                    </form>
                </div>
            </div>
        </div>
    </div>
</template>

<script>
    export default {
        data () {
            return {
                todos: null,
                search: "",
                filterKey: 'all',
                newTodo: {
                    title: ''
                },
                adding: false
            }
        },

        methods: {
            attempt (e) {
                e.preventDefault()
                var that = this
                that.adding = true
                that.$http.post('/api/todo/new', this.newTodo).then((response) => {
                    that.getTodos()
                    that.newTodo.title = ''
                    that.adding = false
                }, (response) => {
                    that.adding = false
                })
            },

            getTodos () {
                var that = this
                that.$http.get('/api/todos').then((response) => {
                    that.todos = response.json()['data']
                }, (response) => {
                    console.log(response)
                })
            },

            toggleCompleted (todo) {
                var that = this
                that.$http.put('/api/todo/toggleCompleted', todo).then((response) => {
                    that.getTodos()
                }, (response) => {
                    console.log(response)
                })
            },

            deleteTodo (todo) {
                var that = this
                that.$http.post('/api/todo', todo).then((response) => {
                    that.todos.$remove(todo)
                }, (response) => {
                    console.log(response)
                })
            }
        },

        computed: {
            hasTodos () {
                if (this.todos === null || this.todos.length === 0) {
                    return false
                }
                else {
                    return true
                }
            },

            todoFilter () {
                return this[this.filterKey]
            },

            all () {
                return this.todos
            },

            active () {
                return this.todos.filter((todo) => {
                    return !todo.completed
                })
            },

            completed () {
                return this.todos.filter((todo) => {
                    return todo.completed
                })
            }
        },

        ready () {
            this.getTodos()
        },

        route: {
            activate (transition) {
                if (!this.$root.authenticated) {
                    transition.redirect('/')
                }
                else {
                    transition.next();
                }
            }
        }
    }
</script>

Great, one last thing. We need to add our final dependancy which is vue-moment. Run the following from your terminal:

npm install --save vue-moment

The Vue-Moment package adds the features of Moment.js for easy use within Vue. We are using this on the following line:

<td>{{ todo.created | moment "Do MMMM YYYY, h:mm:ss a" }}</td>

We now need to tell Vue to use Vue-Moment. Open resources/assets/js/app/main.js and add the following import below the other imports:

import VueMoment from 'vue-moment'

Now add the following below Vue.use(VueResource):

Vue.use(VueMoment)

Now run gulp from your terminal and log in as your test user and you should see your new Todos link. If you seeded your database with a few todos, you would see these here. If you didn't, you could try adding some. You can filter by completed and active from the right menu as well as marking todos as completed by using the button with a tick on it.

The end

This is now the end of the series. To further the application you could research how you could maybe double click to edit the todo item. You could also separate the TodosPage.vue into multiple components to separate some of the logic.

I hope you have enjoyed the series, and I will be bringing more in the future.


comments powered by Disqus