0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【JavaScript】ライブラリ使わず、TODOアプリ作ってみた

Posted at

どうもー。
Reactでアプリ開発をしていますが(あ、趣味でね)、ChatGPTに聞いてばかりで全然自分で考えてコードが書いてなくね?ていうか、そもそもしたい動作はあるのにそれを実現するためのプログラムの書き方が思いつかない…。。。

これは致命的ではないか!?

というわけで理解しているつもりでいたJavaScriptをちゃんと理解してないのでは?と思ったので基礎から叩き込むことにしました。

因みに作るブツはこちら⭐️
スクリーンショット 2024-12-06 18.32.08.png

今回の課題はこちら

作るもの
TODOアプリ←結構作りがちw

使用してもいい言語
・HTML
・CSS
・Sass(これはまぢで便利よ)
・JavaScript

機能
・TODOの追加
・TODOの削除
・TODOの編集
・編集時ステータスの変更(TODO/進行中/完了)
・ステータスごとの表示分け
・検索機能
・並び替え(期限の早い順・遅い順)

やばい、ちょっとハードル上げすぎたか?まあ、出来るようになるためにはこれくら当たり前か。。。
というわけで作っていこうと思いますが、その前に
見た目がそれっぽくないとなんかやる気が出ないので整えます。

1. HTMLとCSSで見た目を整える

HTML↓

todo.html
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>TODOアプリ</title>
    <link
      rel="stylesheet"
      href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"
    />
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0&icon_names=swap_vert"
    />
    <link rel="stylesheet" href="./css/todo.css" />
    <script src="./js/todo.js"></script>
  </head>
  <body>
    <header class="todo-color">
      <h1>TODOアプリ</h1>
    </header>
    <main>
      <section class="add-todo inner">
        <div class="add-todo__button todo-color" id="add-todo-button">
          <i class="fa-solid fa-plus"></i>
          <h2>タスクを追加</h2>
        </div>
        <div class="add-todo__modal" id="new-todo-modal">
          <div class="add-todo__modal-container" id="new-todo-container">
            <i
              class="add-todo__modal-icon fa-solid fa-xmark"
              id="modal-close"
            ></i>
            <h2>新規作成</h2>
            <p class="error-message" id="error-message"></p>
            <div>
              <label for="new-todo-title">タイトル</label>
              <input type="text" id="new-todo-title" />
            </div>
            <div>
              <label for="new-todo-content">内容(空欄可)</label>
              <textarea id="new-todo-content"></textarea>
            </div>
            <div>
              <label for="new-todo-person">担当者</label>
              <input type="text" id="new-todo-person" />
            </div>
            <div>
              <label for="new-todo-date">期限</label>
              <input
                class="new-todo__modal-date"
                type="date"
                id="new-todo-date"
              />
            </div>
            <div class="add-todo__modal-submit">
              <button class="add-todo__modal-button" id="submit-new-todo">
                追加
              </button>
            </div>
          </div>
        </div>
      </section>
      <section class="select-todo-status inner">
        <div class="select-todo-status__list" id="tab-status-menu">
          <p class="_todo-active">TODO</p>
          <p>進行中</p>
          <p>完了</p>
        </div>
      </section>
      <section class="todo-list inner">
        <div class="todo-list__parts-row">
          <div class="todo-list__search">
            <i class="fa-solid fa-magnifying-glass"></i>
            <input type="text" id="search-todo" placeholder="タスクを検索" />
          </div>
          <div class="todo-list__sort" id="todo-sort-icon">
            <div class="todo-list__sort-box">
              <div class="todo-list__sort-icon" id="sort-add-todo">
                <span class="material-symbols-outlined">swap_vert</span>
                <p>並び替え</p>
              </div>
              <ul class="todo-list__sort-pd">
                <li class="sort-deadline">期限が早い順</li>
                <li class="sort-deadline">期限が遅い順</li>
              </ul>
            </div>
          </div>
        </div>
        <div id="todo-contents">
          <ul class="todo-list__list" id="todo-list">
            <!-- todoが追加される -->
          </ul>
          <ul class="todo-list__list" id="todo-doing">
            <!-- todoが追加される -->
          </ul>
          <ul class="todo-list__list" id="todo-done">
            <!-- todoが追加される -->
          </ul>
        </div>
        <div class="edit-todo__modal" id="edit-todo-modal">
          <div class="add-todo__modal-container">
            <i
              class="add-todo__modal-icon fa-solid fa-xmark"
              id="edit-todo-close"
            ></i>
            <h2>タスクを編集</h2>
            <p class="error-message" id="edit-error-message"></p>
            <div>
              <label for="edit-todo-title">タイトル</label>
              <input type="text" id="edit-todo-title" />
            </div>
            <div>
              <label for="edit-todo-content">内容</label>
              <textarea id="edit-todo-content"></textarea>
            </div>
            <div>
              <label for="edit-todo-person">担当者</label>
              <input type="text" id="edit-todo-person" />
            </div>
            <div>
              <label for="edit-todo-date">期限</label>
              <input
                class="edit-todo__modal-date"
                type="date"
                id="edit-todo-date"
              />
            </div>
            <div>
              <label for="edit-todo-status">ステータス</label>
              <select class="edit-todo__status" id="edit-todo-status">
                <option value="0">TODO</option>
                <option value="1">進行中</option>
                <option value="2">完了</option>
              </select>
            </div>
            <div class="add-todo__modal-submit">
              <button class="add-todo__modal-button" id="submit-edit">
                編集する
              </button>
            </div>
          </div>
        </div>
      </section>
    </main>
  </body>
