JavaScriptでアプリを作成しました【2】【TodoList】


## 【要件】
##### 課題1. タスク追加機能
- 追加ボタン押下時にタスクを登録できる
- タスクには、「ID、コメント、状態」が含まれる
- IDは連番にする
- コメントはフォームで入力された値が表示される
- 状態には、「作業中」と表示される
- 削除ボタンが表示されている（ここでは押下時の動作はつけなくてよい

##### 課題2. タスク削除機能
- 削除ボタン押下時にそのタスクを削除できる
- タスク削除時はIDが振り直される
- 削除後、新たにタスクを追加するとIDが連番となっている

##### 課題3. タスク状態変更機能
- タスクの状態を変更できる
- 作業中の状態でボタンをクリックすると完了へ変更される
- 完了の状態でボタンをクリックすると作業中へ変更される

##### 課題4. タスク表示/非表示切り替え機能
- タスクの状態によって表示/非表示を切り替えできる
- 選択されたラジオボタンに応じて、タスクの表示を切り替える
- 「作業中」選択時に特定のタスクの状態を作業中→完了に切り替えた場合、そのタスクが非表示になる。
- 「完了」選択時に特定の特定のタスクの状態を完了→作業中に切り替えた場合、そのタスクが非表示になる。
- 「作業中」選択時にタスクを新規追加した場合、タスクが表示される
- 「完了」選択時にタスクを新規追加した場合、タスクは表示されない（が、裏ではちゃんと追加されている）



## 【作成にあたり学習した事】
- DOM操作(ブラウザとの連携)
- 関数
- 引数
- 返り値
- if文(条件分岐)
- for文(繰り返し文)
- 配列の操作
- オブジェクトの操作
- Chromeのデベロッパーツールでデバッグする方法

## 躓いた所と解決方法
#### 課題１：躓いた所
①　ID連番が表示されるが、コメントの前に実装できない。
②　作業中と削除ボタンが、コメントの下に表示される。

```index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>課題③</title>
</head>
<body>
    <h1>TODOリスト</h1>
    
    <p class="radio">
      <input type="radio" id="r1" name="list" value="1" checked><label for="r1">全て</label>
      <input type="radio" id="r2" name="list" value="2"><label for="r2">作業中</label>
      <input type="radio" id="r3" name="list" value="3"><label for="r3">完了</label>
    </p>

    
    <table>
      <thead>
        <tr>
          <th>ID</th>
          <th>コメント</th>
          <th>状態</th>
        </tr>
      </thead>
      <tbody id="list">
        <tr>
          <td></td>
        </tr>
      </tbody>
    </table>
    
    <h2>新規タスクの追加</h2>
    <input type="text" id="newtask">
    <button id="add">追加</button>

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

```main.js
'use strict';
{
//inputタグを取得して変数に入れる
const newtask = document.getElementById('newtask');

// 入力された値をリストに追加する
const add = document.getElementById('add');

// 連番用idを用意
let id = 0;
add.addEventListener('click',() => {
if(newtask.value !=='') {
    
// TODOリストに表示する
const list = document.getElementById('list'); 

// div要素を作成
const wrapper = document.createElement('div');
wrapper.className = 'wrap';
const divone = document.createElement('div');
const divtwo = document.createElement('div');

// 作成した要素に追加 ①
list.appendChild(wrapper);
wrapper.appendChild(divone);
wrapper.appendChild(divtwo);

// p.button要素の作成 ②
const p = document.createElement('p');
p.className = 'divonep';

const btnone = document.createElement('button');
btnone.innerHTML = '削除';
btnone.className = 'btnone';
const btntwo = document.createElement('button');
btntwo.innerHTML = '作業中';
btntwo.className = 'btntwo';

// ②を①に追加
wrapper.insertBefore(p, divone);
wrapper.insertBefore(btnone, divtwo);
wrapper.insertBefore(btntwo, btnone);

// inputで読み取った値を表示
p.innerHTML = newtask.value;
// idを表示
p.innerHTML += `${++id}`;

}    
});
}
```
#### 課題１：躓いた所の解決方法
① ID連番が表示されるが、コメントの前に実装できない。➡︎ 検証ツールで確認してみた結果、本来ならばtdタグにidとtask、buttonを格納したいがtbodyの中に入っているだけで、trタグ、tdタグで囲まれていない理由で、テーブルの表示が意図しない形になっていた。
##### tableタグの中身を下記に書き換えて、検証ツールで中身の仕組みを確認してみた

```index.html
<table>
  <thead>
    <tr>
      <th>ID</th>
      <th>コメント</th>
      <th>状態</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>0</td>
      <td>test</td>
      <td><button>作業中</button></td>
      <td><button>削除</button></td>
    </tr>
   </tbody>
