0
0

【JavaScript】追加、編集、削除機能付きのToDoアプリを作ってみた その2

Last updated at Posted at 2024-09-08

はじめに

先日書いた以下の記事で、「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に保存し直します

参考記事

0
0
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0