</html>

CSS↓

css/style.css
@charset "UTF-8";
/* --------------------------------------------
  reset
---------------------------------------------*/
body {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  background: #f6f6f6;
  color: #1a1a1a;
}

h1,
h2,
h3,
h4,
h5,
p,
div,
ul,
ol {
  margin: 0;
  padding: 0;
}

ul,
li {
  list-style: none;
  padding: 0;
  margin: 0;
}

/* --------------------------------------------
  common
---------------------------------------------*/
.inner {
  max-width: 650px;
  margin: 0 auto;
  padding-left: 20px;
  padding-right: 20px;
}

.todo-color {
  background: #fff;
}

._todo-active {
  color: #21b2d3;
  border-bottom: 3px solid #21b2d3;
}

main {
  padding-bottom: 20px;
}

/* --------------------------------------------
  header
---------------------------------------------*/
header {
  width: 100%;
  padding: 10px;
  border-bottom: 1px solid #e4e0e0;
  box-sizing: border-box;
}
header h1 {
  text-align: center;
  font-size: 24px;
}
@media (max-width: 767px) {
  header h1 {
    font-size: 20px;
  }
}

/* ---------------------å-----------------------
  add-todo
---------------------------------------------*/
.add-todo {
  padding-top: 20px;
}
.add-todo__button {
  display: flex;
  height: auto;
  align-items: center;
  justify-content: center;
  padding: 10px;
  border-radius: 10px;
  color: #21b2d3;
  font-size: 20px;
  cursor: pointer;
}
.add-todo__button:hover {
  opacity: 0.8;
}
.add-todo h2 {
  font-size: 20px;
  margin-left: 10px;
}
@media (max-width: 767px) {
  .add-todo h2 {
    font-size: 16px;
  }
}
.add-todo__modal {
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  justify-content: center;
  align-items: center;
  z-index: 100;
}
.add-todo__modal-container {
  box-sizing: border-box;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background: #f6f6f6;
  padding: 50px;
  border-radius: 10px;
  max-width: 630px;
  width: 90%;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
}
.add-todo__modal-container div {
  margin-top: 10px;
}
.add-todo__modal-container h2 {
  margin-left: 0;
}
@media (max-width: 767px) {
  .add-todo__modal-container {
    padding: 20px;
  }
}
.add-todo__modal-container label,
.add-todo__modal-container input {
  display: block;
}
.add-todo__modal-container label {
  font-size: 14px;
}
.add-todo__modal-container input[type=text],
.add-todo__modal-container textarea {
  width: 100%;
  padding: 10px;
  border: 1px solid #e4e0e0;
  background: #fff;
  box-sizing: border-box;
  border-radius: 6px;
}
.add-todo__modal-container textarea {
  min-height: 150px;
  resize: horizontal;
}
.add-todo__modal-container input[type=date] {
  border: 1px solid #e4e0e0;
  padding: 5px 10px;
  border-radius: 6px;
}
.add-todo__modal-icon {
  position: absolute;
  top: 20px;
  right: 20px;
  font-size: 24px;
  color: gray;
  cursor: pointer;
}
.add-todo__modal-submit {
  text-align: center;
}
.add-todo__modal-button {
  cursor: pointer;
  width: 400px;
  max-width: 100%;
  padding: 10px;
  border: none;
  color: #fff;
  font-weight: bold;
  background: #21b2d3;
  border-radius: 6px;
  margin-top: 20px;
}
.add-todo__modal-button:hover {
  opacity: 0.8;
}

