HTML5テクニカルノート
Vue + <script setup> + TypeScript: TodoMVC 05 チェックをまとめてオン/オフしたり削除する
- ID: FN2212003
- Technique: ECMAScript 2015
- Package: Vue 3.2
Vue + <script setup> + TypeScriptの構文で、公式サイト「TodoMVC」の例をつくるシリーズ第5回が扱うのはふたつのお題です。まず、Todoリストの項目すべてのチェックをまとめてオン/オフします。つぎに、終了したTodo項目つまりチェック済みの要素を調べて、リストデータから除く断捨離の機能です。
01 Todo項目のチェックをまとめてオン/オフする
Todo項目のチェックをまとめてオン/オフするボタンは、リスト表示のモジュールsrc/components/TodoList.vue
にチェックボックス(<input type="checkbox">
要素)で加えます。
src/components/TodoList.vue
<template< <section class="main"> <input id="toggle-all" type="checkbox" class="toggle-all" /> <label for="toggle-all">Mark all as complete</label> </section> </template>
ルートモジュールsrc/App.vue
に加えるチェックボックスの処理はつぎのとおりです。
- Todoリストにチェックされていない(未了の)項目があれば、すべてにチェックをつけて処理済みにする。
- Todoリストの項目がすべて終了していたら、すべてのチェックを外して未処理にする。
そこで、すべて処理済みかどうかを算出プロパティ(allDone
)として新たに加えます。このプロパティをデータバインディングするのが、表示リストの子コンポーネント(TodoList
)です。また、子コンポーネントがチェックボックスを切り替えると、イベント(toggleAll
)とともに、checked
プロパティ値が送られます。そのイベントハンドラとしてtoggleAll()
を加えました。
src/App.vue
<script setup lang="ts"> const allDone = computed(() => remaining.value === 0); const toggleAll = (checked: boolean) => { todos.value.forEach((todo) => (todo.completed = checked)); }; </script> <template> <section id="app" class="todoapp"> <TodoList :allDone="allDone" @toggleAll="toggleAll" /> </section> </template>
リスト表示のモジュールsrc/components/TodoList.vue
に戻って、定めるのは親コンポーネントとのデータバインディングです。受け取るプロパティ(allDone
)と送るイベント(event: 'toggleAll'
)の型はinterface
に加えます。チェックボックス(<input type="checkbox">
)の:checked
にデータバインディングするのがプロパティ、イベントを送信するのは@change
のハンドラメソッド(onChange()
)です。Todoリスト左上のチェックボックスで、すべての項目のチェックがまとめて変えられるようになりました(図001)。
src/components/TodoList.vue
<script setup lang="ts"> interface Props { allDone: boolean; } interface Emits { (event: 'toggleAll', checked: boolean): void; } const onChange = ({ target }: Event) => { if (!(target instanceof HTMLInputElement)) return; emit('toggleAll', target.checked); }; </script> <template> <section class="main"> <!-- <input id="toggle-all" type="checkbox" class="toggle-all" /> --> <input id="toggle-all" type="checkbox" class="toggle-all" :checked="allDone" @change="onChange" /> </section> </template>
図001■Todoリスト左上のチェックボックスですべての項目のチェックがまとめて変えられる

Todoリスト表示のモジュールsrc/components/TodoList.vue
の記述全体は、つぎのコード001にまとめたとおりです。
コード001■Todoリスト表示のモジュール
src/components/TodoList.vue
<script setup lang="ts">
import type { Todo } from '../App.vue';
import TodoItem from './TodoItem.vue';
interface Props {
allDone: boolean;
filteredTodos: Todo[];
}
interface Emits {
(event: 'removeTodo', todo: Todo): void;
(event: 'done', todo: Todo, completed: boolean): void;
(event: 'toggleAll', checked: boolean): void;
}
defineProps<Props>();
const emit = defineEmits<Emits>();
const removeTodo = (todo: Todo) => {
emit('removeTodo', todo);
};
const done = (todo: Todo, completed: boolean) => {
emit('done', todo, completed);
};
const onChange = ({ target }: Event) => {
if (!(target instanceof HTMLInputElement)) return;
emit('toggleAll', target.checked);
};
</script>
<template>
<section class="main">
<input
id="toggle-all"
type="checkbox"
class="toggle-all"
:checked="allDone"
@change="onChange"
/>
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
<li
v-for="todo in filteredTodos"
:class="['todo', { completed: todo.completed }]"
:key="todo.id"
>
<TodoItem :todo="todo" @removeTodo="removeTodo" @done="done" />
</li>
</ul>
</section>
</template>
02 チェック済みの項目すべてをリストデータから除く
Todoリストデータからチェック済みの項目すべてを除くボタン(<button>
要素)は、フッタのTodoコントローラモジュールsrc/components/TodoController.vue
に加えましょう。@click
イベントで親コンポーネントにイベント(event: 'removeCompleted'
)を送るのがremoveCompleted()
ハンドラメソッドです。なお、Todoリストの項目すべてが未処理なら、断捨離できません。その場合、v-show
ディレクティブでボタンは非表示にしました。
src/components/TodoController.vue
<script setup lang="ts"> interface Emits { (event: 'removeCompleted'): void; } const emit = defineEmits<Emits>(); const removeCompleted = () => { emit('removeCompleted'); }; </script> <template> <footer class="footer" v-show="todos.length"> <button class="clear-completed" v-show="todos.length > remaining" @click="removeCompleted" > Clear completed </button> </footer> </template>
子コンポーネント(TodoController
)からのイベント(removeCompleted
)を受け取るのが、親モジュールsrc/App.vue
のハンドラメソッドremoveCompleted()
です。未処理のTodo項目リストを取り出す関数(getActive()
)はすでに定めてあるので、改めておおもとの配列ref
オブジェクト(todos
)のデータに上書きすれば済みます。Todoリストのデータは断捨離されて、残るのは未処理項目のみです(図002)。
src/App.vue
<script setup lang="ts"> const removeCompleted = () => { todos.value = getActive(todos.value); }; </script> <template> <section id="app" class="todoapp"> <TodoController @removeCompleted="removeCompleted" /> </section> </template>
図002■チェック済みの終了項目をまとめて削除できる

