HTML5テクニカルノート
Vue.js + ES6: TodoMVCをつくる 01 ー 項目の追加と削除および残り項目数表示
- ID: FN1706008
- Technique: HTML5 / JavaScript
- Library: Vue.js 2.6.10
Vue.js公式サイトの「例」に、正味120行足らずのコードでつくられた「TodoMVC の例」があります。Todoリストというお題はすでに「Vue.js + ES6入門」01〜11で扱いました(サンプル001)。リストとしての機能にさほど違いはありません。けれど、「TodoMVC の例」はやたらとボタンが並んでおらず、すっきりとしたインターフェイスです。「Vue.js + ES6入門」にはない工夫がコードにも施されています。そこで、これから5回に分けてその中身を解説します。今回は、ごく基本的なリスト項目の追加と削除および残り項目数の表示です(「Vue.js + ES6入門」のおさらいも兼ねます)。
サンプル001■Vue.js + ES6: props validation and key attribute
See the Pen Vue.js + ES6: props validation and key attribute by Fumio Nonaka (@FumioNonaka) on CodePen.
01 HTMLドキュメントの組み立て
HTMLドキュメントの組み立てから確かめましょう。スクリプト(JS)とスタイル(CSS)は、別ファイルにします。まず、<head>
要素には、つぎのようにCDNからVue.jsライブラリを<script>
要素で読み込み、スタイルは「TodoMVC の例」のJSFiddleからCSSのURLを<link>
要素に加えました。
<head>要素<link href="https://unpkg.com/todomvc-app-css@2.2.0/index.css" rel="stylesheet"> <script src="https://unpkg.com/vue/dist/vue.js"></script>
つぎに、<body>
要素です。Vueのアプリケーションとデータバインディングしたり、メソッドを呼び出しているところは、このあとJavaScriptコードとともに解説してゆきます。v-show
ディレクティブを加えた要素は、与えた値がtrue
と評価されたときに表示されます。したがって、配列の項目リスト(todos
)にデータがなければ表示されないということです。外部JavaScriptファイル(script.js
)を読み込む<script>
要素は最後に加えました。
<body>要素<section class="todoapp"> <header class="header"> <h1>todos</h1> <input class="new-todo" autofocus autocomplete="off" placeholder="What needs to be done?" v-model="newTodo" @keypress.enter="addTodo"> </header> <section class="main" v-show="todos.length" v-cloak> <ul class="todo-list"> <li v-for="todo in filteredTodos" class="todo" :key="todo.id" :class="{completed: todo.completed}"> <div class="view"> <input class="toggle" type="checkbox" v-model="todo.completed"> <label>{{todo.title}}</label> <button class="destroy" @click="removeTodo(todo)"></button> </div> </li> </ul> </section> <footer class="footer" v-show="todos.length" v-cloak> <span class="todo-count"> <strong>{{remaining}}</strong> items left </span> </footer> </section> <footer class="info"> <p>Double-click to edit a todo</p> </footer> <script src="script.js"></script>
v-cloak
ディレクティブは、つぎの<style>
要素と組み合わせて使っています。Vueインスタンスのコンパイルが済むまで有効なので、ディレクティブを与えた要素はデータがバインドされない間は表示(display
プロパティ)されません。
<style>要素[v-cloak] { display: none; }
<body>
要素にスタイルが与えられて、Todoリストの静的なレイアウトになります(図001)。HTMLドキュメントの中身をまとめて以下のコード001に掲げます。
図001■レイアウトされたTodoリスト
コード001■スクリプトとスタイルは外部ファイルに分けたHTMLドキュメント
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Sample</title>
<link href="https://unpkg.com/todomvc-app-css@2.2.0/index.css" rel="stylesheet">
<style>
[v-cloak] {
display: none;
}
</style>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
</head>
<body>
<section class="todoapp">
<header class="header">
<h1>todos</h1>
<input class="new-todo"
autofocus
autocomplete="off"
placeholder="What needs to be done?"
v-model="newTodo"
@keypress.enter="addTodo">
</header>
<section class="main" v-show="todos.length" v-cloak>
<ul class="todo-list">
<li v-for="todo in filteredTodos"
class="todo"
:key="todo.id"
:class="{completed: todo.completed}">
<div class="view">
<input class="toggle" type="checkbox" v-model="todo.completed">
<label>{{todo.title}}</label>
<button class="destroy" @click="removeTodo(todo)"></button>
</div>
</li>
</ul>
</section>
<footer class="footer" v-show="todos.length" v-cloak>
<span class="todo-count">
<strong>{{remaining}}</strong> items left
</span>
</footer>
</section>
<footer class="info">
<p>Double-click to edit a todo</p>
</footer>
<script src="script.js"></script>
</body>
</html>
02 アプリケーションに定めた項目のデータをTodoリストに表示する
VueインスタンスはHTMLドキュメントのDOM要素に関連づけます。そのとき、Vue()
コンストラクタに渡すオブジェクトのel
プロパティを使わずに(el
プロパティについては「Vue.js +ES6入門 01」03「データを要素に表示する」参照)、つぎのように$mount()
メソッドでも加えられます。
<body>要素script.js<section class="todoapp"> </section>
const app = new Vue({ }); app.$mount('.todoapp');
コンストラクタVue()
に渡すオブジェクトには、つぎのようにプロパティdata
とcomputed
を与えました。data
に加えた配列の項目リスト(todos
)に暫定でデータをひとつ納め、Todoリストに表示しましょう。computed
の算出プロパティ(filteredTodos
)は、この配列を返します(算出プロパティについては「Vue.js + ES6入門 05」03「算出プロパティを使う」参照)
script.jsconst app = new Vue({ data: { todos: [ { id: 0, title: 'test todo', completed: false } ], newTodo: '' }, computed: { filteredTodos() { return this.todos; } } });
<body>
要素にはつぎのようにv-for
ディレクティブで、算出プロパティ(filteredTodos
)により得た項目リストの配列からデータを取り出して、<li>
要素として差し込むように定めてありました(v-for
ディレクティブについては「Vue.js + ES6入門 03」01「項目のデータを複数にする」参照)。項目(todo
)のプロパティのうち、済んだかどうか(completed
)がv-model
ディレクティブでチェックボックス(<input>
要素)のオン/オフとバインドされています。項目のテキスト(title
)は<label>
要素に差し込まれます。なお、:class
の構文については次項で触れます。
<body>要素<ul class="todo-list"> <li v-for="todo in filteredTodos" class="todo" :key="todo.id" :class="{completed: todo.completed}"> <div class="view"> <input class="toggle" type="checkbox" v-model="todo.completed"> <label>{{todo.title}}</label> </div> </li> </ul>
key
特別属性は、v-for
ディレクティブで要素のリストを動的につくるとき、それらを互いに識別するための一意な値です(ガイド「key」参照)。要素の順序もこの値で決まります。v-for
ディレクティブにはkey
属性を加えることが強く推奨されています(「キー付き v-for」参照)。これでアプリケーションの項目リストのプロパティ(todos
)に加えたデータがTodoリストに表示されます(図002)。
図002■データがリストに加えられた
03 フィールドに入力したテキストを項目として加える
<body>
要素のテキスト入力フィールド(<input>
要素)には、v-model
ディレクティブでアプリケーションのプロパティ(new-todo
)がバインドされていました。入力したテキストを、アプリケーションに定めるメソッド(addTodo()
)で項目に加えられるようにしましょう。入力ボタンを加えるのでなく、フィールド内の[enter]/[return]キーでメソッドが呼び出されるよう、v-on
ディレクティブが加えられています。@
はv-on
の省略した書き方です。キーボードイベントとしてkeypress.enter
を与えました(「キー修飾子」参照)。[*1]
<body>要素<header class="header"> <h1>todos</h1> <input class="new-todo" autofocus autocomplete="off" placeholder="What needs to be done?" v-model="newTodo" @keypress.enter="addTodo"> </header>
フィールドに入力したテキストを項目に加えるメソッド(addTodo()
)は、つぎのようにアプリケーションのmethods
プロパティに定めます。入力フィールドのテキストは、プロパティ(newTodo
)にバインドしてありました。その値の配列が空でなければString.trim()
メソッドで両端の空白を除いて、改めて値があることを確かめています。そのうえで、オブジェクトにテキスト(title
)のほかふたつのプロパティ(id
とcompleted
)を与えて、新たな項目としてプロパティ(todos
)の配列に加えるのです(図003)。最後に、フィールドのテキストは空にします。
script.jsconst app = new Vue({ data: { todos: [ /* { id: 0, title: 'test todo', completed: false } */ ], newTodo: '' }, methods: { addTodo() { const value = this.newTodo && this.newTodo.trim(); if (!value) { return; } this.todos.push({ id: this.todos.length, title: value, completed: false }); this.newTodo = ''; } } });
これで、入力フィールドのテキストが[enter]/[return]キーで、Todoリストの項目として加わります(図003)。項目の頭には済んだかどうかのチェックボックスが添えられ、項目にマウスポインタを重ねると削除ボタンが表れます。削除の処理はまだ備わっていません。
図003■テキストフィールドに入力して[enter]/[return]で項目に加わる
項目のチェックボックスにチェックすると、項目のテキストはグレーになって取り消し線が引かれます(前掲図003)。これは、つぎのようにチェックボックス(<input>
要素)にv-model
ディレクティブでバインドしたプロパティ(completed
)を、v-bind:class
の構文で<li>
要素のクラスと結びつけたからです(v-bind:class
については「Vue.js + ES6入門 02」03「バインディングでクラス属性を動的に変える」参照)。コロン:
はv-bind
の省略した書き方です。
<body>要素<li v-for="todo in filteredTodos" :class="{completed: todo.completed}"> <div class="view"> <input class="toggle" type="checkbox" v-model="todo.completed"> </div> </li>
[*1]「TodoMVC の例」では、キーボードイベントにkeyup
を用いています。けれども、このイベントでは、日本語変換を[enter]/[return]で確定しただけでもリスナー関数が呼び出されてしまいます。keypress
イベントは変換の確定では起こらないため、そのあと[enter]/[return]キーを押してはじめてリスナー関数が呼び出されます。
04 項目をボタンで削除する
v-for
ディレクティブでつくられるリスト項目(<li>
要素)にはボタン(<button>
要素)が加えられ、マウスポインタを重ねると右端に表れます(前掲図003)。v-on
ディレクティブ(@
)には、つぎのようにclick
イベントのリスナー(removeTodo()
)が与えてありましたので、アプリケーションのメソッドとして定めましょう。
<body>要素<ul class="todo-list"> <li v-for="todo in filteredTodos" :class="{completed: todo.completed}"> <div class="view"> <button class="destroy" @click="removeTodo(todo)"></button> </div> </li> </ul>
Vue()
コンストラクタに渡すオブジェクトのmethods
に、メソッド(removeTodo()
)をつぎのように定めます。項目リストのプロパティ(todos
)の配列から、メソッドArray.indexOf()
によりその項目のインデックスを調べ、Array.splice()
メソッドでエレメントを除いています。これで、削除ボタンを押した項目はリストから消されます。
script.jsconst app = new Vue({ methods: { removeTodo(todo) { this.todos.splice(this.todos.indexOf(todo), 1); } } });
05 残り項目を数える
<body>
要素のフッター(<footer>
要素)には、チェックされていない残り項目数を示すつもりのプロパティ(remaining
)が{{}}
でバインドしてありました。アプリケーションには算出プロパティとして加えます。
<body>要素<footer class="footer" v-show="todos.length" v-cloak> <span class="todo-count"> <strong>{{remaining}}</strong> items left </span> </footer>
Vue()
コンストラクタに渡すオブジェクトのcomputed
には、算出プロパティ(remaining
)をつぎのように定めます。また、methods
に新たなメソッド(getActive()
)を加え、算出プロパティの関数から呼び出しています。メソッドは引数に受け取った項目リストの配列から、Array.filter()
メソッドで完了のプロパティ(completed
)がfalse
の項目をエレメントに納めた配列にして戻します。算出プロパティは、そのArray.length
プロパティ値を返せば、残り項目数が示せるのです。
script.jsconst app = new Vue({ computed: { remaining() { const todos = this.getActive(this.todos); return todos.length; } }, methods: { getActive(todos) { return todos.filter((todo) => !todo.completed ); } } });
これでTodoリストに項目を追加したり、削除できるほか、チェック済みでない残り項目数も示せるようになりました(図004)。ここまで書き上げたJavaScriptファイル(script.js
)の中身は、以下のコード002にまとめます。併せて、サンプル002としてCodePenに掲げました。
図004■残りの項目数が示される
コード002■項目の追加・削除と残り項目数の表示ができるTodoリスト
script.js
const app = new Vue({
data: {
todos: [],
newTodo: ''
},
computed: {
filteredTodos() {
return this.todos;
},
remaining() {
const todos = this.getActive(this.todos);
return todos.length;
}
},
methods: {
addTodo() {
const value = this.newTodo && this.newTodo.trim();
if (!value) {
return;
}
this.todos.push({
id: this.todos.length,
title: value,
completed: false
});
this.newTodo = '';
},
removeTodo(todo) {
this.todos.splice(this.todos.indexOf(todo), 1);
},
getActive(todos) {
return todos.filter((todo) =>
!todo.completed
);
}
}
});
app.$mount('.todoapp');
サンプル002■Vue.js: Vue.js + ES6: Vue TodoMVC 01
See the Pen Vue.js + ES6: Vue TodoMVC 01 by Fumio Nonaka (@FumioNonaka) on CodePen.
- Vue.js + ES6: TodoMVCをつくる 01 ー 項目の追加と削除および残り項目数表示
- Vue.js + ES6: TodoMVCをつくる 02 ー データをローカルに保存する
- Vue.js + ES6: TodoMVCをつくる 03 ー 表示する項目をフィルタで切り替える
- Vue.js + ES6: TodoMVCをつくる 04 ー チェックをまとめてオン/オフしたり削除する
- Vue.js + ES6: TodoMVCをつくる 05 ー 項目のテキストを再編集できるようにする
作成者: 野中文雄
更新日: 2019年11月13日 構文をECMAScript 2015に改め、本文も修正。サンプルはCodePenに差し替えた。
作成日: 2017年06月26日
Copyright © 2001-2019 Fumio Nonaka. All rights reserved.