.error-message {
  font-size: 14px;
  color: rgb(213, 94, 94);
  margin-top: 10px;
}
@media (max-width: 767px) {
  .error-message {
    font-size: 12px;
  }
}

/* --------------------------------------------
  select-todo-status
---------------------------------------------*/
.select-todo-status__list {
  display: flex;
  border-bottom: 2px solid #e4e0e0;
}
.select-todo-status__list p {
  font-weight: bold;
  font-size: 18px;
  margin-top: 20px;
  cursor: pointer;
}
@media (max-width: 767px) {
  .select-todo-status__list p {
    font-size: 16px;
  }
}
.select-todo-status__list p:not(:first-child) {
  margin-left: 35px;
}
.select-todo-status__list p:hover {
  opacity: 0.8;
}

/* --------------------------------------------
  todo-list
---------------------------------------------*/
.todo-list__parts-row {
  display: flex;
  height: auto;
  align-items: center;
  margin-top: 10px;
  justify-content: space-between;
}
@media (max-width: 767px) {
  .todo-list__parts-row {
    display: block;
  }
}
@media (max-width: 767px) {
  .todo-list__sort {
    margin-top: 10px;
  }
}
.todo-list__sort-box {
  position: relative;
  display: inline-block;
}
@media (max-width: 767px) {
  .todo-list__sort-box {
    display: block;
  }
}
.todo-list__sort-icon {
  display: flex;
  height: auto;
  align-items: center;
  position: relative;
  cursor: pointer;
}
.todo-list__sort-icon:hover {
  opacity: 0.8;
}
.todo-list__sort-icon span {
  color: gray;
  font-size: 15px;
}
.todo-list__sort-icon p {
  color: gray;
  font-size: 12px;
  margin-left: 2px;
}
.todo-list__sort-box:hover .todo-list__sort-pd {
  display: block;
}
.todo-list__sort-pd {
  display: none;
  align-items: center;
  flex-direction: column;
  position: absolute;
  background: #fff;
  right: 0;
  padding: 5px 0;
  border-radius: 5px 0 5px 5px;
  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1);
}
@media (max-width: 767px) {
  .todo-list__sort-pd {
    left: 0;
    border-radius: 0 5px 5px 5px;
    width: 92px;
  }
}
.todo-list__sort-pd li {
  font-size: 12px;
  color: gray;
  white-space: nowrap;
  cursor: pointer;
  padding-right: 10px;
  padding-left: 10px;
  position: relative;
}
.todo-list__sort-pd li:not(:first-child) {
  margin-top: 3px;
}
.todo-list__sort-pd li:hover {
  background: #f2fbff;
  border-radius: 0;
}
.todo-list__search {
  position: relative;
}
.todo-list__search input {
  width: 300px;
  max-width: 100%;
  padding: 10px 50px;
  border-radius: 50px;
  border: none;
  box-sizing: border-box;
}
@media (max-width: 767px) {
  .todo-list__search input {
    width: 100%;
  }
}
.todo-list__search i {
  position: absolute;
  top: 50%;
  transform: translateY(-50%);
  left: 20px;
  color: gray;
}
.todo-list__title {
  display: flex;
  height: auto;
  align-items: center;
  justify-content: space-between;
}
.todo-list__icon {
  position: relative;
}
.todo-list__list:not(:first-child) {
  display: none;
}
.todo-list__list li {
  margin-top: 20px;
  border-radius: 10px;
  padding: 15px;
}
.todo-list__list h2 {
  font-size: 19px;
}
@media (max-width: 767px) {
  .todo-list__list h2 {
    font-size: 17px;
  }
}
.todo-list__list-text {
  color: gray;
  font-size: 16px;
  margin-top: 10px;
}
@media (max-width: 767px) {
  .todo-list__list-text {
    font-size: 14px;
  }
}
.todo-list__list-detail {
  margin-top: 20px;
}
.todo-list__list-detail p {
  font-size: 14px;
  margin-top: 3px;
}
@media (max-width: 767px) {
  .todo-list__list-detail p {
    font-size: 12px;
  }
}
.todo-list__list-person {
  color: rgba(20, 72, 117, 0.7);
  font-weight: bold;
}
.todo-list__list-deadline {
  color: rgb(213, 94, 94);
  font-weight: bold;
}
.todo-list__icon {
  cursor: pointer;
}
.todo-list__icon i {
  font-size: 30px;
  color: gray;
}
.todo-list__icon:hover .todo-list__dropdown {
  display: block;
}
.todo-list__dropdown {
  position: absolute;
  top: 20px;
  right: 0;
  width: 200px;
  background: #f6f6f6;
  color: #21b2d3;
  display: none;
  border-radius: 3px;
  padding: 7px 0;
}
.todo-list__dropdown p {
  padding: 0 10px;
}
.todo-list__dropdown p:hover {
  background: #f2fbff;
  cursor: pointer;
}