</table>
```
##### 解決

```index.html

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>課題③</title>
</head>
<body>
    <h1>TODOリスト</h1>
    
    <p class="radio">
      <input type="radio" id="r1" name="list" value="1" checked><label for="r1">全て</label>
      <input type="radio" id="r2" name="list" value="2"><label for="r2">作業中</label>
      <input type="radio" id="r3" name="list" value="3"><label for="r3">完了</label>
    </p>

<table>
  <thead>
    <tr>
      <th>ID</th>
      <th>コメント</th>
      <th>状態</th>
    </tr>
  </thead>
    <tr>
      <td></td>
      <!-- <td>0</td>
      <td>test</td>
      <td><button>作業中</button></td>
      <td><button>削除</button></td> -->
    </tr>
    <tbody id="list"></tbody>
</table>
  
    
    <h2>新規タスクの追加</h2>
    <input type="text" id="newtask"/>
    <button id="add">追加</button>

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

```main.js

'use strict';
{
//inputタグを取得して変数に入れる
const newtask = document.getElementById('newtask');

// 入力された値をリストに追加する
const add = document.getElementById('add');

add.addEventListener('click',() => {
if(newtask.value =='') {
    return
}
  // olタグを取得して変数に入れる
  const list = document.getElementById('list')

  const id = list.children.length + 1;
  const comment = newtask.value;

  // 分かりやすい変数名をつけます
  const workButton = document.createElement('button');
  workButton.innerHTML = '作業中';

  const deleteButton = document.createElement('button');
  deleteButton.innerHTML = '削除';
  // テーブルの行に必要なデータの配列を作成します
  const cloumns = [id, comment, workButton, deleteButton]

  //　テーブルの行を作成します
  const tr = document.createElement('tr');

  // 配列の中身を一つずつ取り出して処理をします
  cloumns.forEach((cloumn) => {
  // テーブルの列を作成します
  const td = document.createElement('td');

  // HTMLタグ処理を変更
  if (cloumn instanceof HTMLElement) {
      td.appendChild(cloumn)
  } else {
    td.innerText = cloumn;
  }
  // 行に列を追加します
    tr.appendChild(td);
  })
  //テーブルに行を追加
  list.appendChild(tr);

  newtask.value = ''
})
}
```
#### 課題２：躓いた所
①削除ボタン押下時にそのタスクを削除できる ➡︎　削除ボタンを押したときに何かの処理を実行したいときは『addEventListener』関数を使い、
第一引数にはイベント(クリック,スクロールなど)を第二引数には実行したい関数を渡す。
今回は、削除ボタンが押されたときにタスクを削除したいので、『deleteButton』がクリックされたときにdeleteTaskを呼び出す。
※ 関数の定義方法はいくつか種類があるが一番良く使用されているアロー関数を使用。

```
一番使用されているアロー関数
例：deleteButton.addEventListener('click', () => deleteTask(taskId))
```
deleteTask関数を作成し、deleteTaskの引数には削除したいタスクのIDを渡す。
remove関数を呼び出してタスク(テーブルの一行の要素)を削除する。

※ document.getElementById(taskId);は、文字列ではなく、getElementByIdにtaskIdという変数を渡す。（）内は文字列ではないので、''（シングルコーテーション）を囲うことなく記述。

```
const deleteTask = (taskId) => {
  const task = document.getElementById(taskId)
  task.remove()
}
```

②タスク削除時はIDが振り直される

下記のようにしてidを取得していたがこれではタスクを削除すると`list`の中の要素数が<br>減ってしまい新しくタスクを作成すると連番が取得できない。

```
const id = list.children.length + 1
```
新しくidの変数を用意。タスクが作成されるたびにidに1を足すようにする。
下記のようにすればタスクが削除されてもidの連番は崩れない。
※ id += 1 と id++ は同じ意味

```
let id = 0

add.addEventListener('click', () => {
  if (newtask.value === '') {
    return
  }

  id += 1
  または
  id++
...
```

次にtaskIdをテーブルの行(タスク)のid属性に追加。
文字列に変数を埋め込みたいときはテンプレートリテラル``を使用。
こうすレバidが展開されてtask-1, task-2のようになる。

