MENU

【Javascript】Todoリストを作ってみよう!(タスク登録まで)

2021 6/02
【Javascript】Todoリストを作ってみよう!(タスク登録まで)

おつかれさまです。りっくん(@GT0b4)です。

今回は実際にJavascriptを使って簡単なTodoリストを作る過程を記事にまとめたいと思います!

【こんな人にオススメ】

・Progateとかでちょっと勉強してみたけど、そのあと何をしたらいいかわからない。、

・似たような記事で色々調べたけど、どれも難しくてまだまだチャレンジできない。、

↑といった方でも、取り組みやすいような内容にしていきたいと思います。

Todoリストはアプリケーションの中でもCRUD処理(生成・読み取り・更新・削除)に関してほどよく学べるものなので、この機会にぜひ一緒に作ってみましょう。 

また、今回少し長編になるのでいくつかの記事に分けてまとめたいと思います。
今回は実際に「Todoを登録する」というところまで実装していきたいと思います!

なお本記事ではHTML・CSSは深く掘り下げないつもりなので、ある程度割愛させていいただくことをご理解くださいませ🙏🙏

それでは行ってみましょう〜〜〜💪💪

目次

作成にあたっての流れ

今回は実際にJavascriptを使ってタスクを実際に

  1. 登録
  2. 削除
  3. 編集

の処理機能まで実装していけたらと思います。

※本記事ではタスク(todo)の登録まで行います。

作る準備

  • 用意するもの
    • index.html
    • style.css
      • bootstrap(CDN等で用意してください)
    • index.js

ディレクトリの構成は以下のようになります。

作業フォルダ/
  └src/
    └index.html
    └css/
      └style.css
    └js/
      └index.js

雛形を用意する(HTML/CSS)

<!DOCTYPE html>
<html lang="ja">
  <head>
    <!-- Required meta tags -->
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />

    <!-- BootStrap の読み込み -->
    <link
      rel="stylesheet"
      href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css"
      integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB"
      crossorigin="anonymous"
    />

    <link rel="stylesheet" href="css/style.css" />
    <title>TODOリスト</title>
  </head>

  <body>
    <div class="container">
      <h2>TODOリスト</h2>
      <div class="add mb-3">
          <div class="form-group">
            <label for="inputTodo">タスク</label>
            <input type="text" class="form-control" id="inputTodo" placeholder="買い物をする" />
        </div>
        <button id="addBtn" class="btn btn-primary"/>追加</button>
      </div>
        <table class="table" id="todoList">
          <thead>
            <tr>
              <th>ID</th>
              <th>タスク</th>
              <th></th>
            </tr>
          </thead>
          <tbody id="todoLists"></tbody>
        </table>
    </div>

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

今回はBootstrapを使用していますので、CDN及びリソースの読み込みを忘れないでください。

上記のソースコードで以下のような画面になると思います。

実際の画面

Javascript(タスク登録まで)

それではJavascriptの実装に移りたいと思います。

完成コードはこちら↓↓↓↓↓

"use strict";

const inputTodo = document.getElementById("inputTodo");
const todoLists = document.getElementById("todoLists");
const addBtn = document.getElementById("addBtn");

// デフォルト値で1を設定
let currentNum = 1;
// todoを保存する箱
let todos = [];

addBtn.addEventListener("click", () => {
  if (inputTodo.value === "") {
    alert("タスクを入力してください");
    return;
  }

  // 先ほど用意したタスクを保存する箱に保存
  todos.push({
    id: currentNum,
    title: inputTodo.value
  });

  createListView();

  inputTodo.value = "";
  currentNum++;
});