.edit-todo__modal {
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  justify-content: center;
  align-items: center;
  z-index: 100;
}

.edit-todo__status {
  padding: 5px 10px;
  border: 1px solid #e4e0e0;
  border-radius: 6px;
}

2. JavaScriptで実装

js↓

js/todo.js
let todoList = [];
//新規作成の値
let newTodoTitleElem = "";
let newTodoContentElem = "";
let newTodoPersonElem = "";
let newTodoDateElem = "";
//検索の値
let searchFilterWord = "";
//ステータスの値
let optionIndex = 0;
//編集の時に使う値
let currentEditId = null;
//編集のTODOの値
let editTodoTitle = "";
let editTodoContent = "";
let editTodoPerson = "";
let editTodoDate = "";
let todoStatusTest = "";

//並び替えクリックでプルダウン表示

//タイトル、内容、担当者、期限の内容を取得する
const todoListElem = () => {
  newTodoTitleElem = document.getElementById("new-todo-title");
  newTodoContentElem = document.getElementById("new-todo-content");
  newTodoPersonElem = document.getElementById("new-todo-person");
  newTodoDateElem = document.getElementById("new-todo-date");
};

//編集のタイトル、内容、担当者、期限の内容を取得する
const todoEditListElem = () => {
  editTodoTitle = document.getElementById("edit-todo-title");
  editTodoContent = document.getElementById("edit-todo-content");
  editTodoPerson = document.getElementById("edit-todo-person");
  editTodoDate = document.getElementById("edit-todo-date");
};

