サイトトップ

Director Flash 書籍 業務内容 プロフィール

HTML5テクニカルノート

Vue.js + ES6: TodoMVCをつくる 01 ー 項目の追加と削除および残り項目数表示


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

コード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>要素

<section class="todoapp">

</section>

script.js

const app = new Vue({

});
app.$mount('.todoapp');

コンストラクタVue()に渡すオブジェクトには、つぎのようにプロパティdatacomputedを与えました。dataに加えた配列の項目リスト(todos)に暫定でデータをひとつ納め、Todoリストに表示しましょう。computedの算出プロパティ(filteredTodos)は、この配列を返します(算出プロパティについては「Vue.js + ES6入門 05」03「算出プロパティを使う」参照)

script.js

const 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■データがリストに加えられた

図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)のほかふたつのプロパティ(idcompleted)を与えて、新たな項目としてプロパティ(todos)の配列に加えるのです(図003)。最後に、フィールドのテキストは空にします。

script.js

const 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

項目のチェックボックスにチェックすると、項目のテキストはグレーになって取り消し線が引かれます(前掲図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.js

const 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.js

const 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■残りの項目数が示される

図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.


作成者: 野中文雄
更新日: 2019年11月13日 構文をECMAScript 2015に改め、本文も修正。サンプルはCodePenに差し替えた。
作成日: 2017年06月26日


Copyright © 2001-2019 Fumio Nonaka.  All rights reserved.