書き改めたTodoコントローラsrc/components/TodoController.vue
とルートモジュールsrc/App.vue
の記述全体は、つぎのコード002にまとめました。併せて公開するGitHubのコードが、以下のソース01です。
コード002■Todoコントローラとルートモジュール
src/components/TodoController.vue
<script setup lang="ts">
import { computed } from 'vue';
import type { Todo } from '../App.vue';
interface Props {
todos: Todo[];
remaining: number;
visibility: string;
}
interface Emits {
(event: 'removeCompleted'): void;
}
const props = defineProps<Props>();
const emit = defineEmits<Emits>();
const pluralize = computed(() => (props.remaining === 1 ? 'item' : 'items'));
const removeCompleted = () => {
emit('removeCompleted');
};
</script>
<template>
<footer class="footer" v-show="todos.length">
<span class="todo-count">
<strong>{{ remaining }}</strong> {{ 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>
src/App.vue
<script setup lang="ts">
import { computed, onMounted, ref, watchEffect } from 'vue';
import { fetch, getNewId, save } from './TodoStorage';
import TodoInput from './components/TodoInput.vue';
import TodoList from './components/TodoList.vue';
import TodoController from './components/TodoController.vue';
export interface Todo {
id: number;
title: string;
completed: boolean;
}
const todos = ref(fetch());
const visibility = ref('all');
const remaining = computed(() => getActive(todos.value).length);
const allDone = computed(() => remaining.value === 0);
const filteredTodos = computed((): Todo[] => {
switch (visibility.value) {
case 'all':
return todos.value;
case 'active':
return todos.value.filter((todo) => !todo.completed);
case 'completed':
return todos.value.filter((todo) => todo.completed);
default:
return todos.value;
}
});
watchEffect(() => save(todos.value));
const addTodo = (todoTitle: string) => {
if (!todoTitle) return;
todos.value.push({
id: getNewId(),
title: todoTitle,
completed: false,
});
};
const removeTodo = (todo: Todo) => {
todos.value = todos.value.filter((item) => item !== todo);
};
const done = (todo: Todo, completed: boolean) => {
todo.completed = completed;
};
const getActive = (todos: Todo[]) => {
return todos.filter((todo) => !todo.completed);
};
const onHashChange = () => {
visibility.value = window.location.hash.replace(/#\/?/, '');
};
const removeCompleted = () => {
todos.value = getActive(todos.value);
};
const toggleAll = (checked: boolean) => {
todos.value.forEach((todo) => (todo.completed = checked));
};
onMounted(() => {
window.addEventListener('hashchange', onHashChange);
});
</script>
<template>
<section id="app" class="todoapp">
<header class="header">
<h1>todos</h1>
<TodoInput @addTodo="addTodo" />
</header>
<TodoList
:allDone="allDone"
:filteredTodos="filteredTodos"
:todos="todos"
@removeTodo="removeTodo"
@done="done"
@toggleAll="toggleAll"
/>
<TodoController
:todos="todos"
:remaining="remaining"
:visibility="visibility"
@removeCompleted="removeCompleted"
/>
</section>
</template>
<style>
@import url("https://unpkg.com/todomvc-app-css@2.4.2/index.css");
</style>
ソース01■TodoMVC 05 チェックをまとめてオン/オフしたり削除する
Vue + <script setup> + TypeScript: TodoMVCシリーズ
- TodoMVC 01 リスト項目の表示と追加
- TodoMVC 02 リスト項目の削除とスタイル変更
- TodoMVC 03 算出プロパティとデータのローカルへの保存
- TodoMVC 04 リスト表示する項目を選び出して切り替える
- TodoMVC 05 チェックをまとめてオン/オフしたり削除する
- TodoMVC 06 項目のテキストをダブルクリックで再編集する
- TodoMVC 07 フォーカスをコントロールする
- TodoMVC 08 要素にアニメーションを加える
- TodoMVC 09 コンポーネントからロジックをコンポーザブル関数に切り分ける
作成者: 野中文雄
作成日: 2022年12月18日
Copyright © 2001-2020 Fumio Nonaka. All rights reserved.