HTML5テクニカルノート
Vue.js + ES6入門 07: 表示する項目をフィルタで切り替える
- ID: FN1906004
- Technique: HTML5 / ECMAScript 2015
- Library: Vue.js 2.6.10
「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>
フィルタのボタンが選ばれたとき動的にバインディングするクラスの条件に用いるプロパティ(
<script>要素visibility
)は、Vueインスタンスのdata
オプションに定めて初期値を与えます。これで、フィルタボタン3つの初期状態ができました(図001)。const app = new Vue({ data: { visibility: 'all' }, });
図001■3つのフィルタボタンが加わる
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■クリックしたボタンのスタイルが変わる
コード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>
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■フィルタで表示項目が変わる
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>
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
- Vue.js + ES6入門 01: Vue.jsを始める
- Vue.js + ES6入門 02: 要素のclass属性を動的に変える
- Vue.js + ES6入門 03: データから動的にリストをつくる
- Vue.js + ES6入門 04: フィールドに入力したテキストを動的に項目として加える
- Vue.js + ES6入門 05: 項目を数えて表示する
- Vue.js + ES6入門 06: 項目を調べてデータから削除する
- Vue.js + ES6入門 07: 表示する項目をフィルタで切り替える
- Vue.js + ES6入門 08: フィルタをボタンで切り替える
- Vue.js + ES6入門 09: アプリケーションをコンポーネントに分ける
- Vue.js + ES6入門 10: コンポーネントの応用とデータのチェック
- Vue.js + ES6入門 11: データチェックの応用とkey属性
- Vue.js + ES6入門 12: ローカルコンポーネントを定める
作成者: 野中文雄
作成日: 2019年6月18日
Copyright © 2001-2019 Fumio Nonaka. All rights reserved.