サイトトップ

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

HTML5テクニカルノート

Vue.js + ES6入門 07: 表示する項目をフィルタで切り替える


Vue.js + ES6入門 06: 項目を調べてデータから削除する」では、処理済み項目をまとめて削除できるようにしたり、項目ごとの削除ボタンを加えたりしました。今回は、データは消さずに、処理済みと未処理およびすべての項目を切り替え表示できるようにします。

01 フィルタ切り替えボタンを加える

「Vue.js + ES6入門 06」で書いたコード002「項目を調べてデータから削除する」に手を加えてゆきましょう。まず加えるのは、フィルタの切り替えボタン3つです。全項目(All)と未処理(Active)そして処理済み(Completed)のリストを選べるようにします。切り替えの仕組みにはリンクのハッシュ(#)を用いるので、書き加えるのはつぎのように3つの<a>要素です。Bootstrapのボタンのクラスを割り当て、選ばれたボタンはクラスバインディングでスタイルを変えます。複数のクラスに加えてバインディングするクラスがあるときは、このように配列構文を使うと便利です(「Vue.js: 複数のクラスをバインディングする場合どのような書き方があるか」参照)。

<body>要素

<div id="app" class="container">

	<a href="#/all" :class="['btn btn-outline-info btn-sm', {active: visibility === 'all'}]">All</a>
	<a href="#/active" :class="['btn btn-outline-info btn-sm', {active: visibility === 'active'}]">Active</a>
	<a href="#/completed" :class="['btn btn-outline-info btn-sm', {active: visibility === 'completed'}]">Completed</a>
</div>

フィルタのボタンが選ばれたとき動的にバインディングするクラスの条件に用いるプロパティ(visibility)は、Vueインスタンスのdataオプションに定めて初期値を与えます。これで、フィルタボタン3つの初期状態ができました(図001)。

<script>要素

const app = new Vue({
	data: {

		visibility: 'all'
	},

});

図001■3つのフィルタボタンが加わる

図001

02 選択したボタンのスタイルをクラスバインディングで変える

<a>要素のリンクがクリックされて、href属性に与えたハッシュが切り替わったことを捉えるのはwindow.onhashchangeイベントです。ハッシュの文字列(DOMString)は、プロパティwindow.locationからLocation.hashで得られます。要らない記号(#/)は正規表現によりつぎのようにString.prototype.replace()メソッドで除いて、取り出したのがリンクのキーワードです。これをVueインスタンスのdataオプションのプロパティ(visibility)に納めます。

<script>要素

const app = new Vue({

	mounted() {
		window.addEventListener('hashchange', this.onHashChange);
	},
	methods: {

		onHashChange() {
			const visibility = window.location.hash.replace(/#\/?/, '');
			this.visibility = visibility;
		}
	}
});

これで、フィルタのボタンをクリックすると、バインディングしたクラスが割り当てられ、選択されたことを示します(図002)。ここまでのHTMLとJavaScriptの記述は、以下のコード001にまとめました。

図002■クリックしたボタンのスタイルが変わる

図002

コード001■フィルタボタンのスタイルをクリックで切り替える

<body>要素

<div id="app" class="container">
	<h2>Todo</h2>
	<p>
		全{{todos.length}}件中残り{{remaining}}件
		<button  @click="archive" class="btn btn-danger btn-sm">断捨離</button>
	</p>
	<ul class="list-unstyled">
		<li v-for="todo in todos">
			<label>
				<input type="checkbox" v-model="todo.done">
				<span :class="{'done': todo.done}">{{todo.text}}</span>
			</label>
			<button @click="removeTodo(todo)" class="btn btn-warning btn-sm">削除</button>
		</li>
	</ul>
	<p>
		<input type="text" v-model="todoText" placeholder="add new todo here">
		<button @click="addTodo" class="btn btn-primary btn-sm">追加</button>
	</p>
	<a href="#/all" :class="['btn btn-outline-info btn-sm', {active: visibility === 'all'}]">All</a>
	<a href="#/active" :class="['btn btn-outline-info btn-sm', {active: visibility === 'active'}]">Active</a>
	<a href="#/completed" :class="['btn btn-outline-info btn-sm', {active: visibility === 'completed'}]">Completed</a>
</div>

<script>要素

const app = new Vue({
	data: {
		todoText: '',
		todos: [
			{text: 'Vue.jsを学ぶ', done: true},
			{text: 'Vue.jsでアプリケーションをつくる', done: false},
		],
		visibility: 'all'
	},
	computed: {
		remaining() {
			const count =
				this.todos.reduce((count, todo) =>
					count = (todo.done) ? count : ++count
				, 0);
			return count;
		}
	},
	mounted() {
		window.addEventListener('hashchange', this.onHashChange);
	},
	methods: {
		addTodo() {
			const newTodo = this.todoText.trim();
			this.todoText = '';
			if (!newTodo) {return;}
			this.todos = [
				...this.todos,
				{text: newTodo, done: false}
			];
		},
		removeTodo(todo) {
			this.todos = this.todos.filter((_todo) => _todo !== todo);
		},
		archive() {
			this.todos = this.todos.filter((todo) => !todo.done);
		},
		onHashChange() {
			const visibility = window.location.hash.replace(/#\/?/, '');
			this.visibility = visibility;
		}
	}
});
document.addEventListener('DOMContentLoaded', () =>
	app.$mount('#app')
);

03 フィルタでリスト項目を切り替える

もとのリスト項目のデータはそのままに、表示を切り替えるのがフィルタの仕組みです。もとデータから表示したい要素だけ取り出して返す算出プロパティ(filteredTodos)は、つぎのように定めます。3つのフィルタリングのメソッドを備えるのが新たにつくったオブジェクト(filters)です。メソッド名はフィルタのキーワードと揃えました。算出プロパティは、切り替わったハッシュのキーワード(visibility)と同じ名前のメソッドから、表示する項目の配列を得て返します。

<script>要素

const filters = {
	all(todos) {
		return todos;
	},
	active(todos) {
		return todos.filter((todo) =>
			!todo.done
		);
	},
	completed(todos) {
		return todos.filter((todo) =>
			todo.done
		);
	}
};
const app = new Vue({

	computed: {

		filteredTodos() {
			return filters[this.visibility](this.todos);
		}
	},

});

あとは、リスト項目の<li>要素に加えたv-forディレクティブが、もとデータ(todos)でなく算出プロパティ(filteredTodos)の配列を参照するように書き替えるだけです。

<body>要素

<div id="app" class="container">

	<ul class="list-unstyled">
		<!-- <li v-for="todo in todos"> -->
		<li v-for="todo in filteredTodos">

		</li>
	</ul>

</div>

図003■フィルタで表示項目が変わる

図003

04 ハッシュの扱いを点検する

フィルタは<a>要素のhref属性に与えたハッシュで切り替えました。すると、キーワードでないハッシュを直にURLに打ち込むと、対応するメソッドがないのでエラーが起きてしまいます。

TypeError: filters[this.visibility] is not a function

そこで、メソッドがあるかどうか確かめ、あったら呼び出し、ないときはハッシュを空にしてキーワードは初期値に戻すことにしましょう。

<script>要素

const app = new Vue({

	methods: {

		onHashChange() {
			const visibility = window.location.hash.replace(/#\/?/, '');
			if (filters[visibility]) {
				this.visibility = visibility;
			} else {
				window.location.hash = '';
				this.visibility = 'all';
			}
		}
	}
});

もうひとつ、URLにはハッシュが残ります。リロードしたときURLはそのままなのに、キーワードのプロパティが初期化されてしまうのは問題です。そこで、つぎのようにmounted()オプションでハッシュ変更のメソッド(onHashChange())を呼び出して、キーワードはハッシュに一致させます。

<script>要素

const app = new Vue({

	mounted() {
		window.addEventListener('hashchange', this.onHashChange);
		this.onHashChange();
	},

});

書き改めたHTMLとJavaScriptの記述は、つぎのコード002のとおりです。コードを試すためのサンプル001も併せて掲げます。

コード002■フィルタで表示する項目を切り替える

<body>要素

<div id="app" class="container">
	<h2>Todo</h2>
	<p>
		全{{todos.length}}件中残り{{remaining}}件
		<button  @click="archive" class="btn btn-danger btn-sm">断捨離</button>
	</p>
	<ul class="list-unstyled">
		<li v-for="todo in filteredTodos">
			<label>
				<input type="checkbox" v-model="todo.done">
				<span :class="{'done': todo.done}">{{todo.text}}</span>
			</label>
			<button @click="removeTodo(todo)" class="btn btn-warning btn-sm">削除</button>
		</li>
	</ul>
	<p>
		<input type="text" <v-model="todoText" placeholder="add new todo here">
		<button @click="addTodo" class="btn btn-primary btn-sm">追加</button>
	</p>
	<a href="#/all" :class="['btn btn-outline-info btn-sm', {active: visibility === 'all'}]">All</a>
	<a href="#/active" :class="['btn btn-outline-info btn-sm', {active: visibility === 'active'}]">Active</a>
	<a href="#/completed" :class="['btn btn-outline-info btn-sm', {active: visibility === 'completed'}]">Completed</a>
</div>

<script>要素

const filters = {
	all(todos) {
		return todos;
	},
	active(todos) {
		return todos.filter((todo) =>
			!todo.done
		);
	},
	completed(todos) {
		return todos.filter((todo) =>
			todo.done
		);
	}
};
const app = new Vue({
	data: {
		todoText: '',
		todos: [
			{text: 'Vue.jsを学ぶ', done: true},
			{text: 'Vue.jsでアプリケーションをつくる', done: false},
		],
		visibility: 'all'
	},
	computed: {
		remaining() {
			const count =
				this.todos.reduce((count, todo) =>
					count = (todo.done) ? count : ++count
				, 0);
			return count;
		},
		filteredTodos() {
			return filters[this.visibility](this.todos);
		}
	},
	mounted() {
		window.addEventListener('hashchange', this.onHashChange);
		this.onHashChange();
	},
	methods: {
		addTodo() {
			const newTodo = this.todoText.trim();
			this.todoText = '';
			if (!newTodo) {return;}
			this.todos = [
				...this.todos,
				{text: newTodo, done: false}
			];
		},
		removeTodo(todo) {
			this.todos = this.todos.filter((_todo) => _todo !== todo);
		},
		archive() {
			this.todos = this.todos.filter((todo) => !todo.done);
		},
		onHashChange() {
			const visibility = window.location.hash.replace(/#\/?/, '');
			if (filters[visibility]) {
				this.visibility = visibility;
			} else {
				window.location.hash = '';
				this.visibility = 'all';
			}
		}
	}
});
document.addEventListener('DOMContentLoaded', () =>
	app.$mount('#app')
);

サンプル001■Vue.js + ES6: Filtering items to list

See the Pen Vue.js + ES6: Filtering items to list by Fumio Nonaka (@FumioNonaka) on CodePen.

Vue.js + ES6


作成者: 野中文雄
作成日: 2019年6月18日


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