const createListView = () => {
  // タスクを描画するときにtbodyの中に子要素が一つでもあれば一つになるまで削除する
  while (todoLists.firstChild) {
    todoLists.removeChild(todoLists.firstChild);
  }

  todos.forEach((todo) => {
    // // tr要素の生成
    let todoItem = document.createElement("tr");
    // todoのIDを表示するthの生成
    const todoId = document.createElement("th");
    // todoのタイトルを表示するthの生成
    const todoTitle = document.createElement("th");
    // 削除ボタンを表示するthの生成
    const todoDelete = document.createElement("th");
    // 削除ボタンの生成
    const deleteBtn = document.createElement("button");

    todoId.textContent = todo.id;
    todoTitle.textContent = todo.title;
    deleteBtn.textContent = "削除";
    deleteBtn.classList.add("btn", "btn-secondary");
    todoDelete.appendChild(deleteBtn);

    todoItem.appendChild(todoId);
    todoItem.appendChild(todoTitle);
    todoItem.appendChild(todoDelete);
    todoLists.appendChild(todoItem);
  });
};

1行目の'use strict'は、JavaScriptでエラーになりにくい実装をエラーにし、かつ素早く実行してくれるいわゆる厳格モード(strictモード)に切り替える、という宣言です。

これがないと例えば変数宣言(var、let、const)をしなくても変数の定義ができてしまいます。エラーやバグの温床になるので、'use strict' 宣言はぜひ忘れないようにしておきましょう!

それではまず実際にこちらのソースコードを作る上でどういった手順で書いていくのかというところに着目したいと思います。

おそらくProgateなどで軽く勉強したばかりの状態だと、実際にどうやって書いていけば良いのかが全然掴めないかと思いますので、自分なりにこれらのコードを実装していく過程を記していきます。

Todoリストを作るまでの全体イメージ(タスク登録まで)

  1. 追加したいタスクの内容をフォームに記入後、追加ボタンを押下
  2. あらかじめ用意した配列(複数の情報を保持しておくための箱)に①で生成したTodoを一旦格納しておく。(作ったものから順に詰めていくイメージ)
  3. ②で用意した箱の中身を展開。Todoの存在する数だけ画面上に描画する処理を行う。(タスク登録)

図でイメージするとこんな感じです↓↓↓(あくまでイメージです)

ざっと全体的なイメージを掴んだ上で次に実際に具体的な実装手順を記していきます。

手順

  1. 必要なHTML要素にアクセスできるようにする(3 ~ 5行目)
  2. 空の配列、初期値の用意(8 ~ 11行目)
  3. 具体的な処理の実装(タスク登録、イベント処理)
    1. 空配列にタスクを保存する
    2. タスクを実際に生成して描画処理を行う
    3. タスクを追加した後の処理
  4. 関数処理の実装

ざっくり以上になります。上から詳しく説明していきます。

1. 必要なHTML要素にアクセスできるようにする(3 ~ 5行目)

まず①ですが、言葉の通り必要なHTML要素にアクセスができるよう変数に格納して準備をしておきます。

まず初めに前提として、JavascriptからHTML要素にアクセスする方法の一つとして

document.getElementById('指定のID要素')

という形で要素の取得が可能です。

例えばですが今回の事例だと、

document.getElementById("inputTodo")

こちらでHTMLの中にある#inputTodoの要素を取得することができます。ただ、毎回この記述でアクセスするのは面倒なので、あらかじめinputTodoといった具合に変数に格納しておくと後で使用するときに楽になりますので、押さえておきましょう。

なのでここでは、

const inputTodo = document.getElementById("inputTodo"); //①
const todoLists = document.getElementById("todoLists"); //②
const addBtn = document.getElementById("addBtn"); //③

といった具合に現時点で必要となるDOM要素を取得しておきます。すると、この時点で以下要素へのアクセスが可能になります。

①は以上になります。

2. 空の配列、初期値の用意(7 ~ 10行目)

さて、続いては②ですが、こちらは後々処理を書いていく上で必要になる初期値や空配列といったものを定義していきます。

タスクイメージの図でも記載しましたが、todo(タスク)を追加する上でそれを一旦保存するための箱が必要になります。いわゆる空配列です。

// タスクを保存する箱
let todos = [];

①だけで紹介した「変数」だけだと値を1つしか保存できないので、このような場合だと使い勝手が良くありません。そこで何か複数の要素を保持するためにはこのような空の箱(配列)を用意しておく必要があります。

