JQ Blog

Vue.jsを使ってTodoリストを作成する

この前書いた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.jsapp.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ファイル作成

まず、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>
  • jsファイル作成

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"

他のコンポーネントの追加

コンポーネントの作成はさっきの同じようにする。

  • vueファイル作成

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ファイル編集

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

参考

単一ファイルコンポーネント