DOM操作を使った簡易的なtodoリストアプリを作ってみた

現在つよぽんさんの元でフロントエンド、バックエンドについて教わっています。今回はその中の課題の１つ「【エクササイズ】DOM操作をしてTodoリストを作成する【JavaScript】」に取り組んだ際の流れ、コードの説明をしていきたいと思います。

[つよぽんさんのTwitterアカウント](https://twitter.com/tsuyopon_xyz)
[学習サイト【Web白熱教室】](https://tsuyopon.xyz/)
[MENTA](https://menta.work/plan/677)
## 制作物の完成サンプル

[github](https://github.com/tsuyopon-xyz/js_excercise_for_frontend_4)

---

## 必要な機能
今回は各挙動をDOM操作を用いて実行することとする

- タスク入力欄
  - todoに追加したいタスク（文字）を入力する
- 追加ボタン
  - クリックすると、入力したタスクがtodoリストに追加される
- todoリスト一覧
  - 入力されたタスクを表示する。
  - 「`表示される順の番号` : `入力したタスク`」と表示する。
- 削除ボタン
  - クリックするとタスクを一覧から削除することができる。
    - 残ったタスクの`表示される順の番号`はタスクが削除された後の順番に変更されなければならない。

今回はあくまで純粋なtodoリストを作る課題なので、CSS等の装飾機能はつけない

---

## コード


### HTML
```html:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>【エクササイズ】Todoリストの作成</title>
</head>
<body>
  <h1>Todoアプリ</h1>
  <div>
    <label for="input-todo-box">Todoタスクの入力 : </label>
    <input id="input-todo-box" type="text">
    <button id="add-button">追加</button>
  </div>

  <h2>現在のTodoリスト</h2>
  <ul id="todo-list"></ul>

  <script src="main.js"></script>
</body>
</html>
```


---


### Javascript

```js:タスクを保存するための配列
const todos = [];
```
入力したタスクをこちらに配列として保存します。



```js:DOM要素を用いてID値を会得する
const textBox = document.getElementById('input-todo-box');
const addButton = document.getElementById('add-button');
const todoListElement = document.getElementById('todo-list');
```

DOM操作に用いるために必要な要素を`document.getElementById`で取得します。
今回必要なのは
- タスクの入力 → inputタグのid`input-todo-box`
- タスクの追加 → buttonタグのid`add-button`
- タスクのリスト表示 → ulタグのid`todo-list`

なので、この３つを取得しました。

[Document.getElementById()【MDN】](https://developer.mozilla.org/ja/docs/Web/API/Document/getElementById)

```js:追加ボタンをクリックした際の挙動(DOM操作)

addButton.addEventListener("click", event => {
  const todo = textbox.value;
  textbox.value = "";
  if(todo && todo.match(/\S/g)){
    todos.push(todo);
    showTodo();
  }
});
```

`addEventListener`で`addButton`をクリックした際の挙動を設定します。

[EventTarget.addEventListener()【MDN】](https://developer.mozilla.org/ja/docs/Web/API/EventTarget/addEventListener)


まず変数`todo`にテキストボックスに入力した文字を`value`プロパティを用いて代入します。
その後、入力した後のテキストボックスは空にしなければなりませんので、`""`を代入します。

また、テキストボックス内が空のままだったり、スペース入力でそのまま空のタスクがリストに追加されるのはよくないですので、`if`を使い、todoに文字が入っている場合にのみリストに追加されるようにします。
ifの中で、先に作った定数`todos`の配列に`todo`を追加します。


しかしこれではHTML上にタスクが表示されることがありません。なので、その後HTML上にタスクを表示する関数`showTodo`を宣言し、タスクが追加されるたびに実行されるようにします

```js:todosの中身等をHTML上に表示するための関数
const showTodo = () => {
  // HTML上の表示を一旦リセットする
  while(todoListElement.firstChild) {
    todoListElement.removeChild(todoListElement.firstChild);
  }
  todos.forEach((todo, index) => {
    // ulに追加するためにliタグの作成、todos内の配列を代入
    const liElement = document.createElement("li");
    liElement.textContent = `${index + 1} : ${todo}$`;

    // デリートボタンの作成
    const deleteButton = document.createElement("button");
    deleteButton.textContent = "削除";
    
    //deleteButtonをクリックした際の挙動
    deleteButton.addEventListener("click", event => {
      deleteTodo(index);
    });
  
  
    //作成した要素をHTML上に表示させる
    liElement.appendChild(deleteButton);
    todoListElement.appendChild(liElement);
  

  
  });
};
```

上のDOM操作で宣言した`showTodo`を作成します。

まず最初に一旦HTML上に表示されているタスクを全て削除。なぜ消すか理由は後述します。

次に`forEach`でtodosをイテレーション処理させ、HTML上にタスクが表示されるようにします。
まず、`todoListElement`に渡されているIDは`todo-list`、IDが入っているタグ要素は`ul`ですね。`ul`に入る要素は`li`、なのでliを`document.createElement`で作成、変数に代入し、`textContent`プロパティでtodos内の値を代入します。index値そのままを代入すると0からのスタートになるので、1を足します。

[Node.textContent【MDN】](https://developer.mozilla.org/ja/docs/Web/API/Node/textContent)

[Document.createElement【MDN】](https://developer.mozilla.org/ja/docs/Web/API/Document/createElement)


デリートボタン、それとデリートボタンがクリックされた際の挙動もここで作成します。(入力したタスクに結び付けないといけないため)
`button`要素を`createElement`で作成して、デリートボタンがクリックされた際、`deleteTodo`を実行するようにします。

最後に、liElementとdeteleButtonをtodoListElementに`appendChild`で追加します。これで追加したタスクがページ上に表示されるようになります。

[Node.appendChild【MDN】](https://developer.mozilla.org/ja/docs/Web/API/Node/appendChild)

```js:todoの削除
const deleteTodo = index => {
  todos.splice(index, 1);
  showTodo();
};

```

最後にdeleteButtonをクリックした際に宣言される`deleteTodo`の作成です。
`splice`を使い、インデックス値から１つ目の値、つまりdeleteButtonと結び付けられているタスク(liElement)を削除し、配列の中身を変更します。そして再度showTodoを宣言してタスク一覧を表示させます。今回は課題として関数を別で作るようにしましたが、直接addEventListenerに記述しても問題なく動作します。

[Array.prototype.splice()【MDN】](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/splice)




---
## つまづき

### タスクを入力すると重複して表示される。
これは`showTodo`で最初のwhile文を記述してなかったために起きた症状です。`todoListElement`内にどんどん溜まっていく一方ですから当たり前ですね。

while文の中身は、`firstchild`でtodoListElement内にタスクが入ってる(true)ことを判別した場合、`removechild`でtodoListElement内の`firstChild`を削除し続けるループ処理を実行しています

[Node.firstChild【MDN】](https://developer.mozilla.org/ja/docs/Web/API/Node/firstChild)

[Node.removeChild【MDN】](https://developer.mozilla.org/ja/docs/Web/API/Node/removeChild)

### 削除ボタンを押したら必ず１番目のタスクが削除される。
単純にindex値の渡し忘れでした。

### liElementとdeleteButtonを別々にtodoListElementに追加した場合改行される
```js
todoListElement.appendChild(liElement);
todoListElement.appendChild(deleteButton);
```
とすると、タスクとボタンの間で改行が発生しました、この追加方法だとHTMLに直すと

```html
<ul>
  <li>...</li>
  <button>...</button>
</ul>
```

このように、`ul`の中に`li`と`button`が別々に追加されるので、改行が発生します。なので

```js
liElement.appendChild(deleteButton);
todoListElement.appendChild(liElement);
```

こうすると

```html
<ul>
  <li>...<button>...</button></li>
</ul>
```
`li`の中に`button`を追加した後、`ul`に追加しているので、改行されずに表示されます。
@tsuyopon_xyz さんありがとうございます！


### テキストボックスにスペース入力した後追加をすると空のタスクがリストに追加される
```js
todo.match(/\S/g)
```
で解決しました、意味はよくわかりませんが、

> \S	
スペース以外の文字にマッチします。 [^ \f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff] に相当します。
例えば /\S\w*/ は "foo bar" の 'foo' にマッチします。

とのことです。

[正規表現【MDN】](https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions)

---

## 今後してみたいこと

### CSSを使った装飾
現状の見た目だとかなり味気ないので、webサイトとして見れる形にしたい。

### ユーザー登録機能を使って個々にtodoを作れるようにする
上記に加え、Webアプリとして機能するようにしてみたい。

---
みていただきありがとうございました