こうすることで実際に複数のタスクを登録することが実現可能になります。

次にタスクのIDを振るために、初期値としてcurrentNumという変数を定義してあげましょう。初期値は1としておきます。

// 初期値
let currentNum = 1;

今回のタスク登録の部分ではそこまで活用される場面はありませんが、後々「削除」や「編集」などといったところで大いに活用するものになるので、今は各タスクには番号が振られるんだなというぐらいで押さえておくと良いでしょう。

②は以上になります。

3. 具体的な処理の実装(タスク登録、イベント処理)

さて、①、②では下準備の部分行いました。ようやく次のステップへと移っていきます。
ここではイベントの設定をしていきます。

イベントとはざっくりいうとユーザー操作によって発生するあらゆるアクションの総称を指します。
例えばですが、以下のものが存在します。

・クリック
・ホバー
・キーボード入力
・フォーム送信
・画面のリサイズ(可変)
… etc

今回の事例で言うと実際にタスクを追加するためにボタンを”クリック”する必要がありますね。

つまり、追加ボタンのクリックが今回のイベントになります。(通称:クリックイベント)

ここを実際に利用することで、タスクの登録が可能になります。あともう少しです!!

それではクリックイベントからのコードを見ていきましょう。
※完成版のコードとの違いを説明するために、敢えて書き方を変えています。以降はこちらのコードを見ながら解説していきます。

// 「追加」ボタンを押したときに以下の処理が走る。
addBtn.addEventListener("click", () => {

// もしフォームに何の値も入力されなかったとき
  if (inputTodo.value === "") {
    alert("タスクを入力してください");
    return;
  }

// あらかじめ用意した空配列にtodoを保存する ②
  todos.push({
    id: currentNum,
    title: inputTodo.value,
  });

// タスクを実際に生成して描画処理を行う関数 ③
  while (todoLists.firstChild) {
    todoLists.removeChild(todoLists.firstChild);
  }

  todos.forEach((todo) => {
    // tr要素の生成
    const todoItem = document.createElement("tr");
    // todoのIDを表示するthの生成
    const todoId = document.createElement("th");
    // todoのタイトルを表示するthの生成
    const todoTitle = document.createElement("th");
    // 削除ボタンを表示するthの生成
    const todoDelete = document.createElement("th");
    // 削除ボタンの生成
    const deleteBtn = document.createElement("button");

    todoId.textContent = todo.id;
    todoTitle.textContent = todo.title;
    deleteBtn.textContent = "削除";
    deleteBtn.classList.add("btn", "btn-secondary");
    
    todoItem.appendChild(todoId);
    todoItem.appendChild(todoTitle);
    todoItem.appendChild(todoDelete);

    todoLists.appendChild(todoItem);
  });

// タスクを追加した後の処理 ④
  inputTodo.value = "";
  currentNum++;
});

少し複雑になった感がありますが、順番に上から見ていきます。
※繰り返しになりますが、これらの処理は「追加」ボタンが押されたときに初めて走る処理群です。

3-1:空配列にタスクを保存する

続いて②ですが、ここがキモになります。

先ほどあらかじめ用意した空配列を思い出してください。
空の配列、初期値の用意(8 ~ 11行目)で用意した空配列ですね。ここでようやくコイツを使います。

// タスクを保存する箱
let todos = [];

※大事なのでもう一度貼っておきます。

で、実際の処理がこちらですね。

// 先に用意した空配列にタスクを保存する
  todos.push({
    id: currentNum,
    title: inputTodo.value,
  });

ここでは登録したタスクを実際に

・タスクの番号:currentNum
・タスク名:inputTodo.value

という形式で一つのオブジェクトにしてtodosに格納します。いわゆる連想配列です。

連想配列は、値に任意の名前(キー)を割り振ることができます。

今回の例だと、タスクの番号(ID)とタスクの名前の2種類のデータを扱う必要があるので、このような異なるデータ同士を一括で扱う場合には連想配列を使うと便利になります。

連想配列に関してのより詳しい内容はこちらより↓↓↓↓↓↓

