HTML5テクニカルノート
Vue.js + CLI入門 05: チェックをまとめてオン/オフしたり削除する
- ID: FN1901001
- Technique: HTML5 / ECMAScript 2015
- Library: Vue.js 2.6.6
Vue.js公式サイトの「TodoMVC の例」を単一ファイルコンポーネント(.vue)でつくるシリーズの第5回で加えるのは、リスト項目をまとめて処理するふたつの機能です。ひとつは、リスト項目すべてのチェックを一度にオン/オフできるようにします。もうひとつは、チェックされた項目をまとめて削除できるボタンです。
01 項目のチェックをまとめてオン/オフする
まず、リスト項目の処理済みチェックを、まとめてオン/オフできるようにしましょう。リスト表示のコンポーネント(src/components/TodoList.vue
)には、先頭にまだ使っていないチェックボックスの<input>
要素(type属性checkbox
)がすでに加えてありました。このチェックボックスを親のアプリケーションのプロパティ(allDone
)と、つぎのように双方向でデータバインディングします。チェックのプロパティ(checked
)をバインディングするのも忘れないでください。そして、<input type="checkbox">
要素には、id
属性が与えられています。対応する<label>
要素を加えると、index.css
によりチェックサインが表れるはずです(後掲図001参照)。
src/components/TodoList.vue<template> <section class="main" v-show="todos.length" v-cloak> <input id="toggle-all" class="toggle-all" type="checkbox" :value="allDone" :checked="allDone" @change="onInput"> <label for="toggle-all"></label> </section> </template> <script> import TodoItem from './TodoItem.vue'; export default { props: { allDone: Boolean }, methods: { onInput() { this.$emit('allDone', !this.allDone); } } } </script>
親アプリケーション(src/App.vue
)のバインディング先は、算出プロパティ(allDone
)です。算出プロパティは、通常は値を返すgetter関数として扱われます。けれど、オブジェクトにしてメソッドget()
とset()
をそれぞれ定めることもできるのです(「算出Setter関数」参照)。すると、算出プロパティの値はもちろん、他のデータを書き替えたり、さらに別の処理も加えられます。つぎの算出プロパティの値は、すべての項目に処理済みのチェックがされているかどうか、getterが返すブール(論理)値です。そして、setterはチェックボックスから渡された引数値(value
)を、リストデータ(todos
)のすべての項目のプロパティ(completed
)に定めます。
src/App.vue<template> <section id="app" class="todoapp"> <todo-list :allDone="allDone" @allDone="onAllDone"> </todo-list> </section> </template> <script> export default { computed: { allDone: { get() { return this.remaining === 0; }, set(value) { this.todos.forEach((todo) => todo.completed = value ); } } }, methods: { onAllDone(done) { this.allDone = done; } } } </script>
リスト左上のチェックボックスで、リスト項目すべてのチェックがまとめて変えられるようになりました。未処理の項目があれば、クリックですべての項目に処理済みのチェックがつきます(図001)。未処理がなくなっている場合は、逆にチェクが全部外されるということです。
図001■リスト左上のチェックボックスですべての項目のチェックがまとめて変えられる
02 チェック済みの項目すべてをリストから除く
チェック済みの項目すべてをリストから除くボタン(<button>
要素)は、以下のようにフッターのコンポーネント(src/components/TodoController.vue
)に加えます(図002)。v-on:click
(省略記法@click
)イベントに定めたメソッド(removeCompleted()
)は、お約束どおり$emit()
メソッドで親アプリケーションにイベント(removeCompleted
)を送る役割です。また、v-show
ディレクティブは、リストの全項目数より残り(チェックなし)項目数(算出プロパティremaining
)が少ないことを条件にしているので、チェックされている項目がなければ表示されません。
イベント(removeCompleted
)を受け取ったアプリケーション(src/App.vue
)は、ハンドラメソッド(removeCompleted()
)を呼び出し、フィルタのメソッド(filters.active()
)により未処理の項目を取り出したうえで、リスト項目のデータ(todos
)を書き替えればよいでしょう。こうして、処理済みの項目はデータから除かれることになるのです。
src/App.vue<template> <section id="app" class="todoapp"> <todo-controller @removeCompleted="removeCompleted"> </todo-controller> </section> </template> <script> export default { methods: { removeCompleted() { this.todos = filters.active(this.todos); } } } </script>
src/components/TodoController.vue<template> <footer class="footer" v-show="todos.length" v-cloak> <button class="clear-completed" v-show="todos.length > remaining" @click="removeCompleted"> Clear completed </button> </footer> </template> <script> export default { methods: { removeCompleted() { this.$emit('removeCompleted'); } } } </script>
図002■チェックした処理済みの項目がまとめて除かれる
ここまで書き上げたアプリケーション(src/App.vue
)とリスト表示(src/components/TodoController.vue
)およびフッター(src/components/TodoController.vue
)のコンポーネント(VUE)ファイルの中身を、つぎのコード001にまとめました。他のコンポーネントも含めたアプリケーションのファイル全体については、CodeSandboxに公開した以下のサンプル001をご参照ください。
コード001■未処理の項目数を示す
<template>
<section id="app" class="todoapp">
<header class="header">
<h1>todos</h1>
<todo-input
@add-todo="addTodo" />
</header>
<todo-list
:todos="todos"
:filtered-todos="filteredTodos"
:allDone="allDone"
@remove-todo="removeTodo"
@done="done"
@allDone="onAllDone">
</todo-list>
<todo-controller
:todos="todos"
:remaining="remaining"
:visibility="visibility"
@removeCompleted="removeCompleted">
</todo-controller>
</section>
</template>
<script>
import TodoInput from './components/TodoInput.vue';
import TodoList from './components/TodoList.vue';
import TodoController from './components/TodoController.vue';
const STORAGE_KEY = 'todos-vuejs-2.6';
const todoStorage = {
fetch() {
const todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]');
todos.forEach(function(todo, index) {
todo.id = index;
});
todoStorage.uid = todos.length;
return todos;
},
save(todos) {
localStorage.setItem(STORAGE_KEY, JSON.stringify(todos));
}
};
const filters = {
all(todos) {
return todos;
},
active(todos) {
return todos.filter((todo) =>
!todo.completed
);
},
completed(todos) {
return todos.filter((todo) =>
todo.completed
);
}
};
export default {
name: 'app',
components: {
TodoInput,
TodoList,
TodoController
},
data() {
return {
todos: todoStorage.fetch(),
visibility: 'all'
}
},
computed: {
filteredTodos() {
return filters[this.visibility](this.todos);
},
remaining() {
const todos = filters.active(this.todos);
return todos.length;
},
allDone: {
get() {
return this.remaining === 0;
},
set(value) {
this.todos.forEach((todo) =>
todo.completed = value
);
}
}
},
watch: {
todos: {
handler(todos) {
todoStorage.save(todos);
},
deep: true
}
},
mounted() {
window.addEventListener('hashchange', this.onHashChange);
},
methods: {
addTodo(todoTitle) {
const newTodo = todoTitle && todoTitle.trim();
if (!newTodo) {
return;
}
this.todos.push({
id: todoStorage.uid++,
title: newTodo,
completed: false
});
},
removeTodo(todo) {
this.todos = this.todos.filter((item) => item !== todo);
},
done(todo, completed) {
todo.completed = completed;
},
onHashChange() {
const visibility = window.location.hash.replace(/#\/?/, '');
this.visibility = visibility;
},
onAllDone(done) {
this.allDone = done;
},
removeCompleted() {
this.todos = filters.active(this.todos);
}
}
}
</script>
<style>
@import url("https://unpkg.com/todomvc-app-css@2.2.0/index.css");
</style>
<template>
<section class="main" v-show="todos.length" v-cloak>
<input id="toggle-all" class="toggle-all" type="checkbox"
:value="allDone"
:checked="allDone"
@change="onInput">
<label for="toggle-all"></label>
<ul class="todo-list">
<li v-for="todo in filteredTodos"
class="todo"
:key="todo.id"
:class="{completed: todo.completed}">
<todo-item
:todo="todo"
@remove-todo="removeTodo"
@done="done">
</todo-item>
</li>
</ul>
</section>
</template>
<script>
import TodoItem from './TodoItem.vue';
export default {
name: 'TodoList',
components: {
TodoItem
},
props: {
todos: Array,
filteredTodos: Array,
allDone: Boolean
},
methods: {
removeTodo(todo) {
this.$emit('remove-todo', todo);
},
done(todo, completed) {
this.$emit('done', todo, completed);
},
onInput() {
this.$emit('allDone', !this.allDone);
}
}
}
</script>
<style scoped>
[v-cloak] {
display: none;
}
</style>
<template>
<footer class="footer" v-show="todos.length" v-cloak>
<span class="todo-count">
<strong>{{remaining}}</strong> {{remaining | pluralize}} left
</span>
<ul class="filters">
<li><a href="#/all"
:class="{selected: visibility === 'all'}">
All</a></li>
<li><a href="#/active"
:class="{selected: visibility === 'active'}">
Active</a></li>
<li><a href="#/completed"
:class="{selected: visibility === 'completed'}">
Completed</a></li>
</ul>
<button class="clear-completed"
v-show="todos.length > remaining"
@click="removeCompleted">
Clear completed
</button>
</footer>
</template>
<script>
export default {
name: 'TodoController',
filters: {
pluralize(n) {
return n === 1 ? 'item' : 'items';
}
},
props: {
todos: Array,
remaining: Number,
visibility: String
},
methods: {
removeCompleted() {
this.$emit('removeCompleted');
}
}
}
</script>
サンプル001■vue-todo-mvc-05
Vue.js + CLI入門
- Vue.js + CLI 01: リスト項目の表示と追加
- Vue.js + CLI 02: リスト項目の削除とスタイル変更
- Vue.js + CLI入門 03: データの計算処理とローカルへの保存
- Vue.js + CLI入門 04: リスト表示する項目を選び出して切り替える
- Vue.js + CLI入門 05: チェックをまとめてオン/オフしたり削除する
- Vue.js + CLI入門 06: 項目のテキストをダブルクリックで再編集する
- Vue.js + CLI入門 07: フォーカスをコントロールする
- Vue.js + CLI入門 08: 要素にアニメーションを加える
作成者: 野中文雄
更新日: 2019年10月14日 src/App.vueのコードを一部修正。
更新日: 2019年09月13日 本文に一部加筆。
更新日: 2019年09月06日 サンプル001を追加。
更新日: 2019年03月23日 ブラウザの違いによる問題に対応。
作成日: 2019年01月02日
Copyright © 2001-2019 Fumio Nonaka. All rights reserved.