Day28 後半のゴール
前半で、タスクを
「{ title: ..., isDone: ... } というオブジェクトで管理する」
「isDone の値に応じてチェックボックスの ON/OFF を反映する」
ところまで進みました。
後半では、ここから一歩踏み込んで、
チェックボックスをクリックしたら isDone を切り替える
完了したタスクの見た目を変える(取り消し線など)
完了状態も含めて localStorage に保存・復元する
という、「完了機能の本体」を作っていきます。
チェックボックスと isDone を双方向につなぐ
クリックされたときに isDone を更新する
前半では、
「task.isDone の値をチェックボックスに反映する」
という一方向の流れだけを作りました。
後半では逆方向、つまり
ユーザーがチェックボックスをクリックする
→ task.isDone の値を更新する
という流れを追加します。
チェックボックスには change イベントを付けるのが定番です。
checkboxElement.addEventListener("change", () => {
task.isDone = checkboxElement.checked;
});
JavaScriptここでやっていることはシンプルです。
checkboxElement.checked
今のチェック状態(true / false)
それをそのままtask.isDone に代入することで、
「UI の状態をデータに反映」しています。
この一行で、isDone → checked(前半)checked → isDone(後半)
の両方向がつながります。
完了状態の変更も保存する
チェックを変えたら saveTasks も呼ぶ
完了状態も、当然 localStorage に保存したい情報です。
なので、チェックボックスの change イベントの中でsaveTasks() を呼びます。
checkboxElement.addEventListener("change", () => {
task.isDone = checkboxElement.checked;
saveTasks();
renderTasks();
});
JavaScriptここで renderTasks() も呼んでいるのは、
「見た目もすぐに反映したい」からです。
このイベントの流れはこうなります。
ユーザーがチェックを付ける/外す
→ task.isDone が更新される
→ saveTasks() で localStorage に保存される
→ renderTasks() で画面が最新状態に描き直される
これで、「完了状態も含めて永続化される TODO」が完成に近づきます。
完了タスクの見た目を変える
取り消し線を付けて「終わった感」を出す
完了したタスクは、未完了と見た目で区別したいですよね。
一番分かりやすいのは「取り消し線」です。
CSS を使う方法もありますが、
まずは JavaScript だけでやってみます。
if (task.isDone) {
taskTextElement.style.textDecoration = "line-through";
taskTextElement.style.color = "#888";
} else {
taskTextElement.style.textDecoration = "none";
taskTextElement.style.color = "inherit";
}
JavaScriptこれを renderTasks の中で、taskTextElement.textContent = task.title; のあとに書きます。
これで、
isDone === true のタスク
→ 取り消し線+少し薄い色
isDone === false のタスク
→ 普通の表示
という視覚的な違いが生まれます。
「データの状態を UI に反映する」という感覚を、
ここでしっかり体に染み込ませてほしいところです。
完了機能を含めた renderTasks の全体像
追加・削除・完了がそろった描画処理
ここまでの内容を反映した renderTasks は、こんな形になります。
function renderTasks() {
taskListElement.textContent = "";
tasks.forEach((task, index) => {
const taskItemElement = document.createElement("div");
const checkboxElement = document.createElement("input");
checkboxElement.type = "checkbox";
checkboxElement.checked = task.isDone;
const taskTextElement = document.createElement("span");
taskTextElement.textContent = task.title;
if (task.isDone) {
taskTextElement.style.textDecoration = "line-through";
taskTextElement.style.color = "#888";
} else {
taskTextElement.style.textDecoration = "none";
taskTextElement.style.color = "inherit";
}
const deleteButtonElement = document.createElement("button");
deleteButtonElement.textContent = "削除";
checkboxElement.addEventListener("change", () => {
task.isDone = checkboxElement.checked;
saveTasks();
renderTasks();
});
deleteButtonElement.addEventListener("click", () => {
deleteTask(index);
saveTasks();
renderTasks();
});
taskItemElement.appendChild(checkboxElement);
taskItemElement.appendChild(taskTextElement);
taskItemElement.appendChild(deleteButtonElement);
taskListElement.appendChild(taskItemElement);
});
}
JavaScriptこの関数の中で起きていることを整理すると、
タスク1件ごとに
チェックボックス(完了状態の表示+変更)
テキスト(タイトル+見た目の変化)
削除ボタン(削除機能)
をまとめて作り、
イベントもその場で紐づけています。
localStorage と完了状態の関係を確認する
JSON にも isDone が含まれていることを意識する
tasks は今、こういう形の配列です。
[
{ title: "買い物に行く", isDone: false },
{ title: "メールを送る", isDone: true }
]
JavaScriptこれを JSON.stringify(tasks) すると、
だいたいこんな文字列になります。
[
{"title":"買い物に行く","isDone":false},
{"title":"メールを送る","isDone":true}
]
JavaScriptつまり、title だけでなく isDone も含めて
localStorage に保存されている、ということです。
ページを再読み込みしたときは、
loadTasks() で JSON を読み込むJSON.parse で配列に戻すrenderTasks() でチェック状態と見た目を反映する
という流れになるので、
完了状態もちゃんと復元されます。
例題:完了機能の動きを頭の中でシミュレーションする
シナリオを一つ追ってみる
例えば、こんな操作をしたとします。
タスクを 2 つ追加する
「買い物に行く」
「メールを送る」
この時点で tasks はこうです。
[
{ title: "買い物に行く", isDone: false },
{ title: "メールを送る", isDone: false }
]
JavaScript次に、「メールを送る」のチェックボックスを ON にします。
checkboxElement.change イベントが発火
→ task.isDone = checkboxElement.checked;
→ そのタスクの isDone が true になる
→ saveTasks() で localStorage に保存
→ renderTasks() で再描画
再描画後は、
1件目:isDone = false → チェックなし、取り消し線なし
2件目:isDone = true → チェックあり、取り消し線あり
という状態になります。
ここまでを自分の頭で追えるようになると、
「コードを読む力」が一段上がります。
Day28 後半の完成コード(イメージ)
追加・削除・完了・保存・復元がそろった TODO
HTML はこれまでと同じ前提で、
JavaScript 部分のイメージをまとめておきます。
const taskInputElement = document.getElementById("taskInput");
const addButtonElement = document.getElementById("addButton");
const taskListElement = document.getElementById("taskList");
let tasks = [];
function addTask(text) {
const task = {
title: text,
isDone: false
};
tasks.push(task);
}
function deleteTask(index) {
tasks.splice(index, 1);
}
function saveTasks() {
const json = JSON.stringify(tasks);
localStorage.setItem("tasks", json);
}
function loadTasks() {
const json = localStorage.getItem("tasks");
if (json === null) {
tasks = [];
return;
}
tasks = JSON.parse(json);
}
function renderTasks() {
taskListElement.textContent = "";
tasks.forEach((task, index) => {
const taskItemElement = document.createElement("div");
const checkboxElement = document.createElement("input");
checkboxElement.type = "checkbox";
checkboxElement.checked = task.isDone;
const taskTextElement = document.createElement("span");
taskTextElement.textContent = task.title;
if (task.isDone) {
taskTextElement.style.textDecoration = "line-through";
taskTextElement.style.color = "#888";
} else {
taskTextElement.style.textDecoration = "none";
taskTextElement.style.color = "inherit";
}
const deleteButtonElement = document.createElement("button");
deleteButtonElement.textContent = "削除";
checkboxElement.addEventListener("change", () => {
task.isDone = checkboxElement.checked;
saveTasks();
renderTasks();
});
deleteButtonElement.addEventListener("click", () => {
deleteTask(index);
saveTasks();
renderTasks();
});
taskItemElement.appendChild(checkboxElement);
taskItemElement.appendChild(taskTextElement);
taskItemElement.appendChild(deleteButtonElement);
taskListElement.appendChild(taskItemElement);
});
}
addButtonElement.addEventListener("click", () => {
const text = taskInputElement.value.trim();
if (text === "") {
return;
}
addTask(text);
saveTasks();
renderTasks();
taskInputElement.value = "";
});
loadTasks();
renderTasks();
JavaScriptこの時点で、あなたの TODO アプリは、
タスク追加
タスク削除
完了状態の切り替え
完了状態の見た目反映
localStorage への保存・復元
という、かなり「実用レベル」の機能を備えています。
Day28 後半のまとめ
今日の後半で押さえたポイントを、ぎゅっと絞るとこうなります。
チェックボックスの change イベントで task.isDone を更新した
完了状態の変更時にも saveTasks() と renderTasks() を呼ぶようにしたisDone に応じて取り消し線や色を変え、見た目で完了が分かるようにしたtasks をオブジェクト配列にしたことで、完了状態も含めて localStorage に保存できるようになった
ここまで来ると、
「自分で機能を足していける TODO アプリ」の土台は完全にできています。
例えば次のステップとして、
完了タスクを下にまとめる
完了タスクを一括削除する
未完了だけ表示するフィルタを付ける
みたいな機能も、今の設計なら十分狙えます。
もう「おもちゃ」ではなく、ちゃんとしたミニアプリの世界に入っていますよ。