https://www.fenet.jp/dotnet/column/environment/3722/#javascript

Javascriptの連想配列とは?|具体例を交えて解説! | .NETコラム

ここで初めてpushというメソッドが登場します。
これは簡単にいうと指定の配列に任意の値を後ろから詰めていくといったものになります。

実際に②の処理のすぐ後にconsole.log(todos)と記述して、todosの中身を覗いてみましょう。(以降の処理はこの時点で書いてなくても構いません。)
(※console.log(調べたいデータ)は任意の変数の状態や中身を調べたい時にとても有用な手段です。)

例えばここで登録するタスクとして「おつかい」を入力して、追加ボタンを押すとtodosは以下のようになります。

検証ツール上で上記のような画面が確認できます。

実際にここでは空の配列([])に { id:1, title: 'おつかい'}が追加されました。

ご覧の通り[{...}]という形になっていますね。
ここで一つのオブジェクト(連想配列)が空の配列に追加されたことが確認できたかと思います。

この時点ではまだHTML上には何も反映できていませんので、是非手元でコードを動かしてみてtodosの中身を確認したりするなり色々と試してみてください。

3-2:タスクを動的に生成してHTML上に描画処理を行う処理

さて、いよいよ実際に画面上にTodoを描画していきます。

ここでの流れは

配列todosを展開 ▶︎ タスク内容の入ったHTML要素を生成 ▶︎ 画面上に描画

という流れで処理を描いていきます。

ここでの処理を通じて、実際に生成されるHTML要素がこちらになります。(見本)↓↓↓↓

<tr>
<th>1</th>
<th>タスク名</th>
<th><button class="btn btn-secondary">削除</button></th>
</tr>

画像で見るとココ

生成されるHTML要素

全体の流れを確認したところで早速、処理に移ります。

まずはじめに配列の中身を覗いて、展開していきましょう。その処理がこちらの部分です。

todos.forEach((todo) => {
     // 以下略
  });

arr.forEach((hoge) => { 任意の処理 });

forEachの説明ですが簡単な内容としては、

指定の配列(arr)から単一のデータ(hoge)を一つずつ取り出し、その数だけ任意の処理を繰り返し行う

という配列のメソッドになります。

こちらはES6というJavascriptの中でも比較的新しい記法になります。従来は配列を用いた繰り返し文はfor文などで表現するものでしたが、それよりも短く効率的に書くことができるため、基本的に同じようなケースではES6の記法で書くことを推奨されています。
この時点で少し難しいと感じたら、まずは従来のfor文で書いてみて、徐々に慣れていくことも大いにアリだと思います。

で、配列todosからtodoという単一のデータを一つずつ列挙していき、その数だけ任意の処理を行っていきます。

ここでいう任意の処理とは動的にHTML要素を生成して、画面上に描画をする処理になります。それがこちら↓↓

    // tr要素の生成
    const todoItem = document.createElement("tr");
    // todoのIDを表示するthの生成
    const todoId = document.createElement("th");
    // todoのタイトルを表示するthの生成
    const todoTitle = document.createElement("th");
    // 削除ボタンを表示するthの生成
    const todoDelete = document.createElement("th");
    // 削除ボタンの生成
    const deleteBtn = document.createElement("button");

    todoId.textContent = todo.id;
    todoTitle.textContent = todo.title;
    deleteBtn.textContent = "削除";
    deleteBtn.classList.add("btn", "btn-secondary");
    
    todoItem.appendChild(todoId);
    todoItem.appendChild(todoTitle);
    todoItem.appendChild(todoDelete);

    todoLists.appendChild(todoItem);

少しヘビーですね。、m(__)m 上から読んでいきます。

まず初めにdocument.createElement('要素名')でHTML要素の生成ができるので、trを作ります。

const todoItem = document.createElement("tr");

こちらで<tr></tr>の生成ができました。ついでにこちらもtodoItemという変数名で定義してあげましょう。

基本的にcreateElement('要素名')は一つの要素を生成することになります。なので、同じ要領で

