基本要求
Rails version: 5.1 以上
基礎建設
建立帶 vue 的 rails 專案
rails new rails-vue-todolist --skip-turbolinks --skip-spring --webpack=vue
除了 Rails 建立以外,還會有 webpack 的安裝,過程需要等待一下
cd rails-vue-todolist
git add .
git commit -m "Initialize Rails App"
快速建立 scaffold 的 CRUD
rails g scaffold Todolist item:string
別忘記 migrate, 生成 table
rake db:migrate
# 指定一下首頁
root "todolists#index"
可以先嘗試一下 vue 的 hello world
加入 javascript pack tag
# app/views/layout/application.html.erb
...
<%= javascript_pack_tag 'hello_vue' %>
...
然後在 todolists 的 index 設置進入點
# app/views/todolists/index.html.erb
<div id="hello"></div>
...
刷新頁面後,應該能在首頁看到 Hello Vue! 的字樣
大概知道這樣的關係後,就可以將 vue 逐步的動手改造 todolists 啦
首先,我們先把 app/views/todolists/index.html.erb
中的內容清空
只留下
# app/views/todolists/index.html.erb
<div id="todolists"></div>
這意味著 index 頁面,將不直接從 server side render, 直接用 vue 來做,而這是一個入口點的設置。
修改進入的主要檔案, 不用 demo file 了
# app/views/layout/application.js
# 把原本的 <%= javascript_pack_tag 'hello_vue' %> 替換如下
<%= javascript_pack_tag 'application' %>
宣告抓取 element todolists
# app/javascript/pack/application.js
import Vue from 'vue'
import App from '../todolists.vue'
document.addEventListener('DOMContentLoaded', () => {
const el = "#todolists"
const app = new Vue({
el,
render: h => h(App)
})
console.log(app)
})
並且生成 todolists.vue 在 app/javascript 之下
指令: touch app/javascript/todolists.vue
Index
接下來我們的 todolists/index, 就會直接從這個 vue 檔案渲染出來。
# app/javascript/todolists.vue
<template>
<div>
<h1>Todo Lists</h1>
<table>
<thead>
<tr>
<th>#</th>
<th>Item</th>
</tr>
</thead>
<tbody>
<tr v-for="todo in list" >
<td>{{ todo.id }}</td>
<td>{{ todo.item }}</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
data: function () {
return {
list: [
{ id: 1, item: "Foo" },
{ id: 2, item: "Bar" }
]
}
}
}
</script>
這時候,刷新頁面,應該可以看到我們 default 的數據 foo & bar 了。
再來要確保資料的生成,可以進 rails console 建立幾筆資料
Todolist.create(item: "Test1")
Todolist.create(item: "Test2")
再來我們期望能夠在畫面正確輸出 Test1 & Test2, 就要開始動手寫 vue method 了
# app/javascript/todolists.vue
<script>
export default {
data: function () {
return {
list: [
{ id: 1, item: "Foo" },
{ id: 2, item: "Bar" }
]
}
},
created: function() {
this.fetchTodoLists();
},
methods: {
fetchTodoLists: function() {
const resource = this.$resource('/todolists.json/{ id }');
resource.get().then(function(response){
this.list = response.data
});
}
}
}
這時候刷新頁面,你會發現什麼都沒改變,並且 console 出現
TypeError: this.$resource is not a function
因為這時我們還沒有引入這個 Vue 的 resource 套件,方便我們操作 http method
先安裝相關的套件,下指令
yarn add vue-resource
安裝完後修改 app/javascript/application.js 在頂部加上這兩行,把他import 進去
import VueResource from 'vue-resource'
Vue.use(VueResource);
刷新頁面,應該就會發現我們的 item 是從我們 DB 拉出來的吧?
完成 index lists 的部分了
Create & Delete
接下來我們做 Create 的部分
# app/javascript/todolists.vue
<template>
...
<input type="text" v-model="todo" class="form-control" autofocus="true">
<button @click="addTodo()" class="btn btn-primary" :disabled="!todo.length">Add Todo</button>
...
</template>
<script>
export default {
data: function () {
return {
todo: '',
list: [
{ id: 1, item: "Foo" },
{ id: 2, item: "Bar" }
]
}
},
...
......
</script>
操作一下,是不是發現有輸入框和按鈕了?而且當輸入框沒任何字串時,按鈕的樣式會是 disable
再來就是綁定 addTodo 的事件了,一樣是寫在 method
# app/javascript/todolists.vue
<script>
...
...
methods: {
...
addTodo(){
this.$http.post('todolists.json', { item: this.todo }, {})
.then((res) => this.fetchTodoLists(), this.todo = '')
.catch((error) => console.log('Got a problem' + error));
},
</script>
迫不及待按下 add Todo 了吧? 這時候你會收到 422 的 response, 是因為 csrf token 的保護,導致你送出的表單被否決了。
簡單的解決方法如下
# app/javascript/appliation.js
...
...
document.addEventListener('DOMContentLoaded', () => {
Vue.http.headers.common['X-CSRF-Token'] = document.getElementsByName('csrf-token')[0].getAttribute('content')
...
...
})
這樣一來 Create 就做好了吧? Delete 也是一樣意思,只是從發 POST 變成發 Delete,這邊就只放 code 了
# app/javascript/todolists.vue
<template>
<div>
<h1>Todo Lists</h1>
<input type="text" v-model="todo" class="form-control" autofocus="true">
<button @click="addTodo()" class="btn btn-primary" :disabled="!todo.length">Add Todo</button>
<table>
<thead>
<tr>
<th>#</th>
<th>Item</th>
<th>operate</th>
</tr>
</thead>
<tbody>
<tr v-for="todo in list" >
<td>{{ todo.id }}</td>
<td>{{ todo.item }}</td>
<td>
<button @click="deleteTodo(todo.id)" class="btn btn-primary">Delete Todo</button>
</td>
</tr>
</tbody>
</table>
</div>
</template>
<script>
export default {
data: function () {
return {
todo: '',
list: [
{ id: 1, item: "Foo" },
{ id: 2, item: "Bar" }
]
}
},
created: function() {
this.fetchTodoLists();
},
methods: {
addTodo(){
this.$http.post('todolists.json', { item: this.todo }, {})
.then((res) => this.fetchTodoLists(), this.todo = '')
.catch((error) => console.log('Got a problem' + error));
},
deleteTodo(todo_id){
this.$http.delete('todolists/'+ todo_id +'.json')
.then((res) => this.fetchTodoLists())
.catch((error) => console.log('Got a problem' + error));
},
fetchTodoLists: function() {
const resource = this.$resource('/todolists.json/{ id }');
resource.get().then(function(response){
this.list = response.data
});
}
}
}
</script>
Edit & Update
實做方式有兩種
- vue routes(換頁)
- SPA(不換頁)
這邊我用 SPA 的方式去做,直接點擊文字可以出現一個 輸入框以及更新的按鈕,詳細實做就看我的 github 參考了