サイトトップ

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

HTML5テクニカルノート

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


Vue.js公式サイトの「例」に、正味120行足らずのコードでつくられた「TodoMVC の例」があります。Todoリストというお題はすでに「Vue.js入門」01〜07で扱いました(サンプル001)。リストとしての機能にさほど違いはありません。けれど、「TodoMVC の例」はやたらとボタンが並んでおらず、すっきりとしたインターフェイスです。「Vue.js入門」にはない工夫がコードにも施されています。そこで、これから5回に分けてその中身を解説します。今回は、ごく基本的な項目の追加と削除および残り項目数の表示です(「Vue.js入門」のおさらいも兼ねます)。

サンプル001■Vue.js: Todo List with Adding or Removing Items

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.0.6/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"
			@keydown.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.0.6/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"
			@keydown.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入門 01」03「データを要素に表示する」参照)、つぎのように$mount()メソッドでも加えられます。

<body>要素

<section class="todoapp">

</section>

script.js

var app = new Vue({

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

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

script.js

var app = new Vue({
	data: {
		todos: [
			{
				id: 0,
				title: 'test todo',
				completed: false
			}
		],
		newTodo: ''
	},
	computed: {
		filteredTodos: function() {
			return this.todos;
		}
	}
});

<body>要素にはつぎのようにv-forディレクティブで、算出プロパティ(filteredTodos)により得た項目リストの配列からデータを取り出して、<li>要素として差し込むように定めてありました(v-forディレクティブについては「Vue.js入門 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を加えることが推奨されています。これでアプリケーションの項目リストのプロパティ(todos)に加えたデータがTodoリストに表示されます(図002)。

図002■データがリストに加えられた

図002

03 フィールドに入力したテキストを項目として加える

<body>要素のテキスト入力フィールド(<input>要素)には、v-modelディレクティブでアプリケーションのプロパティ(new-todo)がバインドされていました。入力したテキストを、アプリケーションに定めるメソッド(addTodo())で項目に加えられるようにしましょう。入力ボタンを加えるのでなく、フィールド内の[enter]/[return]キーでメソッドが呼び出されるよう、v-onディレクティブが加えられています。@v-onの省略した書き方です。キーボードイベントとしてkeydown.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"
		@keydown.enter="addTodo">
</header>

フィールドに入力したテキストを項目に加えるメソッド(addTodo())は、つぎのようにアプリケーションのmethodsプロパティに定めます。入力フィールドのテキストは、プロパティ(newTodo)にバインドしてありました。その値が空でなければString.trim()メソッドで両端の空白を除いて、改めて値があることを確かめています。そのうえで、オブジェクトにテキスト(title)のほかふたつのプロパティ(idとcompleted)を与えて、新たな項目としてプロパティ(todos)の配列に加えるのです(図003)。最後に、フィールドのテキストは空にします。

script.js

var app = new Vue({
	data: {
		todos: [
			/* {
				id: 0,
				title: 'test todo',
				completed: false
			} */
		],
		newTodo: ''
	},

	methods: {
		addTodo: function() {
			var 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入門 02」02「バインディングでクラス属性を動的に変える」参照)。コロン: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]で確定しただけでもリスナー関数が呼び出されてしまいます。keydownイベントは変換の確定では起こらないため、そのあと[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

var app = new Vue({

	methods: {

		removeTodo: function(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

var app = new Vue({

	computed: {

		remaining: function() {
			var todos = this.getActive(this.todos);
			return todos.length;
		}
	},
	methods: {

		getActive: function(todos) {
			return todos.filter(function(todo) {
				return !todo.completed;
			});
		}
	}
});

これでTodoリストに項目を追加したり、削除できるほか、チェック済みでない残り項目数も示せるようになりました(図004)。ここまで書き上げたJavaScriptファイル(script.js)の中身は、以下のコード002にまとめます。併せて、サンプル002としてjsdo.itに掲げました。

図004■残りの項目数が示される

図004

コード002■項目の追加・削除と残り項目数の表示ができるTodoリスト

script.js

var app = new Vue({
	data: {
		todos: [],
		newTodo: ''
	},
	computed: {
		filteredTodos: function() {
			return this.todos;
		},
		remaining: function() {
			var todos = this.getActive(this.todos);
			return todos.length;
		}
	},
	methods: {
		addTodo: function() {
			var value = this.newTodo && this.newTodo.trim();
			if (!value) {
				return;
			}
			this.todos.push({
				id: this.todos.length,
				title: value,
				completed: false
			});
			this.newTodo = '';
		},
		removeTodo: function(todo) {
			this.todos.splice(this.todos.indexOf(todo), 1);
		},
		getActive: function(todos) {
			return todos.filter(function(todo) {
				return !todo.completed;
			});
		}
	}
});
app.$mount('.todoapp');

サンプル002■Vue.js: TodoMVC part 01


作成者: 野中文雄
作成日: 2016年6月26日


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