//エラーメッセージの非表示
const clearErrorMessage = () => {
  //TODOの追加の時のエラー
  const newTodoErrorMessage = document.getElementById("error-message");
  newTodoErrorMessage.textContent = "";
  //TODO編集時のエラー
  const editTodoErrorMessage = document.getElementById("edit-error-message");
  editTodoErrorMessage.textContent = "";
};

//追加するTODOのモーダルを表示
const handleNewTodoModal = () => {
  //タスクの追加ボタンモーダル出現
  const addTodoButton = document.getElementById("add-todo-button");
  const newTodoModal = document.getElementById("new-todo-modal");
  addTodoButton.addEventListener("click", () => {
    newTodoModal.style.display = "block";
  });
  //タスク追加のモーダルを閉じる
  const modalClose = document.getElementById("modal-close");
  modalClose.addEventListener("click", () => {
    newTodoModal.style.display = "none";
    todoListElem();
    removeTodoInput();
    clearErrorMessage();
  });
};

//TODOの値を取得する関数
const getTodoValue = () => {
  //TODOの値の取得
  todoListElem();

  //エラーメッセージの表示
  if (
    newTodoTitleElem.value === "" ||
    newTodoPersonElem.value === "" ||
    newTodoDateElem.value === ""
  ) {
    const errorMessage = document.getElementById("error-message");
    errorMessage.textContent = "必要事項を入力してください";
    return;
  }

  //エラーメッセージを非表示する
  clearErrorMessage();
  //オブジェクト生成
  const todoObj = {
    id: Date.now(),
    todoTitle: newTodoTitleElem.value,
    todoContent: newTodoContentElem.value,
    todoPerson: newTodoPersonElem.value,
    todoDeadline: newTodoDateElem.value,
    todoStatus: 0,
  };

  //配列に格納
  todoList.push(todoObj);
};

//todoの最初の要素を削除←前のTODOが消されていないため
const removePrevTodo = () => {
  const todoElements = [
    document.getElementById("todo-list"),
    document.getElementById("todo-doing"),
    document.getElementById("todo-done"),
  ];
  todoElements.forEach((todoElement) => {
    while (todoElement.firstChild) {
      todoElement.firstChild.remove();
    }
  });
};

