Laravel8+Vue3+Bootstrap5實作TODO List ep08:用vue重新製作todo list畫面

Table of Contents

Table of Contents

使用Laravel8+Vue3+Bootstrap5實作TODO List網頁應用程式的系列文章第八篇。
本文將示範用vue重新建立todo list畫面。

教學影片

列表畫面

/resources/views/js/components/TodoListComponent.vue中實作列表的部分。

<template>
    <div>
        <h2>My Todo List Component</h2>
        <ul>
            <li v-for="(todo, index) in todos" :key="index">
                <label :for="`todo-${index}`">
                    <span>{{ todo.body }}</span>
                </label>
            </li>
        </ul>
    </div>
</template>
<script>
    export default {
        data: function() {
            return {
                todos: []
            }
        },
        mounted() {
            this.fetchTodos()
        },
        methods: {
            fetchTodos() {
                axios.get('/api/todos')
                .then( res => {
                    console.log(res)
                    console.log(res.status)
                    if (res.status == 200) {
                        this.todos = res.data
                    }
                })
                .catch( error => {
                    console.log(error)
                })
            },
        }
    }
</script>

這裡一樣使用迴圈來顯示每個項目,在vue中有v-for語法去做迴圈處理,只要在v-for中帶入todos(列表資料)就可以去處理todos中的每個項目。
主要是<span>{{ todo.body }}</span>這個部分中去顯示事項內容。
而想要在<template>中使用todos變數,則必須在data的函數中做設定,即在被回傳(return)的物件中加入todos屬性

好,那麼todos的資料是要如何得到呢?
可以從之前建立好的待辦事項列表API(api/todos)去取得後放到todos中。
所以在methods中實作一個方法fetchTodos,透過axios這個程式庫來執行http請求,請求成功後將結果放入todos裡面。

希望在開啟網頁時,就先去呼叫API取得資料,然後在畫面上顯示出來,所以在mounted中去呼叫API。 關於mounted可以參考這篇文章vue|生命週期

新增待辦事項

接著實作新增待辦事項的部分,其中包含一個新事項內容的輸入框、一個新增按鈕。

  • input
  • button
<template>
    <div>
        <h2>My Todo List Component</h2>
        <div>
            <input type="text" v-model="newTodo.body" placeholder="Enter new task">
            <button @click="addTodo">+</button>
        </div>
        <ul>
            <li v-for="(todo, index) in todos" :key="index">
                <label :for="`todo-${index}`">
                    <span>{{ todo.body }}</span>
                </label>
            </li>
        </ul>
    </div>
</template>
<script>
    export default {
        data: function() {
            return {
                todos: [],
                newTodo: {
                    body: ""
                }
            }
        },
        // ...
        methods: {
            // ...
            addTodo() {
                if (this.newTodo.body == '') {
                    return
                }
                axios.post('api/todo',{
                    body: this.newTodo.body
                }).then( res => {
                    console.log(res)
                    if (res.status == 201) {
                        // clear input
                        this.newTodo.body = ''
                        this.todos.unshift(res.data)
                    }
                }).catch( error => {
                    console.log(error)
                })
            },
        }
    }
</script>

使用v-model去讓輸入框的內容跟newTodo.body做綁定。
如此輸入的內容就等於是newTodo.body
那麼同樣地,一樣要在data的函數中去新增newTodo屬性。

再來是實作新增按鈕的點擊事件 @click="addTodo"
在addTodo中去呼叫之前做好的新增待辦事項API。
請求成功後,雖然資料庫裡成功添加了新的待辦事項,但是畫面依然還是舊資料的狀態,為了不去因重新載入資料而刷新整個畫面,這裏直接對畫面的資料做修改。
使用array.unshift方法,把新事項的資料加到todos矩陣中的第一項位置。
如此透過vue的功能,列表的資料會被快速更新且不會重新載入整個畫面。
這也是為什麼要用vue來重新製作todo list畫面的原因。

另外,關於if (res.status == 201) 的判斷式,這裏判斷回傳的請求狀態為201表示成功,所以稍微修改一下TodoController.phpstore函數的回傳值。

public function store(Request $request)
{
    $newTodo = new Todo();
    $newTodo->body = $request->body;
    $newTodo->save();
    return response($newTodo, Response::HTTP_CREATED);
}

