【JavaScript】TODOアプリを作成する

Reactの習得にあたって、こちらのUdemyの教材で学習をした内容についてまとめました。

【最新ver対応済】モダンJavaScriptの基礎から始める挫折しないためのReact入門
【フルリニューアルしました!】Reactの習得に苦戦する理由は「JavaScript」への理解不足です。このコースではスムーズにReact開発のスタート地点に立てるように、モダンJavaScriptの動作の仕組みや概念、機能から解説します。

Reactを理解するためにはまずはJavaScriptの習得が必要ということで、JavaScriptのみでTODOリストを作成しています。

Udemyと全く同じではなく、CSSのデザイン部分は少し省いています。

目標物

それではいざコーディングへ!

「追加」ボタンクリックでアラートを表示させる

やりたいこと

追加ボタンを押した時に、ボタンが機能しているか確認するために、まずは試しにalert表示させる。

やること

「追加」ボタンにidを付与

<button id="add-button">追加</button>

付与したidに対してクリックイベントを追加する。

const onClickAdd = () => {
  alert();
};
document
  .getElementById("add-button")
  .addEventListener("click", () => onClickAdd());

これらについて詳しく

const onClickAdd = () => {
  alert();
};

これにより、追加ボタンを押したときに関数を実行する。

.addEventListener("click", () => onClickAdd());

これにより、clickイベントで関数onClickAddが実行される。

「追加」ボタンを押した時にフォームに入力された値を変数に渡す

まずはHTMLのinputタグにidを付与する。

<input id="add-text" placeholder="ToDoを入力" />

次にJavaScript側を編集

変数inputTextを定義し、ここにinputで取得した値を入れる。

取得したいのはvalueなので、.valueとする。

const inputText = document.getElementById("add-text").value

これにより、add-textというidが持っているvalueが、inputTextに格納される。

値が渡されたので、「追加」ボタンクリック後にフォームの入力を空欄にする処理をする。

document.getElementById("add-text").value = "";

ここまでのまとめ

const onClickAdd = () => {
  const inputText = document.getElementById("add-text").value;
  document.getElementById("add-text").value = "";
  alert(inputText);
};
document
  .getElementById("add-button")
  .addEventListener("click", () => onClickAdd());

inputTextの値を”未完了のTODO”に登録していく

DOMの生成は、createElementという関数を使っていく。

const div = document.createElement("div");
console.log(div);

> <div></div>

この関数は、JavaScript上でHTMLのDOMを生成することができる。

divというタグを生成し、divという変数で定義する。

これにより、「追加」ボタンクリックでdivタグを取得できる。

HTMLで、divにはlist-rowというclass名を付与しているため、JavaScriptでも付与する。

  const div = document.createElement("div");
  div.className = "list-row";
  console.log(div);

> <div class="list-row"></div>

JavaScriptの方で、document.createElementを使って、HTMLのDOMを生成して、 HTML に差し込んでいく。

  const li = document.createElement("li");
  li.innerText = inputText;

liタグの中身に要素を格納したい時は、innerTextを使う。

liタグを生成して、その中にinputTextの値を格納することによって、liタグ内に入力された値が入った状態で出力される。

階層構造を作る

以下のようなタグの階層構造を作る。

      <ul id="incomplete-list">
        <div class="list-row">
          <li>ToDoです</li>
          <button>完了</button>
          <button>削除</button>
        </div>
      </ul>

階層を作るにはappendChildを使う。

ここまでをまとめると

const onClickAdd = () => {
  const inputText = document.getElementById("add-text").value;
  document.getElementById("add-text").value = "";

  const div = document.createElement("div");
  div.className = "list-row";

  const li = document.createElement("li");
  li.innerText = inputText;

  div.appendChild(li);

  console.log(div);
};

consoleの出力結果がこちら

  <div class="list-row">
    <li>テスト</li>
  </div>

index.htmlと同じ階層になっていることがわかる。

最後に、divタグの一個上の階層のulタグにincomplete-listというid名を付け、divを取得し、要素を切り替える。

document.getElementById("incomplete-list").appendChild(div);

ここまでの画面

TODOリストにボタンを追加する

  // 完了buttonを生成
  const completeButton = document.createElement("button");
  completeButton.innerText = "完了";

  // 削除buttonを生成
  const deleteButton = document.createElement("button");
  deleteButton.innerText = "削除";

  // HTMLの階層を生成
  div.appendChild(li);
  div.appendChild(completeButton);
  div.appendChild(deleteButton);

completeButtonという変数を定義して、buttonタグを生成し、その中に「完了」「削除」を入れる。

clickイベントを追加して、完了と、削除ボタンをクリックしたときに、alertが表示されるようにして、定義した関数が機能しているかの確認をする。

  // 完了buttonを生成
  const completeButton = document.createElement("button");
  completeButton.innerText = "完了";
  completeButton.addEventListener("click", () => {
    alert("完了");
  });

  // 削除buttonを生成
  const deleteButton = document.createElement("button");
  deleteButton.innerText = "削除";
  deleteButton.addEventListener("click", () => {
    alert("削除");
  });

「削除」ボタンクリックでリストが削除されるようにする

parentNodeを使い、親の要素を取得する。

子要素から指定のものを消す場合はremoveChildを使う。

  const deleteButton = document.createElement("button");
  deleteButton.innerText = "削除";
  deleteButton.addEventListener("click", () => {
    const deleteTarget = deleteButton.parentNode;
    document.getElementById("incomplete-list").removeChild(deleteTarget);
  });

document.getElementByIdについて解説

