【Vue.js】toDoリストのアプリを作ってみる

ベースのHTMLの作成

まずは表示させる目標物の確認。
今回はこんな形をゴールにする。
f:id:tender-gorilla:20190502150204p:plain
インプットで入力した文字がAddボタンを押されることで下のリストに表示されるようになっている。
それではHTMLを作成する。

<div id="app">
  <h2>toDo List</h2>
  <form>
    <input type="text">
    <button>
      Add
    </button>
  </form>
  <ul>
    <li>
      <input type="checkbox">
      <span>Vue.jsの勉強</span>
      <button>Delete</button>
    </li>
  </ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>

これで見た目は同じようになった。

Addボタンを押すところまでのJSファイルの作成

続いてデータのやり取りをするためのJSファイルを作成。

var app = new Vue({
  el: '#app',  // idがappのelementに対しての動き
  data: {
    newItem: '',  // 入力した文字のデータを反映させる
    todos: []  // 入力したリストのデータを格納する
  },
  methods: {
    addItem: function(event) {
      // 後のことを考えtodosに格納するオブジェクトを作成しておく
      var todo = {
        item: this.newItem  // このnewItemはdataのnewItem
      };
      //作成したオブジェクトをtodosに入れる
      this.todos.push(todo);
    }
  }
})

ここでJavaScriptの復習。

配列名.push(要素)  // 配列の末尾に要素を追加

JSファイルで使ったメソッドたちをHTML側に反映していく。

<div id="app">
  <h2>toDo List</h2>
  <form>
    <input type="text" v-model="newItem">
    <button v-on:click="addItem">
      Add
    </button>
  </form>
  <ul>
    <li v-for="todo in todos">
      <input type="checkbox">
      <span>{{ todo.item}}</span>
      <button>Delete</button>
    </li>
  </ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>

入力してAddボタンを押すとリスト化できるところまでのベースは出来た…かと思いきや、出来ていない。
今回はformでデータのやり取りを行おうとしているので、このままだとAddボタンを押すとページの再読み込みを行ってしまう。
それを止めるため、formにsubmit.preventを追加。

<form v-on:submit.prevent>

これでAddボタンを押しても再読み込みされずに変更点が反映される。

見た目の編集

ベースは出来たので見た目を修正していく。

リストの・を消す

今のままだとリストを追加すると「・」が頭についてしまう。
見栄えがよろしくないので決しておく。

#app ul {
  list-style: none;
}

Add後に入力欄をクリア

Addボタンを押したあと次のタスクを追加しようとしても、前回入力されたのが残ったままになっている。
これをクリアするタイミングはアイテムを格納したあと。

var app = new Vue({
  el: '#app',
  data: {
    newItem: '',
    todos: []
  },
  methods: {
    addItem: function(event) {
      var todo = {
        item: this.newItem
      };
      this.todos.push(todo);
      this.newItem = '';  // newItemが空になり空欄化される
    }
  }
})

空欄のタスクは追加されない

今のままだと空欄のままAddすると空のタスクが追加されてしまう。
これを防ぐにはイベントの最初で入力の有無を確認する。

  methods: {
    addItem: function(event) {
      if(this.newItem == '') return;  // newItemが空なら最初に戻る
      var todo = {
        item: this.newItem
      };
      this.todos.push(todo);
      this.newItem = '';  // newItemが空になり空欄化される
    }
  }
})

checkを付けるとタスクに線が引かれる

せっかくチェックボックスをつけたから、活用したい。
チェックを付けた時にタスクに線を引くようにする。
見た目はこんな感じ。
f:id:tender-gorilla:20190502163358p:plain
チェックボックスにマークが入っていない時状態はfalse、チェックが付くとtrue扱いとなる。
そこで、todoにチェックボックスの状態を示すプロパティを追加する。

  methods: {
    addItem: function(event) {
      if(this.newItem == '') return;
      var todo = {
        item: this.newItem,
        isDone: false  // チェックボックスの状態
      };
      this.todos.push(todo);
      this.newItem = '';
    }
  }
})

これをHTML側にも追加する。

<li v-for="todo in todos">
  <input type="checkbox" v-model="todo.isDone">
  <span>{{ todo.item}}</span>
  <button>Delete</button>
</li>

isDoneがtrueになった時にcssが追加されるようにする。
この情報はisDoneの情報を読み取るだけなのでv-bindで良い。

<li v-for="todo in todos">
  <input type="checkbox" v-model="todo.isDone">
  <span v-bind:class="{ done: todo.isDone }">{{ todo.item}}</span>
  <button>Delete</button>
</li>

これで、doneというclassはisDoneがtrueの時だけ表示されるようになる。
cssを編集する。

#app li > span.done {
  text-decoration: line-through;
}

Deleteボタンで削除

最後の仕上げ、追加したタスクを削除する。
削除したいtodoオブジェクトが、todosの何番目なのかを把握しないといけない。
そこで、todosに格納されたtodoを表示する際にindexのデータもトスしておく。

<li v-for="(todo, index) in todos">
  <input type="checkbox" v-model="todo.isDone">
  <span v-bind:class="{ done: todo.isDone }">{{ todo.item}}</span>
  <button>Delete</button>
</li>

それではJSファイルに削除のメソッドを追加する。
配列から削除するにはspliceメソッドを使う。

deleteItem: function(index) {
  this.todos.splice(index, 1);
}

indexで削除するオブジェクトを、1は削除するデータの数を示す。
JS側が出来たのでHTMLに追記する。

<li v-for="(todo, index) in todos">
  <input type="checkbox" v-model="todo.isDone">
  <span v-bind:class="{ done: todo.isDone }">{{ todo.item}}</span>
  <button v-on:click="deleteItem(index)">Delete</button>
</li>

JSのfunction引数がindexになっているので、記載するのを忘れないよう注意。

完成形コード

HTML

<div id="app">
  <h2>toDo List</h2>
  <form v-on:submit.prevent>
    <input type="text" v-model="newItem">
    <button v-on:click="addItem">
      Add
    </button>
  </form>
  <ul>
    <li v-for="(todo, index) in todos">
      <input type="checkbox" v-model="todo.isDone">
      <span v-bind:class="{ done: todo.isDone}">{{ todo.item }}</span>
      <button v-on:click="deleteItem(index)">Delete</button>
    </li>
  </ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>

CSS

#app ul {
  list-style: none;
}
#app li > span.done {
  text-decoration: line-through;
}

JS

var app = new Vue({
  el: '#app',
  data: {
    newItem: '',
    todos: []
  },
  methods: {
    addItem: function(event) {
      if(this.newItem == '') return;
      var todo = {
        item: this.newItem,
        isDone: false
      };
      this.todos.push(todo);
      this.newItem = '';
    },
    deleteItem: function(index) {
      this.todos.splice(index, 1);
    }
  }
})

動作確認