//TODOの追加
const appendTodoElem = () => {
  removePrevTodo();
  todoList
    .filter(
      (todo) =>
        todo.todoTitle.includes(searchFilterWord) ||
        todo.todoContent.includes(searchFilterWord) ||
        todo.todoPerson.includes(searchFilterWord)
    )
    .forEach((todo) => {
      const todoElem = document.getElementById("todo-list");
      const todoDoingElem = document.getElementById("todo-doing");
      const todoDoneElem = document.getElementById("todo-done");
      //TODOの要素の作成
      const todoLiElem = document.createElement("li");
      todoLiElem.classList.add("todo-color");
      //=== タイトルの要素を作成 ===//
      const todoTitleDivElem = document.createElement("div");
      todoTitleDivElem.classList.add("todo-list__title");
      //タイトル
      const todoTitleElem = document.createElement("h2");
      todoTitleElem.textContent = todo.todoTitle;
      //アイコン
      const todoIconDivElem = document.createElement("div");
      todoIconDivElem.classList.add("todo-list__icon");
      const todoIconIElem = document.createElement("i");
      todoIconIElem.classList.add("fa-solid", "fa-ellipsis");
      const todoDropdownDivElem = document.createElement("div");
      todoDropdownDivElem.classList.add("todo-list__dropdown");
      const todoRemoveElem = document.createElement("p");
      todoRemoveElem.textContent = "削除する";
      const todoEditElem = document.createElement("p");
      todoEditElem.textContent = "編集する";
      todoDropdownDivElem.appendChild(todoRemoveElem);
      todoDropdownDivElem.appendChild(todoEditElem);
      todoIconDivElem.appendChild(todoIconIElem);
      todoIconDivElem.appendChild(todoDropdownDivElem);
      //タイトルの親要素にアイコンとドロップダウンを入れる
      todoTitleDivElem.appendChild(todoTitleElem);
      todoTitleDivElem.appendChild(todoIconDivElem);

      //===  TODOを削除する  ===//
      todoRemoveElem.addEventListener("click", () => {
        todoList = todoList.filter((_todo) => _todo.id !== todo.id);
        appendTodoElem();
      });

      //=== 内容の要素を作成 ===//
      const todoContentElem = document.createElement("p");
      todoContentElem.classList.add("todo-list__list-text");
      todoContentElem.textContent = todo.todoContent;

      //=== 期限と担当者の要素を作成 ===//
      const todoDetailElem = document.createElement("div");
      todoDetailElem.classList.add("todo-list__list-detail");
      const todoPersonElem = document.createElement("p");
      todoPersonElem.classList.add("todo-list__list-person");
      todoPersonElem.textContent = `担当者:${todo.todoPerson}`;
      const todoDeadlineElem = document.createElement("p");
      todoDeadlineElem.classList.add("todo-list__list-deadline");
      todoDeadlineElem.textContent = `期限:${todo.todoDeadline}`;
      todoDetailElem.appendChild(todoPersonElem);
      todoDetailElem.append(todoDeadlineElem);

      //==== リストにタイトル、内容、担当者、期限を挿入 ====//
      todoLiElem.appendChild(todoTitleDivElem);
      todoLiElem.appendChild(todoContentElem);
      todoLiElem.appendChild(todoDetailElem);

      //===== TODOの追加 =====//
      //編集時のstatusによって表示を分ける
      if (todo.todoStatus === 0) {
        todoElem.appendChild(todoLiElem);
      } else if (todo.todoStatus === 1) {
        todoDoingElem.appendChild(todoLiElem);
      } else if (todo.todoStatus === 2) {
        todoDoneElem.appendChild(todoLiElem);
      }

      //=================== ここから編集 ======================//
      //ステータスの値を取得
      const selectStatusElem = document.getElementById("edit-todo-status");

      //=== TODOを編集する 編集画面の表示 ===//
      const editTodoModalElem = document.getElementById("edit-todo-modal");
      todoEditElem.addEventListener("click", () => {
        //エラーメッセージをリセット
        clearErrorMessage();
        //編集モーダルのタイトル、内容、担当者、期限の要素を取得
        todoEditListElem();
        //クリックしたものと一致するTODOを見つける
        currentEditId = todoList.find((_todo) => _todo.id === todo.id);
        //編集モーダルにクリックしたTODOの値を入れる
        editTodoTitle.value = currentEditId.todoTitle;
        editTodoContent.value = currentEditId.todoContent;
        editTodoPerson.value = currentEditId.todoPerson;
        editTodoDate.value = currentEditId.todoDeadline;
        selectStatusElem.value = currentEditId.todoStatus;

        //編集モーダル開く
        editTodoModalElem.style.display = "block";
      });
      //=== 編集画面を非表示 ===//
      const editTodoCloseElem = document.getElementById("edit-todo-close");
      editTodoCloseElem.addEventListener("click", () => {
        editTodoModalElem.style.display = "none";
      });

      //=== ステータスの内容を変更 ===//
      selectStatusElem.addEventListener("change", (e) => {
        optionIndex = e.target.selectedIndex;
      });

      const submitEditButton = document.getElementById("submit-edit");
      //===== 編集したTODOを保存 =====//
      submitEditButton.addEventListener("click", () => {
        //編集モーダルのタイトル、内容、担当者、期限の要素を取得
        todoEditListElem();
        if (
          editTodoTitle.value === "" ||
          editTodoContent.value === "" ||
          editTodoPerson.value === "" ||
          editTodoDate.value === ""
        ) {
          const errorMessage = document.getElementById("edit-error-message");
          errorMessage.textContent = "必要事項を入力してください";
          return;
        }
        todoList = todoList.map((todo) => {
          if (todo.id === currentEditId.id) {
            return {
              ...todo,
              todoTitle: editTodoTitle.value,
              todoContent: editTodoContent.value,
              todoDeadline: editTodoDate.value,
              todoPerson: editTodoPerson.value,
              todoStatus: optionIndex,
            };
          }
          return todo;
        });

        appendTodoElem();
        editTodoModalElem.style.display = "none";
      });
      //======= 編集ここまで =========//
    });
};

