はじめに
JavaScriptの学習を進める中で、学んだことのアウトプットとして、シンプルなTodoリスト作成に挑戦しました。
この記事では、その作成を通して得られた学びや気づきを備忘録としてまとめます。
概要
HTML・CSS(Bootstrap)・JavaScript を用いて作成したシンプルな Todo リストです。
以下の基本機能を備えています。
-
タスクの追加
入力欄に文字を入力し、「追加」ボタンを押すとリストにタスクが追加されます -
タスクの削除
各タスクに表示される「削除」ボタンを押すと、そのタスクがリストから削除されます -
タスクの編集
「編集」ボタンを押すと、タスクを編集可能な状態に切り替わります。編集後、「保存」または「キャンセル」が選べます -
完了状態の切り替え
チェックボックスのオン/オフでタスクの完了状態を切り替えることができ、完了したタスクには打ち消し線や文字色の変更が適用されます
学んだこと
template要素の活用
<ul><li>のような基本的なリスト要素であれば、createElementを使って動的に追加・削除する方法を学びました。
しかし、より複雑なUIを構築する際に、一つひとつの要素をcreateElementで地道に作成していく必要があるのかと悩んでいました。
そこで、今回template要素が非常に便利だと学びました。template要素を使うことで、HTMLの構造をテンプレートとして定義し、JavaScriptから簡単に複製してDOMに追加できるため、効率的にUIを構築できます。
cloneNodeとHTML要素
template要素を複製する際にcloneNode()メソッドを使いますが、ここで一つ注意点がありました。template要素のcontentプロパティをcloneNode(true)で複製すると、返されるのは通常のHTML要素(HTMLElement)ではなく、DocumentFragmentという特殊な型になる点です。
そのため、DocumentFragment の中から1つの HTMLElement を querySelector() で取り出す必要がありました。
const template = document.getElementById("todo-template");
const newClone = template.content.cloneNode(true); // DocumentFragment
const oldItem = document.querySelector(".todo-item"); // HTMLElement
// ❌ エラー(DocumentFragment, HTMLElement)
oldItem.parentElement.replaceChild(newClone, oldItem);
// ✅ 成功(HTMLElement, HTMLElement)
const newItem = newClone.querySelector(".todo-item"); // HTMLElement
oldItem.parentElement.replaceChild(newItem, oldItem);
イベント委譲
複数のTodoアイテムがある場合、どのTodoに対する操作なのかを特定するのが課題でした。
個々の要素にイベントリスナーを設定する方法では、動的に生成される要素の対応がわかりませんでした。
そこで、イベント委譲という手法を学びました。
これは、個々の要素にイベントリスナーを設定するのではなく、それらの親要素にイベントリスナーを設定し、クリックされた子要素をevent.targetで判別するという方法です。
この手法のおかげで、動的に追加されるTodoアイテムにも柔軟に対応できるようになりました。
// 親要素にクリックイベントを設定(イベント委譲)
todoList.addEventListener("click", (event) => {
if (event.target.classList.contains("delete-button")) {
// クリックされたボタンの親の.todo-itemを取得
const item = event.target.closest(".todo-item");
// delete処理
}
});
dataset属性によるデータ管理
idの代わりに、HTML要素にカスタムデータを格納できるdataset属性(data-*属性)を使って、TodoアイテムのIDを管理する方法を学びました。これにより、要素に紐づく情報をHTML側でシンプルに保持でき、JavaScriptからのアクセスも容易になります。
const clone = template.content.cloneNode(true);
const todoItem = clone.querySelector(".todo-item");
todoItem.dataset.id = id; // ここでIDをHTMLに埋め込む
findIndexとvalueOfの使い方
Todoリストで特定のタスクを更新したり削除したりする際、配列の中から該当のTodoアイテムを正確に特定する必要がありました。ここでオブジェクトの比較について深く学ぶことになりました。
当初、valueOf()メソッドを使えばオブジェクトの内容を比較できると考え、以下のように記述しました。
const todos = [
{ id: 1, name: "task1" },
{ id: 2, name: "task2" },
];
const target = { id: 2, name: "task2" };
const index = todos.findIndex((item) => item.valueOf() === target.valueOf());
console.log(index); // -1 ❌ 見つからない
しかし、JavaScriptでオブジェクト同士を===で比較する際は参照の比較が行われます。valueOf()はデフォルトでオブジェクト自身を返すため、たとえ内容が同じでも参照が異なれば常にfalseとなるのです。
この課題に対し、タスクの一意な識別子であるidプロパティを用いて比較することで解決できました。これにはArray.prototype.findIndex()メソッドが非常に有効でした。
const todos = [
{ id: 1, name: "task1" },
{ id: 2, name: "task2" },
];
const targetId = 2;
const index = todos.findIndex((item) => item.id === targetId);
console.log(index); // 1 ✅
感想
JavaScriptを学んでいく中で、自分の手で動くWebページを作れるようになり、ワクワクしました。
今回はシンプルなTodoリストでしたが、今後はもう少し複雑な機能にも挑戦していきたいです。
