【JavaScript】追加、編集、削除機能を持つToDoアプリを作ってみた

## はじめに
これまで勉強してきたことを活かして、追加、編集、削除機能を持つToDoアプリを作ってみたので、その時のことをメモに残します。
▼（2024/09/07追記）▼
コメントにて完成品に不具合があるとのご指摘をいただきましたので、完成品と記事に掲載しているコードの修正を行いました。
## 完成品
<p class="codepen" data-height="400" data-default-tab="result" data-slug-hash="mdZQaZm"</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

## 実装したいこと
- テキストボックスにToDoを入力し追加ボタンを押すと、ToDoがリストに追加される
- テキストボックスが空欄のまま追加ボタンを押すと、アラートが出る
- リストに追加された各ToDoには、編集ボタンと削除ボタンを付与する
- 編集ボタンを押すと、ToDoのテキスト部分がToDo名の値を保持したテキストボックスに変化する
- ToDoの編集が完了したら、テキストボックスが消えて新たなToDo名のテキストに変化する
- ToDo編集時も、テキストボックスが空欄であればアラートを出す
- 削除ボタンを押すと、ToDoが削除される
## 製作過程
### HTML
```html:index.html
<form action="#" id="todoForm">
    <input type="text" name="todo" id="todo">
    <input type="submit" value="追加する">
</form>

<ul id="todoList"></ul>
```
### JavaScript
#### 全コード
```javascript:app.js
const form = document.querySelector("#todoForm");
const list = document.querySelector("#todoList");

// ToDo追加の関数
const addTodo = (val) => {
    // テキストボックスが空欄の場合はアラートを出す
    if (!val) {
        alert("ToDoを入力してください!!");
    } else { // テキストボックスが空欄でなければToDoを追加
        // liタグを作成
        const todo = 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タグ、編集ボタン、削除ボタンを追加
        todo.append(label, editBtn, deleteBtn);

        // ulタグにliタグを追加
        list.append(todo);
    }
};

// ToDo編集の関数
const editTodo = (target) => {
    if (target.innerText === "編集する") {
        // 編集ボタンのテキストを変更
        target.innerText = "登録する";

        // 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を入力してください!!")
        } else { // テキストボックスが空欄でなければToDoを修正する
            // spanタグを作成し、spanタグのテキストにテキストボックスのvalue属性を代入
            const label = document.createElement("span");
            label.innerText = editInput.value;

            // テキストボックスをspanタグに差し替える
            editInput.replaceWith(label);

            // 編集ボタンのテキストを変更
            target.innerText = "編集する";
        }
    }
};

// ToDo削除の関数
const deleteTodo = (target) => {
    // 削除ボタンの親liタグを削除
    target.closest("li").remove();
};

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);
    // テキストボックスを初期化
    document.querySelector("#todo").value = "";
});
```
#### 解説１: ToDo追加
```javascript:app.js
// ToDo追加の関数
const addTodo = (val) => {
    // テキストボックスが空欄の場合はアラートを出す
    if (!val) {
        alert("ToDoを入力してください!!");
    } else { // テキストボックスが空欄でなければToDoを追加
        // liタグを作成
        const todo = 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タグ、編集ボタン、削除ボタンを追加
        todo.append(label, editBtn, deleteBtn);

        // ulタグにliタグを追加
        list.append(todo);
    }
};
```
- テキストボックスが空欄
    - アラートを出す
- テキストボックスが空欄でない
    - テキストボックスに入力された値を取得し、以下のような構造のHTMLタグが`ul`タグの中に追加される
        ```html:index.html
        <li>
            <span>テキストボックスに入力された値</span>
            <button class="editBtn">編集する</button>
            <button class="deleteBtn">削除する</button>
        </li>
        ```
#### 解説２: ToDo削除
```javascript:app.js
// ToDo削除の関数
const deleteTodo = (target) => {
    // 削除ボタンの親liタグを削除
    target.closest("li").remove();
};
```
- 削除ボタンがクリックされたら、親タグである`li`タグごと削除される
#### 解説３: ToDo編集機能
```javascript:app.js
// ToDo編集の関数
const editTodo = (target) => {
    if (target.innerText === "編集する") {
        // 編集ボタンのテキストを変更
        target.innerText = "登録する";

        // 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を入力してください!!")
        } else { // テキストボックスが空欄でなければToDoを修正する
            // spanタグを作成し、spanタグのテキストにテキストボックスのvalue属性を代入
            const label = document.createElement("span");
            label.innerText = editInput.value;

            // テキストボックスをspanタグに差し替える
            editInput.replaceWith(label);

            // 編集ボタンのテキストを変更
            target.innerText = "編集する";
        }
    }
};
```
- ToDoの編集を行う場合のプログラム
    - 編集ボタンのテキストを「編集する」から「登録する」に変更する
    - テキストボックスを作成し、value属性値を`span`タグのテキスト（ToDo）に設定する
    - `span`タグをテキストボックスに差し替える
- ToDoの編集が完了した場合のプログラム
    - テキストボックスが空欄でない
        - `span`タグを作成し、テキストをテキストボックスのvalue属性値に設定する
        - テキストボックスを`span`タグに差し替える
        - 編集ボタンのテキストを「登録する」から「編集する」に変更する
    - テキストボックスが空欄
        - アラートを出す