この前書いたRailsにおけるVue.jsの使い方に続いてVue.jsを使ってTodoを作成するようにしてみた。
環境
- Rails 5.1.0.rc1
- Ruby 2.4.0p0
- Vue.js 2.0
webpack-dev-server
ロカルの場合、bin/webpack-dev-server
コメンドを使って「webpack」を起動するとjsファイルがビルドされる。
単一ファイルコンポーネント
Vue.jsには.vue
というファイルを通して単一ファイルコンポーネントが使える。
もちろん.js
で管理することもできるが、Vue.jsの公式サイトでみてみると
- グローバル宣言は全てのコンポーネントに一意な名前を強制すること
- シンタックスハイライトの無い文字列テンプレートと複数行 HTML の時に醜いスラッシュが強要されること
- CSS サポート無しだと、 HTML と JavaScript がコンポーネントにモジュール化されている間、これ見よがしに無視されること
- ビルド処理がないと Pug (前 Jade) や Babel のようなプリプロセッサよりむしろ、 HTML や ES5 JavaScript を制限されること
ということで単一ファイルコンポーネント
を使ってこれらの問題を解決すると書いてある。正直まだjsにコンポーネントを置くのとvueファイルに置くのの違いってよく実感できないんだけど。。(笑)
とりあえずRails new
をしてプロジェクトを作成したらapp/javascript/packs
ディレクトリーにDefaultでhello_vue.js
とapp.vue
ファイルが生成される。それの中身をみてみると
hello_vue.js
1
2
3
4
5
6
7
8
9
10
11
12
13
| import Vue from 'vue/dist/vue.esm'
import App from './app.vue'
document.addEventListener('DOMContentLoaded', () => {
document.body.appendChild(document.createElement('hello'))
const app = new Vue({
el: 'hello',
template: '<App/>',
components: { App }
})
console.log(app)
})
|
app.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| <template>
<div id="app">
<p></p>
</div>
</template>
<script>
module.exports = {
data: function () {
return {
message: "Hello Vue!"
}
}
}
</script>
<style scoped>
p {
font-size: 2em;
text-align: center;
}
</style>
|
vueファイルでtemplate
,script
,style
を使うことができる。つまり、vueファイル単位で簡単にモジュール化することができる。
でも今回作るTodoリストアプリケーションはscript
だけ使ってみる。
コンポーネント作成
まず、vue
ファイルを作成しよう。もちろん使うRails側のコントローラーとモデルは先に用意して置こう。
todo-list.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
| <script>
module.exports = {
template: '#todo-list',
data: function () {
return {
todos: [],
inputValue: '',
createMode: false
}
},
mounted: function () {
var that = this;
this.$http.get('/api/todos').then(
function(response){
that.todos = response.data;
});
},
directives: {
"focus": function(el) {
el.focus()
}
},
props: [],
methods: {
onOffCreateMode: function(boolean) {
this.createMode = boolean;
},
createTodo: function () {
var that = this;
this.$http.post('/api/todos', {title: this.inputValue}).then(
function(response){
that.todos.push(response.body);
that.inputValue = '';
});
this.onOffCreateMode(false);
},
DeleteTodo: function (todo) {
var that = this;
this.$http.delete('/api/todos/' + todo.id).then(
function(response){
that.todos = response.body;
});
}
}
}
</script>
|
todo.js
1
2
3
4
5
6
7
8
9
| import Vue from 'vue/dist/vue.esm'
import TodoList from './todo-list.vue'
Vue.use(require('vue-resource/dist/vue-resource.min'));
Vue.component('todo-list', TodoList)
new Vue({
el: '#todo',
})
|
index.html.slim
1
2
3
4
5
| #todo
todo-list
= render 'components/todos'
= javascript_pack_tag 'todo'
|
_todos.html.slim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| template#todo-list
div
ul
div v-show="!createMode"
button v-show="!createMode" v-on:click="onOffCreateMode(!createMode)"
| +
div v-show="createMode"
button v-on:click="onOffCreateMode(!createMode)"
| -
input[v-model="inputValue" v-on:keypress.enter="createTodo" v-focus]
li v-for="todo in todos"
|
button v-on:click="DeleteTodo(todo)" style="margin-left: 20px;"
| Delete
todo-item :todo="todo"
|
他のコンポーネントの追加
コンポーネントの作成はさっきの同じようにする。
todo-item.vue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
| <script>
module.exports = {
template: '#todo-item',
data: function () {
return {
todoItems: [],
inputValue: '',
createMode: false
}
},
mounted: function () {
var that = this;
this.$http.get('/api/todo_items?todo_id=' + this.todo.id).then(
function(response){
that.todoItems = response.data;
});
},
directives: {
"focus": function(el) {
el.focus()
}
},
props: ['todo', 'item'],
methods: {
onOffCreateMode: function(boolean) {
this.createMode = boolean;
},
createItem: function () {
var that = this;
this.$http.post('/api/todo_items', {title: this.inputValue, todo_id: this.todo.id}).then(
function(response){
that.todoItems.push(response.body);
that.inputValue = '';
});
this.onOffCreateMode(false);
},
DeleteItem: function (item) {
var that = this;
this.$http.delete('/api/todo_items/' + item.id + '?todo_id=' + this.todo.id).then(
function(response){
that.todoItems = response.body;
});
}
}
}
</script>
|
jsファイルはさっきのtodo.js
に追加する形で。
todo.js
1
2
3
4
5
6
7
8
9
10
11
| import Vue from 'vue/dist/vue.esm'
import TodoList from './todo-list.vue'
import TodoItem from './todo-item.vue' /* todo-itemをimportする */
Vue.use(require('vue-resource/dist/vue-resource.min'));
Vue.component('todo-list', TodoList)
Vue.component('todo-item', TodoItem) /* コンポーネントを作成 */
new Vue({
el: '#todo',
})
|
_todo_item.html.slim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| template#todo-item
div
div v-show="!createMode"
a href="javascript:void(0)" v-on:click="onOffCreateMode(!createMode)"
| 項目を追加
div v-show="createMode"
input[v-model="inputValue" v-on:keypress.enter="createItem" v-focus]
button v-on:click="onOffCreateMode(!createMode, todo)"
| -
ul
li v-for="item in todoItems"
|
button v-on:click="DeleteItem(item)" style="margin-left: 20px;"
| Delete
|
参考
単一ファイルコンポーネント