//===== TODOのinputを削除 =====//
const removeTodoInput = () => {
  newTodoTitleElem.value = "";
  newTodoContentElem.value = "";
  newTodoPersonElem.value = "";
  newTodoDateElem.value = "";
};

//========  ここで処理が実行されている  ========//
document.addEventListener("DOMContentLoaded", () => {
  //モーダルの表示
  handleNewTodoModal();

  //==== タブの切り替え ====//
  //タブの色変更
  const tabMenuElem = document.getElementById("tab-status-menu");
  const tabPElem = tabMenuElem.children;
  Array.from(tabPElem).forEach((tab, tabIndex) => {
    tab.addEventListener("click", () => {
      Array.from(tabPElem).forEach((t) => t.classList.remove("_todo-active"));
      tab.classList.add("_todo-active");

      //タブの切り替えでコンテンツ表示
      const todoListsElem = document.querySelectorAll(".todo-list__list");
      todoListsElem.forEach((todoList) => {
        todoList.style.display = "none";
      });
      todoListsElem[tabIndex].style.display = "block";
    });
  });

  //TODOの追加
  const newTodoButtonElem = document.getElementById("submit-new-todo");
  newTodoButtonElem.addEventListener("click", () => {
    //TODOの値を取得
    getTodoValue();
    //TODO一覧に追加
    if (todoList.length > 0) {
      appendTodoElem();
      const newTodoModal = document.getElementById("new-todo-modal");
      newTodoModal.style.display = "none";
    }
    //inputを空にする
    removeTodoInput();
  });

  //TODOの検索
  const searchTodoElem = document.getElementById("search-todo");
  searchTodoElem.addEventListener("input", () => {
    searchFilterWord = searchTodoElem.value;
    appendTodoElem();
  });

  //======= 並び替え =======//
  //カラーのリセット
  const sortResetColor = () => {
    sortDeadlineLiElem.forEach((clearSortList) => {
      clearSortList.style.background = "";
      clearSortList.style.color = "";
    });
  };
  const sortDeadlineLiElem = document.querySelectorAll(".sort-deadline");
  sortDeadlineLiElem.forEach((sortList, sortIndex) => {
    //active時の色変更
    const sortActiveColor = () => {
      sortList.style.background = "#75c5ea";
      sortList.style.color = "#fff";
    };

    sortList.addEventListener("click", () => {
      sortResetColor();
      todoList.sort((a, b) => {
        const dateA = new Date(a.todoDeadline);
        const dateB = new Date(b.todoDeadline);
        if (sortIndex === 0) {
          return dateA - dateB;
        } else if (sortIndex === 1) {
          return dateB - dateA;
        }
        return;
      });
      sortActiveColor();
      appendTodoElem();
    });

    //並び替えボタンを押した時元に戻す
    const sortAddTodo = document.getElementById("sort-add-todo");
    sortAddTodo.addEventListener("click", () => {
      sortResetColor();
      todoList.sort((a, b) => a.id - b.id);
      appendTodoElem();
    });
  });
});

すんません、コード汚いかも。。。

苦労した点

・編集機能のステータス変更

js/todo.js
if (todo.todoStatus === 0) {
  todoElem.appendChild(todoLiElem);
} else if (todo.todoStatus === 1) {
  todoDoingElem.appendChild(todoLiElem);
} else if (todo.todoStatus === 2) {
  todoDoneElem.appendChild(todoLiElem);
}