・ID名
・タイトル名
・削除ボタン

の3つの要素を生成するよう記述していきましょう。

// todoのIDを表示するthの生成
 const todoId = document.createElement("th");
// todoのタイトルを表示するthの生成
 const todoTitle = document.createElement("th");
// 削除ボタンを表示するthの生成
 const todoDelete = document.createElement("th");
// 削除ボタンの生成
 const deleteBtn = document.createElement("button");

削除ボタンは<th></th>の中にさらに<button></button>要素が必要になるので、同時にこちらも生成します。
※今回削除機能までは実装しないので、一旦ボタンの生成までとします。

それぞれの要素の生成ができたところで、さらに肉付けを行っていきます。

// IDが入るth要素(todoId)のテキストにtodo.idを代入
todoId.textContent = todo.id;
// タスクの名前が入るth要素(todoTitle)のテキストにtodo.titleを代入
todoTitle.textContent = todo.title;
// 削除ボタンのテキストに'削除'というテキストを代入
deleteBtn.textContent = "削除";
// 削除ボタンのクラスに 'btn'、 'btn-secondary' を代入
deleteBtn.classList.add("btn", "btn-secondary");
// 削除ボタンが入るth要素(todoDelete)にdeleteBtnを差し込む
todoDelete.appendChild(deleteBtn);

コメントに書いてある通りですが、ここではthの中の整形及びボタンの肉付けを行っています。

ここで特筆すべき点はtodoDelete.appendChild(deleteBtn)といった、appendChildを使ったHTML要素の差し込みになります。

任意の親要素.appendChild('追加したい要素')とすることで、任意の要素を指定の親要素に追加することができます。

最後に、最初に生成したth要素(todoItem)にまだこれらが追加できていないので、ボタンの時と同じ要領でそれぞれ追加していきます!

//<tr></tr>にそれぞれの要素を追加していく

todoItem.appendChild(todoId); //タスクID
todoItem.appendChild(todoTitle); //タスク名
todoItem.appendChild(todoDelete); //削除ボタン

// ここで<tr>/<tr>に上の3つのthを差し込む。
todoLists.appendChild(todoItem);

するとようやくこれで、。、、

<tr>
<th>1</th>
<th>タスク名</th>
<th><button class="btn btn-secondary">削除</button></th>
</tr>

Todo項目が完成しました! 長かったですね。。、、💦

これでタスクの登録はできました!ヤッター!

。。アレ?

荒ぶるTodoリスト

なんかバグってね??2個目のタスク追加したら前のTodoも一緒に追加されるんだけど、。思ってたのと違う、。、

ここ、ハマりポイントなので注意です。

なぜこんなことが起きてしまうのかというと、タスクの描画処理は配列todosが保持する全てのデータを元に毎回1からHTMLによる表示を行うからです。

口頭で言ってもイメージしにくいかと思いますので、今一度最初のtodo登録のイメージ図を振り返ってみましょう。

右下の最後の※todosにタスクが蓄積されるというところに注目です。todosには登録したタスクが次から次へと蓄積される仕組みになっており、タスク登録をするとその時点で保持してある内容を元に1から画面上に描画していく流れになっています。

要は、1回目、2回目、3回目、。。とタスクを登録する際には現時点で以下のような流れになっています。

1回目タスク登録(A):[{タスクA}] ▶︎ 描画処理
//タスクAを画面上に描画

2回目タスク登録(B):[{タスクA}、{タスクB}] ▶︎ 描画処理
// Aのタスクが画面上に残ったまま新たに2つ分のタスクが画面上に追加される。

3回目タスク登録(C):[{タスクA}、{タスクB}}、{タスクC}] ▶︎ 描画処理 //
// A、Bで登録したタスクが画面上に残ったまま新たに3つ分のタスクが画面上に追加される。

イメージ湧いてきたでしょうか?以上のことにより、先程のような表示結果が発生してしまうこととなります。

じゃあこれをどうすれば解決できるのかというところですが、todos配列を展開する前に戻って考える必要があります。

それがこちらです。