如果不想修改的話,請把判斷式改為if (res.status == 200)

更新待辦事項的完成狀態

接著實作狀態更新的部分。
在每個項目中加入完成/未完成刪除的按鈕。

<template>
    <div>
        <!-- ... -->
        <ul>
            <li v-for="(todo, index) in todos" :key="index">
                <!-- ... -->
                <div>
                    <button @click="completed(todo)"> O </button>
                    <button @click="deleteTodo(todo)"> - </button>
                </div>
            </li>
        </ul>
    </div>
</template>
<style>
    .line-through {
        text-decoration: line-through;
    }
</style>
<script>
    export default {
        // ...
        methods: {
            // ...
            completed(todo) {
                axios.put(`api/todo/${todo.id}`, {
                    completed: !todo.completed
                }).then( res => {
                    console.log(res)
                    if (res.status == 200) {
                        // update todo
                        todo.completed = !todo.completed
                    }
                }).catch( error => {
                    console.log(error)
                })
            }
        }
    }
</script>

完成/未完成狀態的按鈕點擊事件為completed,主要呼叫先前建立的更新待辦事項API(api/todo/{todo_id})。
API的參數要放入新狀態(completed)的值,若狀態是未完成(completed = false)則要變成完成,若是完成(completed = true)則要變未完成。

相同地,資料庫的資料被成功更新後,畫面上的資料也要做更新。

if (res.status == 200) {
    // update todo
    todo.completed = !todo.completed
}

刪除待辦事項

最後實作刪除待辦事項的部分。

整個完整的程式碼如下。

<template>
    <div>
        <h2>My Todo List Component</h2>
        <div>
            <input type="text" v-model="newTodo.body" placeholder="Enter new task">
            <button @click="addTodo">+</button>
        </div>
        <ul>
            <li v-for="(todo, index) in todos" :key="index">
                <label :for="`todo-${index}`">
                    <span :class="[todo.completed ? 'line-through' : '']" >{{ todo.body }}</span>
                </label>
                <div>
                    <button @click="completed(todo)"> O </button>
                    <button @click="deleteTodo(todo)"> - </button>
                </div>
            </li>
        </ul>
    </div>
</template>
<style>
    .line-through {
        text-decoration: line-through;
    }
</style>
<script>
    export default {
        data: function() {
            return {
                todos: [],
                newTodo: {
                    body: ""
                }
            }
        },
        mounted() {
            this.fetchTodos()
        },
        methods: {
            fetchTodos() {
                axios.get('/api/todos')
                .then( res => {
                    console.log(res)
                    console.log(res.status)
                    if (res.status == 200) {
                        this.todos = res.data
                    }
                })
                .catch( error => {
                    console.log(error)
                })
            },
            addTodo() {
                if (this.newTodo.body == '') {
                    return
                }
                axios.post('api/todo',{
                    body: this.newTodo.body
                }).then( res => {
                    console.log(res)
                    if (res.status == 201) {
                        // clear input
                        this.newTodo.body = ''
                        this.todos.unshift(res.data)
                    }
                }).catch( error => {
                    console.log(error)
                })
            },
            completed(todo) {
                axios.put(`api/todo/${todo.id}`, {
                    completed: !todo.completed
                }).then( res => {
                    console.log(res)
                    if (res.status == 200) {
                        // update todo
                        todo.completed = !todo.completed
                    }
                }).catch( error => {
                    console.log(error)
                })
            },
            deleteTodo(todo) {
                axios.delete(`api/todo/${todo.id}`)
                .then( res => {
                    console.log(res)
                    if (res.status == 204) {
                        this.todos = this.todos.filter(item => item != todo)
                    }
                }).catch( error => {
                    console.log(error)
                })
            }
        }
    }
</script>

刪除的按鈕點擊事件為deleteTodo,在事件中呼叫先前建立的刪除待辦事項API(api/todo/{todo_id})。

http請求的方法是delete,資料庫的資料被成功刪除後,畫面上的項目也要做移除。
移除的處理是從todos裡移除被點擊的項目。
這裏的做法是用array.filter來將目標項目移除。


本篇用vue重新建立了todo list畫面。
功能雖然完成了,但是畫面卻是簡陋,下一回將導入bootstrap5將畫面修整好看一些。
那麼,我們下回見!