ここをずっと

js/todo.js
if (optionIndex === 0) {
  todoElem.appendChild(todoLiElem);
} else if (optionIndex === 1) {
  todoDoingElem.appendChild(todoLiElem);
} else if (optionIndex === 2) {
  todoDoneElem.appendChild(todoLiElem);
}

としていて、あるTODOのステータスを進行中(optionのインデックスが2)に変更したのに変更したものだけでなく全てが進行中の欄に移動して、「は?」ってなりました。
※因みにtodoStatusの値を調べたら、変更したものにだけ適応されていました。
原因は、optionIndexは現在選択しているTODOのステータスでもなんでもなく、ただのoptionのインデックス番号に過ぎないということでした。
編集をするときは現在選択しているTODOを特定しないといけないです。

・ステータス変更の編集画面の値の取得
編集画面を表示させた時にステータス変更のoptionの値が保持されていませんでした。
原因は

js/todo.js
selectStatusElem.value = currentEditId.todoStatus;

ここの部分かな?と思ったのですが、どこが間違っているのか見当がつかず。。。調べたところ、コードはこのままで合っているんですが、htmlのoptionにvalueをセットをセットしていなかったことが原因でした。

js/todo.html
<select class="edit-todo__status" id="edit-todo-status">
  <option value="0">TODO</option>
  <option value="1">進行中</option>
  <option value="2">完了</option>
</select>

これで編集画面で値をセットしたら編集したTODOのみステータスを変更できるようになりました。
ですが、ここで頭をよぎったことが、

「あれ?でも、optionのvalueって文字列だよね?なんでselectStatusElem.value(←こいつは文字列型) = currentEditId.todoStatus(←こいつは数値型)は編集画面でちゃんと値保存できているんだ?」

どうやら、JavaScriptは代入時に暗黙的型変換というやつを行うらしいのです。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Data_structures

なので

js/todo.js
selectStatusElem.value = currentEditId.todoStatus;

を記述することでcurrentEditId.todoStatusは文字列型に変換されるので
編集時に選んだステータスをまた編集したい時に編集画面を開くと前に選んだステータスがちゃんと反映されているのです!

・sortメソッドの使い方(並び替え)
これまじで最初よく分かんなかったです。
sortメソッドは文字列や数値を降順、昇順と並び替えが簡単にできる便利なメソットです。
例えば、
const array = [0, 3, 2, 1, 6]
みたいな配列があったとします。
これを昇順で並べ替えると、
array.sort((a, b) => {
return a - b
})
となります。
第一引数→第二引数っていうのを基本で考えます。
a = 0, b = 3 で比較した場合:
これは0 - 3になるのでマイナスを返します。
aはbの後ろに来ます。
a = 3, b = 2で比較した場合:
これは3 - 2になるのでプラスを返します。
aはbの前に来ます。
で全て比較がされると、
[0, 1, 2, 3, 6]で並び替えられます。

その上でこのコードを見ると

js/todo.js
todoList.sort((a, b) => {
  const dateA = new Date(a.todoDeadline);
  const dateB = new Date(b.todoDeadline);
  if (sortIndex === 0) {
    return dateA - dateB;
  } else if (sortIndex === 1) {
    return dateB - dateA;
  }
  return;
});

sortIndex === 0の時は、昇順
sortIndex === 1の時は、降順に並び替えられるということです!

まとめ

JavaScriptは理解した気になっていただけで、まだ全然理解が足りない部分があったということをこのTODOアプリ道場(自分で作った課題だけどw)で知れて良かったです。
行き詰まったら、基礎に戻るってめちゃくちゃ大事ですね!!
割と自分で考えてコードが書けるようになってきた気がします!
この調子で頑張っていこうと思います⭐️

0
2
2

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
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?