getElementByIdとは、指定したid値を持つ要素をElementオブジェクトとして返すメソッド

Elementオブジェクト とは何か?

HTMLの要素、つまりタグの認識でよい。

「完了」ボタンも「削除」ボタンと同様にクリックで消す

完了と削除で同じ役割のため、関数化する。

以下の部分を関数化する。

    const deleteTarget = deleteButton.parentNode;
    document.getElementById("incomplete-list").removeChild(deleteTarget);

関数化により以下のようになる。

  const deleteFromIncompleteList = (target) => {
    document.getElementById("incomplete-list").removeChild(target);

呼び出し側
    deleteFromIncompleteList(deleteButton.parentNode);

「完了」ボタンを押したときに”未完了のTODO”のから”完了したTODO”に移行する

完了ボタンの親要素を取得して変数addTargetと定義する

const addTarget = completeButton.parentNode;

取得した親要素の中の一番最初の子要素のtextを取得する。

const text = addTarget.firstElementChils.innerText;

上記で生成したaddTargetのtextを削除する。

addTarget.textContent = null;

完了したTODOに入れるためにliタグを作成し、その中に上記で定義したtextを入れる。

    const li = document.createElement("li");
    li.innerText = text;

“完了したTODO”から”未完了のTODO”に戻すための「戻す」ボタンを作成

    const backButton = document.createElement("button");
    backButton.innerText = "戻す";

これら作成した要素を完了したTODOの子要素に設定

これで、階層構造はできたので、あとは完了したリストに表示する。

document.getElementById("complete-list").appendChild(addTarget);

「戻す」ボタンの機能を作成する

戻すボタンの親タグの要素を完了リストから削除

      const deleteTarget = backButton.parentNode;
      document.getElementById("complete-list").removeChild(deleteTarget);

テキストを取得する。

    document.getElementById("complete-list").appendChild(addTarget);

取得したテキストを基に、未完了のTODOに要素を追加する。

「追加」をしたときとほとんど同じなので関数化して、両方で使えるようにする。

関数定義

const createIncompleteList = (text) => {
  
  この中に共通部分を入れる

}

関数呼び出し側

createIncompleteList(引数);

この引数の中に「追加」と「戻す」ボタンクリック時にわたす引数を定義する。

コードの全貌

<!DOCTYPE html>
<html>
  <head>
    <title>ToDo(JS)</title>
    <meta charset="UTF-8" />
  </head>

  <body>
    <div class="input-area">
      <input id="add-text" placeholder="ToDoを入力" />
      <button id="add-button">追加</button>
    </div>

    <div class="incomplete-area">
      <p>未完了のToDo</p>
      <ul id="incomplete-list"></ul>
    </div>

    <div class="complete-area">
      <p>完了したToDo</p>
      <ul id="complete-list"></ul>
    </div>

    <script src="src/index.js"></script>
  </body>
</html>

body {
  font-family: sans-serif;
}

.input-area {
  background-color: #c1ffff;
}

.incomplete-area {
  background-color: #c6ffe2;
}

.complete-area {
  background-color: #ffffe0;
}

li {
  margin-right: 5px;
}

.list-row {
  display: flex;
  align-items: center;
}

import "./styles.css";

const onClickAdd = () => {
  // フォームの入力値を取得とクリア
  const inputText = document.getElementById("add-text").value;
  document.getElementById("add-text").value = "";

  createIncompleteList(inputText);
};

// 「未完了のTODO」からリストを削除
const deleteFromIncompleteList = (target) => {
  document.getElementById("incomplete-list").removeChild(target);
};

const createIncompleteList = (text) => {
  // divの生成とclass名付与
  const div = document.createElement("div");
  div.className = "list-row";

  // liの生成とliのtextを取得
  const li = document.createElement("li");
  li.innerText = text;

  // 完了buttonを生成
  const completeButton = document.createElement("button");
  completeButton.innerText = "完了";
  // 「完了」ボタンクリックで完了ボタンの親要素ごと未完了のTODOから削除
  completeButton.addEventListener("click", () => {
    deleteFromIncompleteList(completeButton.parentNode);
    // 完了リストに追加する要素
    const addTarget = completeButton.parentNode;
    const text = addTarget.firstElementChild.innerText;

    addTarget.textContent = null;

    const li = document.createElement("li");
    li.innerText = text;

    const backButton = document.createElement("button");
    backButton.innerText = "戻す";
    backButton.addEventListener("click", () => {
      const deleteTarget = backButton.parentNode;
      document.getElementById("complete-list").removeChild(deleteTarget);

      const text = backButton.parentNode.firstElementChild.innerText;
      createIncompleteList(text);
    });

    addTarget.appendChild(li);
    addTarget.appendChild(backButton);

    document.getElementById("complete-list").appendChild(addTarget);
  });

  // 削除buttonを生成
  const deleteButton = document.createElement("button");
  deleteButton.innerText = "削除";
  deleteButton.addEventListener("click", () => {
    deleteFromIncompleteList(deleteButton.parentNode);
  });

  // HTMLの階層を生成
  div.appendChild(li);
  div.appendChild(completeButton);
  div.appendChild(deleteButton);

  document.getElementById("incomplete-list").appendChild(div);
};

document
  .getElementById("add-button")
  .addEventListener("click", () => onClickAdd());

まとめ

主にやっていることは2つだけ

  • idを付与して操作する
  • 要素の子や親を取得してそこを書き替える

そのためにいろんなメソッドを使用しているが、やっていることはこの2つだけ。

だけど、こんな簡単なTODO機能でも、結構コードが複雑になる。

これをスッキリさせようというのがReact!

コメント