サイトトップ

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

HTML5テクニカルノート

Vue.js + ES6入門 08: フィルタをボタンで切り替える


Vue.js + ES6入門 07: 表示する項目をフィルタで切り替える」は、データのフィルタにより、処理済みと未処理およびすべての項目を切り替え表示できるようにしました。今回、基本的な機能は変えません。フィルタの切り替えを、リンクのハッシュでなく、ボタンで行うようにしてみます。

01 フィルタの切り替えの設定を確かめる

「Vue.js + ES6入門 07」で書いたコード002「フィルタで表示する項目を切り替える」は、<a>要素に加えたhref属性のハッシュ(#)により、フィルタを切り替えました。これはVue.js公式サイトの「TodoMVCの例」が用いた手法です。けれど、ボタンにイベントリスナーを定める方がすっきりします。

また、フィルタを決めるキーワード(all/active/completed)が、ハッシュとクラスバインディング、およびリンクボタンの名前に手打ちで書かれています。これらは自動で一致させたいところです。

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

さらにキーワードは、フィルタのオブジェクト(filters)にも、つぎのようにメソッドとして備わっています。そこで、このメソッド名をフィルタボタンのタグの設定に利用することにしましょう。

<script>要素

const filters = {
	all(todos) {

	},
	active(todos) {

	},
	completed(todos) {

	}
};

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

テンプレートでオブジェクトからプロパティを取り出すのは、v-forディレクティブです。第1引数(value)は値、そして第2引数(key)にプロパティ名が受け取れます(「オブジェクトのv-for」参照)。これを、つぎのように<button>要素の属性の定めに用いればよいでしょう。クリックイベント(@click)のハンドラメソッド(changeFilter())にも、このプロパティ名を渡します。なお、文字列にブラケット記法[]でインデックスから文字を得るのはECMAScript 5の構文です。

<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> -->
	<button type="button"
		v-for="(value, key) in filters"
		:class="['btn btn-outline-info btn-sm mr-1', {active: visibility === key}]"
		@click="changeFilter(key)">
		{{ key[0].toUpperCase() + key.substr(1) }}
	</button>
</div>

ボタンクリックのイベントハンドラ(changeFilter())は、引数に受け取ったキーワードをプロパティ(visibility)に与えるだけです。ハッシュ切り替えのメソッド(onHashChange())とイベントリスナーの設定(mounted())は要らなくなります。

<script>要素

const app = new Vue({

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

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

フィルタのボタンをクリックすると、表示される項目がこれまでと同じように切り替わります(図001))。ひとつ違うのは、URLにハッシュが残らないことです。そのため、ページを読み込み直すと、フィルタボタンの選択は初期値(All)に戻ります。URLも切り替えたい場合には、ルーティングを考えた方がよいでしょう。

図001■フィルタボタンで表示される項目が切り替わる

図001
   
図002

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

コード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 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>
	<button type="button"
		v-for="(value, key) in filters"
		:class="['btn btn-outline-info btn-sm mr-1', {active: visibility === key}]"
		@click="changeFilter(key)">
		{{ key[0].toUpperCase() + key.substr(1) }}
	</button>
</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);
		}
	},
	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);
		},
		changeFilter(visibility) {
			this.visibility = visibility;
		}
	}
});
document.addEventListener('DOMContentLoaded', () =>
	app.$mount('#app')
);

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

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

Vue.js + ES6


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


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