```
  const tr = document.createElement('tr')
  const taskId = `task-${id}`
  tr.id = taskId
```

③削除後、新たにタスクを追加するとIDが連番となっている
現状のコードだと連番表示になっているが、例えば1,2,3,4,5の連番があったとして5を削除すると次のIDは6からになり
1,2,3,4,6となるので、下記のコードに書き換え。

##### 課題２：躓いた所の解決方法
index.htmlは、特に変更ない。

```main.js

'use strict';
{
//inputタグを取得して変数に入れる
const newtask = document.getElementById('newtask');
// 入力された値をリストに追加する
const add = document.getElementById('add');
//削除ボタンを押したときに何かの処理を実行したい
const deleteTask = (btn) => {
  const task = btn.closest('tr');
  const tbody=task.closest('tbody');
  //remove関数を呼び出してタスク(テーブルの一行の要素)を削除します
  task.remove();
  tbody.querySelectorAll('tr').forEach((x,y)=>{
    x.setAttribute('id',`id-${y+1}`);
    x.querySelector('td').textContent=y+1;
  });
  id--;
}
//idの変数を用意
let id = 0;

add.addEventListener('click',() => {
  if(newtask.value =='') {
    return
  }
  id += 1;
  const list = document.getElementById('list')
  const tr = document.createElement('tr')

  const taskId = `task-${id}`
  tr.id = taskId
  const comment = newtask.value;
  
  // 分かりやすい変数名をつけます
  const workButton = document.createElement('button');
  workButton.innerHTML = '作業中';

  const deleteButton = document.createElement('button');
  deleteButton.innerHTML = '削除';

  deleteButton.addEventListener('click', () => deleteTask(deleteButton));
  // テーブルの行に必要なデータの配列を作成します
  const cloumns = [id, comment, workButton, deleteButton]

  // 配列の中身を一つずつ取り出して処理をします
  cloumns.forEach((cloumn) => {
    // テーブルの列を作成します
  const td = document.createElement('td');

   // HTMLタグ処理を変更
  if (cloumn instanceof HTMLElement) {
      td.appendChild(cloumn)
  } else {
    td.innerText = cloumn;
  }
  //行に列を追加します
    tr.appendChild(td);

  })
  //テーブルに行を追加
  list.appendChild(tr);
  newtask.value = '' 
})
   }
```
#### 課題3. 躓いた所
① タスクの状態を変更できる
② 作業中の状態でボタンをクリックすると完了へ変更され、完了の状態でボタンをクリックすると作業中へ変更される

addEventListenerメソッドを用いて、clickしたときに{}内の処理を行うようにする。
『作業中』のボタンを押したとき、innerHTMLの値が”作業中”と一致している場合、
workButtonのinnerHTML の値を『完了』に変更する。
そうでない場合(innerHTMLの値が『作業中』と一致しない場合)、
workButtonのinnerHTML の値を『作業中』に変更する。


```main.js
if (workButton.innerHTML === '作業中') {
　　　workButton.innerHTML = '完了';
　　} else {
　　　workButton.innerHTML = '作業中';
　　}
```
#### 課題4. 躓いた所

#### タスクの切り替えのサンプルコード

```main.js
const option = document.getElementsByName(“radio”);
const checkOption = () => {
    const progressTasks = document.querySelectorAll(‘.work’);
    const doneTasks = document.querySelectorAll(‘.finish’);
    if (option[1].checked) {
        progressTasks.forEach(element => {
            element.style.display = ‘’;
        });
        doneTasks.forEach(element => {
            element.style.display = ‘none’;
        });
    } else if (option[2].checked) {
        progressTasks.forEach(element => {
            element.style.display = ‘none’;
        });
        doneTasks.forEach(element => {
            element.style.display = ‘’;
        });
    } else {
        progressTasks.forEach(element => {
            element.style.display = ‘’;
        });
        doneTasks.forEach(element => {
            element.style.display = ‘’;
        });
    }
}
```

① タスクの状態によって表示/非表示を切り替えできる
② 選択されたラジオボタンに応じて、タスクの表示を切り替える
③ 「作業中」選択時に特定のタスクの状態を作業中→完了に切り替えた場合、そのタスクが非表示になる。
④「完了」選択時に特定の特定のタスクの状態を完了→作業中に切り替えた場合、そのタスクが非表示になる。
⑤ 「作業中」選択時にタスクを新規追加した場合、タスクが表示される
⑥ 「完了」選択時にタスクを新規追加した場合、タスクは表示されない（が、裏ではちゃんと追加されている）