//todoListに子要素がある限り
while (todoLists.firstChild) {
    // todoListの子要素を削除する
    todoLists.removeChild(todoLists.firstChild);
}

こちらはtodosList(初めの方に定義したul要素)直下に子要素がなくなるまで、子要素を削除し続けるという内容になります。

なので、タスクを描画する前にここで、今ulの中に子要素があるかどうか確認をして、あったらそれらがなくなるまで一旦全て削除してから後続の処理に移るといった流れになります。

なので、これを書くことで前に登録したタスクが画面上に残ったまま問題はここで解消されます。

③は以上になります。

3-3タスクを追加した後の処理

いよいよ大詰めです。早速③で修正した問題を確かめたいのですが、最後にタスクのIDが追加するたびに1増えていく実装をします。

// タスクを追加した後の処理
currentNum++;
inputTodo.value = "";

ついでにタスク登録後もなおフォームに入力値が残ったままになるので、その値を空になるにすると、。。

タスク登録完了の画面

ようやく形になりましたね!!!

4. 関数処理の実装

この時点で目的とするタスクの登録の実装は完了です!
ただ、現状のコードが少し冗長なので一部関数にまとめてコードを書き直してみましょう!

完成コードにはもう記載していますが、例えば今回で言うと、先述のこれらの処理は関数としてまとめられそうな気がしますね。

// todoを生成しHTML上に挿し込む処理
const createListView = () => {

  // todoを描画するときにtbodyの中に子要素が一つでもあれば一つになるまで削除する
  while (todoLists.firstChild) {
    todoLists.removeChild(todoLists.firstChild);
  }

  todos.forEach((todo) => {
    // tr要素の生成
    const todoItem = document.createElement("tr");
    // todoのIDを表示するthの生成
    const todoId = document.createElement("th");
    // todoのタイトルを表示するthの生成
    const todoTitle = document.createElement("th");
    // 削除ボタンを表示するthの生成
    const todoDelete = document.createElement("th");
    // 削除ボタンの生成
    const deleteBtn = document.createElement("button");

    todoId.textContent = todo.id;
    todoTitle.textContent = todo.title;
    deleteBtn.textContent = "削除";
    deleteBtn.classList.add("btn", "btn-secondary");
    
    todoItem.appendChild(todoId);
    todoItem.appendChild(todoTitle);
    todoItem.appendChild(todoDelete);

    todoLists.appendChild(todoItem);
  });
};

ここではcreateListViewという関数にして、先程のtodoを生成 ~ 表示までの処理をまとめています。

こうすることであとはこの処理をつかいたいところでcreateListView()と記述してやることで、同様の処理を走らせることができます!

なので、もし再利用できそうな処理の塊だとか、気になったところがあればこのように関数にして書き換えてみましょう!

具体的な実装はここまでになります!!

実際に動かしてみよう!

これでタスクの登録までは一旦完成です!おつかれさまでした!!✨

ここで改めてここまで完成したTodoリストを動かしてみましょう!

サンプルとしてタスク入力がなかったときの処理も書いていますが、ここではおまけ程度として触れておきます。

// タスクの項目に何も入力がなかった場合
  if (inputTodo.value === "") {
    alert("タスクを入力してください");
    return;
  }

登録するタスクが空なのに登録されるのは少々不自然かと思うので、このような細かい配慮もぜひ考えてみてください😊

まとめ

いかがだったでしょうか。

今回はtodoリストをテーマに要素のアクセスから動的なHTML要素の生成まで、実際に手を動かす形で記事をまとめました。

タスク登録だけでかなり内容がてんこ盛りになってしまいましたが、その分わかりやすさ重視の内容になっていれば幸いです。

タスクの削除や編集などがまだ行えていませんが、徐々に記事にまとめていこうかと思います。

それではまた次回の記事でお会いしましょう〜!

もりけん塾で学習中!

もりた先生のブログ: kenjimorita.jp (武骨日記)
もりた先生のアカウント: twitter.com/terrace_tech

この記事を書いた人

コメント

コメントする

CAPTCHA


目次
閉じる