はじめに
先日書いた以下の記事で、「IndexedDBやlocalStorageを使えばより実用的になります」とのコメントをいただいたので、この度localStorageを使ってToDoアプリを改良してみました。
完成品
See the Pen Untitled by ry0h3i (@ry0h3i) on CodePen.
コード解説
全コード
script.js
const form = document.querySelector("#todoForm");
const list = document.querySelector("#todoList");
const storage = localStorage;
const todoArr = [];
const storageKey = "key";
// localStorageへデータを保存する
const saveStorage = (key, val) => {
storage.setItem(key, JSON.stringify(val));
};
// localStorageからデータを取得する
const getStorage = (key) => {
// localStorageにデータが残っていれば、データを配列に変換して呼び出し元に返す
// localStorageにデータが残っていなければ、空の配列を呼び出し元に返す
return JSON.parse(storage.getItem(key)) ?? [];
};
// localStorageを初期化する
const clearStorage = (key) => {
storage.removeItem(key);
};
// ToDoを追加するための関数
const addTodo = (val) => {
// テキストボックスが空欄の場合はアラートを出す
if (!val) {
alert("ToDoを入力してください!!");
return;
}
// liタグを作成する
const item = document.createElement("li");
// spanタグを作成し、テキストボックスの入力値を代入する
const label = document.createElement("span");
label.innerText = val;
// 編集ボタンを作成する
const editBtn = document.createElement("button");
editBtn.classList.add("editBtn");
editBtn.innerText = "編集する";
// 削除ボタンを作成する
const deleteBtn = document.createElement("button");
deleteBtn.classList.add("deleteBtn");
deleteBtn.innerText = "削除する";
// liタグにspanタグ、編集ボタン、削除ボタンを追加する
item.append(label, editBtn, deleteBtn);
// ulタグにliタグを追加する
list.append(item);
};
// ToDoを保存するための関数
const saveTodo = (val) => {
// localStorageに渡す配列にテキストボックスの入力値を追加する
todoArr.push(val);
// localStorageにデータ(配列)を保存する
saveStorage(storageKey, todoArr);
};
// アプリ開始時とリロード時にデータの確認を行うための関数
const readTodo = () => {
// localStorageのデータを取得する
const todos = getStorage(storageKey);
// localStorageにデータが残っていなければ、処理を終える
if (todos.length === 0) return;
// localStorageにデータが残っていた場合
for (let i = 0; i < todos.length; i++) {
// 返ってきたデータ(配列)の要素を取り出し、localStorageに渡す配列に追加する
const todo = todos[i];
todoArr.push(todo);
// localStorageにデータを保存する
saveStorage(storageKey, todoArr);
// ToDoを追加する
addTodo(todo);
}
};
// ToDoを編集するための関数
const editTodo = (target) => {
// クリックされた編集ボタンにクラスを付与する
target.classList.toggle("isEdited");
// クリックされていないその他のボタンを取得する
const otherBtns = document.querySelectorAll("button:not(.isEdited)");
if (target.classList.contains("isEdited")) {
// 編集ボタンのテキストを変更
target.innerText = "登録する";
// クリックされていないその他の編集ボタンを押せないようにする
for (let btn of otherBtns) {
btn.disabled = true;
}
// spanタグを取得
const label = target.previousElementSibling;
// テキストボックスを作成し、value属性をspanタグのToDoに設定
const editInput = document.createElement("input");
editInput.setAttribute("type", "text");
editInput.setAttribute("value", label.innerText);
// spanタグをテキストボックスに差し替える
label.replaceWith(editInput);
} else {
// テキストボックスを取得する
const editInput = target.previousElementSibling;
// テキストボックスが空欄の場合はアラートを出す
if (!editInput.value) {
alert("ToDoを入力してください!!");
return;
}
// localStorageに渡す配列の要素を全て削除する
todoArr.splice(0);
// localStorageを初期化する
clearStorage(storageKey);
// spanタグを作成し、テキストボックスの入力値を代入する
const label = document.createElement("span");
label.innerText = editInput.value;
// テキストボックスをspanタグに差し替える
editInput.replaceWith(label);
// 全てのliタグを取得する
const items = document.querySelectorAll("li");
for (let item of items) {
// li > spanのテキストを取得する
const todoVal = item.querySelector("span").innerText;
// ToDoを保存する
saveTodo(todoVal);
}
// クリックされていないその他のボタンを再び押せるようにする
for (let btn of otherBtns) {
btn.disabled = false;
}
// 編集ボタンのテキストを変更
target.innerText = "編集する";
}
};
// ToDoを削除するための関数
const deleteTodo = (target) => {
// 削除ボタンの親liタグを削除
target.closest("li").remove();
// localStorageに渡すための配列の要素を全て削除する
todoArr.splice(0);
// localStorageを初期化する
clearStorage(storageKey);
// liタグを全て取得する
const items = document.querySelectorAll("li");
// ToDoが0個だった場合、処理を終える
if (!items) return;
// ToDoが残っている場合
for (let item of items) {
// li > spanのテキストを取得する
const todoVal = item.querySelector("span").innerText;
// ToDoを保存する
saveTodo(todoVal);
}
};
// アプリ開始時とリロード時にデータの確認を行う
readTodo();
document.addEventListener("click", function (e) {
// イベントが発生した要素を取得
const element = e.target;
if (!(element instanceof HTMLElement)) return;
// 編集ボタンがクリックされた場合、ToDo編集の関数を実行する
if (element.matches(".editBtn")) {
editTodo(element);
}
// 削除ボタンがクリックされた場合、ToDo削除の関数を実行する
else if (element.matches(".deleteBtn")) {
deleteTodo(element);
}
});
form.addEventListener("submit", function (e) {
e.preventDefault();
// テキストボックスの入力値を取得する
const todoVal = document.querySelector("#todo").value;
// ToDoを追加する
addTodo(todoVal);
// ToDoを保存する
saveTodo(todoVal);
// テキストボックスの値を初期化する
document.querySelector("#todo").value = "";
});
前回から変更した箇所
イベントリスナー周り
script.js
form.addEventListener("submit", function (e) {
e.preventDefault();
// テキストボックスの入力値を取得する
const todoVal = document.querySelector("#todo").value;
// ToDoを追加する
addTodo(todoVal);
// ToDoを保存する
saveTodo(todoVal);
// テキストボックスの値を初期化する
document.querySelector("#todo").value = "";
});
- ToDoを追加した後に、ToDoをlocalStorageに保存する処理を追加しました
localStorage周り
script.js
const storage = localStorage;
const todoArr = [];
const storageKey = "key";
// localStorageへデータを保存する
const saveStorage = (key, val) => {
storage.setItem(key, JSON.stringify(val));
};
// localStorageからデータを取得する
const getStorage = (key) => {
// localStorageにデータが残っていれば、データを配列に変換して呼び出し元に返す
// localStorageにデータが残っていなければ、空の配列を呼び出し元に返す
return JSON.parse(storage.getItem(key)) ?? [];
};
// localStorageを初期化する
const clearStorage = (key) => {
storage.removeItem(key);
};
-
todoArr
... ToDoを格納し、localStorageにデータとして保存するための配列 -
storageKey
... localStorageに渡すキー
script.js
// ToDoを保存するための関数
const saveTodo = (val) => {
// localStorageに渡す配列にテキストボックスの入力値を追加する
todoArr.push(val);
// localStorageにデータ(配列)を保存する
saveStorage(storageKey, todoArr);
};
- テキストボックスの入力値をlocalStorageに保存する配列へ追加して、その配列をlocalStorageに保存します
script.js
// ToDoを削除するための関数
const deleteTodo = (target) => {
// 削除ボタンの親liタグを削除
target.closest("li").remove();
// localStorageに渡すための配列の要素を全て削除する
todoArr.splice(0);
// localStorageを初期化する
clearStorage(storageKey);
// liタグを全て取得する
const items = document.querySelectorAll("li");
// ToDoが0個だった場合、処理を終える
if (!items) return;
// ToDoが残っている場合
for (let item of items) {
// li > spanのテキストを取得する
const todoVal = item.querySelector("span").innerText;
// ToDoを保存する
saveTodo(todoVal);
}
};
- ToDoを削除したら、localStorageのデータを整理するために、localStorageとlocalStorageに渡す配列を一旦リセットします
- ToDoが他に残っている場合はそれらを取得して、localStorageに保存し直します
script.js
// ToDoを編集するための関数
const editTodo = (target) => {
// クリックされた編集ボタンにクラスを付与する
target.classList.toggle("isEdited");
// クリックされていないその他のボタンを取得する
const otherBtns = document.querySelectorAll("button:not(.isEdited)");
if (target.classList.contains("isEdited")) {
// 編集ボタンのテキストを変更
target.innerText = "登録する";
// クリックされていないその他の編集ボタンを押せないようにする
for (let btn of otherBtns) {
btn.disabled = true;
}
// spanタグを取得
const label = target.previousElementSibling;
// テキストボックスを作成し、value属性をspanタグのToDoに設定
const editInput = document.createElement("input");
editInput.setAttribute("type", "text");
editInput.setAttribute("value", label.innerText);
// spanタグをテキストボックスに差し替える
label.replaceWith(editInput);
} else {
// テキストボックスを取得する
const editInput = target.previousElementSibling;
// テキストボックスが空欄の場合はアラートを出す
if (!editInput.value) {
alert("ToDoを入力してください!!");
return;
}
// localStorageに渡す配列の要素を全て削除する
todoArr.splice(0);
// localStorageを初期化する
clearStorage(storageKey);
// spanタグを作成し、テキストボックスの入力値を代入する
const label = document.createElement("span");
label.innerText = editInput.value;
// テキストボックスをspanタグに差し替える
editInput.replaceWith(label);
// 全てのliタグを取得する
const items = document.querySelectorAll("li");
for (let item of items) {
// li > spanのテキストを取得する
const todoVal = item.querySelector("span").innerText;
// ToDoを保存する
saveTodo(todoVal);
}
// クリックされていないその他のボタンを再び押せるようにする
for (let btn of otherBtns) {
btn.disabled = false;
}
// 編集ボタンのテキストを変更
target.innerText = "編集する";
}
};
- クリックされた編集ボタンに
isEdited
クラスを付け外しして、isEdited
クラスの有無で処理を切り替えます - 1つの編集ボタンがクリックされたら、その他のボタンは編集作業が完了するまで押せないようにします
- ToDo編集後にもう一度編集ボタンがクリックされたら、一旦localStorageとlocalStorageに保存する配列をリセットし、現存するToDoをlocalStorageに保存し直します
script.js
// アプリ開始時とリロード時にデータの確認を行うための関数
const readTodo = () => {
// localStorageのデータを取得する
const todos = getStorage(storageKey);
// localStorageにデータが残っていなければ、処理を終える
if (todos.length === 0) return;
// localStorageにデータが残っていた場合
for (let i = 0; i < todos.length; i++) {
// 返ってきたデータ(配列)の要素を取り出し、localStorageに渡す配列に追加する
const todo = todos[i];
todoArr.push(todo);
// localStorageにデータを保存する
saveStorage(storageKey, todoArr);
// ToDoを追加する
addTodo(todo);
}
};
// アプリ開始時とリロード時にデータの確認を行う
readTodo();
- アプリ開始時とリロード時にlocalStorageのデータを確認します
- localStorageにデータが残っていた場合、返ってきたデータをlocalStorageへ渡す配列に追加し、その配列をlocalStorageに保存し直します
参考記事