#####躓いた所　1. checkTasksメソッドで表示／非表示を切り替える。
『workクラスがついているタスクを作業中』『finishクラスがついているタスクを完了』

```
const checkTasks = () => {
  const workingTasks = document.querySelectorAll(".work");
  const doneTasks = document.querySelectorAll(".finish");
}
```

##### 躓いた所 2. workクラス、finishクラスを設定する
workクラス、finishクラスがついているタグのdisplay状態を切り替えて、表示したり非表示したりするので、trタグにwork、finishクラスをつければ、表示／非表示が切り替えられる。

```
作業中のとき
         <tr class='work'>
            <td>ID</td>
            <td>コメント</td>
            <td>切り替えボタン</td>
            <td>削除ボタン</td>
        </tr>
````
```
完了のとき
         <tr class='finish'>
            <td>ID</td>
            <td>コメント</td>
            <td>切り替えボタン</td>
            <td>削除ボタン</td>
        </tr>
```

##### 課題4：躓いた所の解決方法

課題4の躓いた所1.2.を解決するのは、以下の３つが必要になる。

1. タスクを追加したときに、workクラスを付与する。
2.「作業中」ボタンを押したときに、workクラスを削除する。finishクラスを追加する。
3.「完了」ボタンを押したときに、finishクラスを削除する。workクラスを追加する。


#### 1. タスクを追加したときに、workクラスを付与する。

```
add.addEventListener("click", () => {
  〜〜略〜〜
  //テーブルに行を追加
  list.appendChild(tr);
 
  ⭐️ //trタグにworkクラスを追加する 
  ⭐️ tr.classList.add('work')

  newtask.value = "";
});
```

#### 2.「作業中」ボタンを押したときに、workクラスを削除する。finishクラスを追加する。<br>3.「完了」ボタンを押したときに、finishクラスを削除する。workクラスを追加する。

```
workButton.addEventListener("click", () => {
⭐️  // クリックされたボタンがある親のtrタグを取得する 　
⭐️  const tr = workButton.closest('tr'); 
    if (workButton.innerHTML === "作業中") {
      workButton.innerHTML = "完了";
⭐️   tr.classList.add('finish');
⭐️   tr.classList.remove('work'); 
    } else {
      workButton.innerHTML = "作業中";
⭐️  tr.classList.add('work');
⭐️  tr.classList.remove('finish'); 
    }
  });
```
####躓いた所　3. ラジオボタンを切り替えれる。（checkTasksメソッドが動作）
onchangeイベントが設定されていないので、onchangeイベントを設定して、ラジオボタンを変更したときに、checkTasksメソッドを呼び出す。

```
<p class="radio">
      <input type="radio" name="list" value="1"
        checked ="checked" 
⭐️        onchange="checkTasks()"
      >
      <label for="r1">全て</label>
      <input type="radio" id="radio-working" name="list" value="2"　
⭐️        onchange="checkTasks()"><label for="r2">作業中</label>
      <input type="radio" id="radio-done" name="list" value="3"　
⭐️        onchange="checkTasks()"><label for="r3">完了</label>
    </p>
```
optionは以下で取得していたのを書き換え。

```
修正前　const option = document.getElementsByName("radio");
修正後　const option = document.getElementsByName("list");
```
## 完成！！
```html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>課題③</title>
</head>
<body>
    <h1>TODOリスト</h1>
 <p class="radio">
      <input type="radio" name="list" value="1"
        checked ="checked" onchange="checkTasks()"
      >
      <label for="r1">全て</label>
      <input type="radio" id="radio-working" name="list" value="2"　
        onchange="checkTasks()"><label for="r2">作業中</label>
      <input type="radio" id="radio-done" name="list" value="3"　
        onchange="checkTasks()"><label for="r3">完了</label>
    </p>

<table>
  <thead>
    <tr>
      <th>ID</th>
      <th>コメント</th>
      <th>状態</th>
    </tr>
  </thead>
    <tr>
      <td></td>
      <!-- <td>0</td>
      <td>test</td>
      <td><button>作業中</button></td>
      <td><button>完了中</button></td>
      <td><button>削除</button></td> -->
    </tr>
    <tbody id="list"></tbody>
</table>
<h2>新規タスクの追加</h2>
    <input type="text" id="newtask"/>
    <button id="add">追加</button>

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

```js
"use strict";

//inputタグを取得して変数に入れる
const newtask = document.getElementById("newtask");
// 入力された値をリストに追加する
const add = document.getElementById("add");
//削除ボタンを押したときに何かの処理を実行したい

const deleteTask = (btn) => {
  const task = btn.closest("tr");
  const tbody = task.closest("tbody");
  //remove関数を呼び出してタスク(テーブルの一行の要素)を削除します
  task.remove();
  tbody.querySelectorAll("tr").forEach((x, y) => {
    x.setAttribute("id", `id-${y + 1}`);
    x.querySelector("td").textContent = y + 1;
  });
  id--;
};
//idの変数を用意
let id = 0;

add.addEventListener("click", () => {
  if (newtask.value == "") {
    return;
  }
  id += 1;
  const list = document.getElementById("list");
  const tr = document.createElement("tr");

  const taskId = `task-${id}`;
  tr.id = taskId;
  const comment = newtask.value;

  // 分かりやすい変数名をつけます
  const workButton = document.createElement("button");
  workButton.innerHTML = "作業中"; //完了の状態でボタンをクリックすると作業中へ変更される
  //作業中の状態でボタンをクリックすると完了へ変更される
  workButton.addEventListener("click", () => {
    // クリックされたボタンがある親のtrタグを取得する 　
    const tr = workButton.closest('tr');
    if (workButton.innerHTML === "作業中") {
      workButton.innerHTML = "完了";
      tr.classList.add('finish');
      tr.classList.remove('work'); 
    } else {
      workButton.innerHTML = "作業中";
      tr.classList.add('work');
      tr.classList.remove('finish');
    }
  });

  const deleteButton = document.createElement("button");
  deleteButton.innerHTML = "削除";

  deleteButton.addEventListener("click", () => deleteTask(deleteButton));
  // テーブルの行に必要なデータの配列を作成します
  const cloumns = [id, comment, workButton, deleteButton];

  // 配列の中身を一つずつ取り出して処理をします
  cloumns.forEach((cloumn) => {
    // テーブルの列を作成します
    const td = document.createElement("td");

    // HTMLタグ処理を変更
    if (cloumn instanceof HTMLElement) {
      td.appendChild(cloumn);
    } else {
      td.innerText = cloumn;
    }
    //行に列を追加します
    tr.appendChild(td);
  });
  //テーブルに行を追加
  list.appendChild(tr);
  //trタグにworkクラスを追加する 
  tr.classList.add('work')
  newtask.value = "";
});

// チェックボックスの表示の切替
const option = document.getElementsByName("list");

const checkTasks = () => {
  const workingTasks = document.querySelectorAll(".work");
  const doneTasks = document.querySelectorAll(".finish");

  if (option[1].checked) {
    workingTasks.forEach((workingTasks) => {
      workingTasks.style.display = "";
    });
    doneTasks.forEach((doneTasks) => {
      doneTasks.style.display = "none";
    });
  } else if (option[2].checked) {
    doneTasks.forEach((doneTasks) => {
      doneTasks.style.display = "";
    });
    workingTasks.forEach((workingTasks) => {
      workingTasks.style.display = "none";
    });
  } else {
    workingTasks.forEach((workingTasks) => {
      workingTasks.style.display = "";
    });
    doneTasks.forEach((doneTasks) => {
      doneTasks.style.display = "";
    });
  }
};
```

## 参考サイト
[表で見やすくしたい！HTMLでテーブルを表示する方法【初心者向け】現役Webデザイナーが解説](https://techacademy.jp/magazine/5858)

[DOMContentLoadedイベント：DOMツリーの構築が完了したとき](https://www.javadrive.jp/javascript/event/index15.html)

[JavaScriptでcreateElementメソッドを使いHTMLを動的生成する方法を現役エンジニアが解説【初心者向け】](https://techacademy.jp/magazine/36505)

[HTMLの中身を変更 – innerHTML, innerText, textContentsの違いとは？【JavaScript入門】](https://uxbear.me/innerhtml-innertext-textcontents-javascript/)

[簡素なtodoリストをjavascriptで作る](https://qiita.com/takeshi_104/items/3138195ba0c58e37b1c3)

[【JavaScript: ToDoリスト】table要素を理解してToDoを表示させる方法](https://ina-yona-mochiko.com/programming/javascript/todolist-table/)

[【JavaScript: Todoリスト】”作業中”と”完了”を切り替える方法](https://ina-yona-mochiko.com/programming/javascript/todolist-if-return/)