JavaScript | 1 日 90 分 × 7 日アプリ学習:ミニ総合アプリ(初級編)

JavaScript
スポンサーリンク

5日目のゴールと今日やること

5日目のテーマは
「ミニ総合アプリを“複数項目 × 条件分岐で切り替えられるアプリ”に進化させる」
ことです。

ここまでであなたは、
テキスト 1 項目を入力して、一覧表示・削除・編集・検索・ソート・バリデーションまで扱えるようになりました。

5日目はここに、

  • 入力項目を「複数」にする(タイトル+カテゴリ+完了フラグなど)
  • 一覧で「複数項目」を表示する
  • 条件分岐で「表示モード」を切り替える(すべて/未完了のみ など)
  • データ構造(オブジェクト)を意識して設計する

という、“ちょっとした ToDo アプリ”に近い形を目指します。


入力項目を「タイトル+カテゴリ」に増やす

HTML に複数入力欄を用意する

まずは、タイトルとカテゴリを入力できるようにします。

<div>
  <input type="text" id="titleInput" placeholder="タイトルを入力">
  <select id="categorySelect">
    <option value="仕事">仕事</option>
    <option value="プライベート">プライベート</option>
    <option value="その他">その他</option>
  </select>
  <button id="addButton">追加</button>
</div>

<div id="errorArea"></div>
<div id="statusArea"></div>
<div id="listArea"></div>

ここでのポイントは、
「1件のデータが“複数の情報”を持つ前提で設計する」
という意識を持つことです。

データ構造をオブジェクトで設計する

1件のデータをこういう形にします。

{
  id: 1,
  title: "資料作成",
  category: "仕事",
  done: false
}
JavaScript

タイトル
カテゴリ
完了フラグ(done)

この 3 つを持つことで、
後で「カテゴリで絞り込み」「完了だけ表示」などの条件分岐がしやすくなります。


入力から「複数項目のオブジェクト」を作る

データ配列と ID カウンタ

let nextId = 1;
const items = [];
JavaScript

入力値を取得してオブジェクトを作る

const titleInput = document.getElementById("titleInput");
const categorySelect = document.getElementById("categorySelect");
const addButton = document.getElementById("addButton");

addButton.addEventListener("click", () => {
  const title = titleInput.value.trim();
  const category = categorySelect.value;

  const errors = validateInput(title);
  if (errors.length > 0) {
    showErrorMessages(errors);
    return;
  }
  clearErrorMessages();

  const newItem = {
    id: nextId,
    title: title,
    category: category,
    done: false
  };

  items.push(newItem);
  nextId += 1;

  titleInput.value = "";
  renderList();
  updateStatusMessage();
});
JavaScript

ここで重要なのは、
「入力 → オブジェクト化 → 配列に push」
という流れを、
1件の“レコード”として意識することです。


バリデーションを「タイトル専用」にして整理する

タイトルのバリデーション関数

function validateInput(title) {
  const errors = [];

  if (title.length === 0) {
    errors.push("タイトルを入力してください。");
  }

  if (title.length > 30) {
    errors.push("タイトルは30文字以内にしてください。");
  }

  return errors;
}
JavaScript

エラー表示関数

const errorArea = document.getElementById("errorArea");

function showErrorMessages(errors) {
  errorArea.innerHTML = "";
  errors.forEach((msg) => {
    const p = document.createElement("p");
    p.textContent = msg;
    p.style.color = "red";
    errorArea.appendChild(p);
  });
}

function clearErrorMessages() {
  errorArea.innerHTML = "";
}
JavaScript

ここでの深掘りポイントは、
「入力チェックは“入力の意味”に合わせて関数を分ける」
ということです。

タイトルのルール
(文字数・空チェックなど)

メールアドレスのルール
(@ を含むかなど)

こうやって“意味ごと”に関数を分けていくと、
アプリが大きくなっても整理しやすくなります。


一覧に「タイトル+カテゴリ+完了状態」を表示する

renderList を複数項目対応にする

const listArea = document.getElementById("listArea");

function renderList() {
  listArea.innerHTML = "";

  if (items.length === 0) {
    const p = document.createElement("p");
    p.textContent = "データがありません。";
    listArea.appendChild(p);
    return;
  }

  items.forEach((item) => {
    const row = document.createElement("div");

    const titleSpan = document.createElement("span");
    titleSpan.textContent = item.title;

    const categorySpan = document.createElement("span");
    categorySpan.textContent = `(${item.category})`;
    categorySpan.style.marginLeft = "8px";
    categorySpan.style.fontSize = "0.9em";
    categorySpan.style.color = "#555";

    if (item.done) {
      titleSpan.style.textDecoration = "line-through";
      titleSpan.style.color = "#888";
    }

    const doneButton = document.createElement("button");
    doneButton.textContent = item.done ? "未完了に戻す" : "完了";
    doneButton.addEventListener("click", () => {
      toggleDone(item.id);
    });

    const deleteButton = document.createElement("button");
    deleteButton.textContent = "削除";
    deleteButton.addEventListener("click", () => {
      deleteItem(item.id);
    });

    row.appendChild(titleSpan);
    row.appendChild(categorySpan);
    row.appendChild(doneButton);
    row.appendChild(deleteButton);

    listArea.appendChild(row);
  });
}
JavaScript

ここでの重要ポイントは、

  • 表示用の要素を「役割ごと」に分けて作る(タイトル用、カテゴリ用、ボタン用)
  • done フラグによって見た目を変える(取り消し線・色)

というところです。


完了フラグを切り替える条件分岐

完了状態のトグル関数

function toggleDone(id) {
  const target = items.find((item) => item.id === id);
  if (!target) return;
  target.done = !target.done;
  renderList();
  updateStatusMessage();
}
JavaScript

target.done = !target.done; は、
true → false
false → true

と反転させる書き方です。

ここでの条件分岐は、
「今の状態によって次の状態を変える」
という典型的なパターンです。


削除機能を複数項目版でもそのまま使う

削除関数

function deleteItem(id) {
  const newItems = items.filter((item) => item.id !== id);
  items.length = 0;
  newItems.forEach((i) => items.push(i));
  renderList();
  updateStatusMessage();
}
JavaScript

ここは 2〜4日目と同じ考え方です。

  • filter で「残したいものだけ」を残す
  • 元の配列を書き換える
  • 再描画する

という流れは、
「配列が真実、画面はその写し」という設計のままです。


条件分岐で「表示モード」を切り替える

表示モードの状態を持つ

「すべて」「未完了のみ」「完了のみ」
といった表示モードを切り替えられるようにします。

let viewMode = "all"; // "all" | "todo" | "done"
JavaScript

HTML にボタンを用意します。

<button id="viewAllButton">すべて</button>
<button id="viewTodoButton">未完了のみ</button>
<button id="viewDoneButton">完了のみ</button>

JavaScript で取得してイベントを付けます。

const viewAllButton = document.getElementById("viewAllButton");
const viewTodoButton = document.getElementById("viewTodoButton");
const viewDoneButton = document.getElementById("viewDoneButton");

viewAllButton.addEventListener("click", () => {
  viewMode = "all";
  renderList();
  updateStatusMessage();
});

viewTodoButton.addEventListener("click", () => {
  viewMode = "todo";
  renderList();
  updateStatusMessage();
});

viewDoneButton.addEventListener("click", () => {
  viewMode = "done";
  renderList();
  updateStatusMessage();
});
JavaScript

viewMode に応じて表示するデータを絞る

renderList の中で、
viewMode に応じて items を絞り込みます。

function renderList() {
  listArea.innerHTML = "";

  let visibleItems = items;

  if (viewMode === "todo") {
    visibleItems = items.filter((item) => !item.done);
  } else if (viewMode === "done") {
    visibleItems = items.filter((item) => item.done);
  }

  if (visibleItems.length === 0) {
    const p = document.createElement("p");
    p.textContent = "表示できるデータがありません。";
    listArea.appendChild(p);
    return;
  }

  visibleItems.forEach((item) => {
    // さきほどの row 作成処理をここで使う
  });
}
JavaScript

ここでの深掘りポイントは、
「状態(viewMode)× 条件分岐(if / else if)× filter」
という組み合わせです。

これができると、
「同じデータを、違う切り口で見せる」
というアプリらしい動きが作れます。


状態メッセージで「今どんな表示か」を伝える

状態メッセージの更新

const statusArea = document.getElementById("statusArea");

function updateStatusMessage() {
  if (items.length === 0) {
    statusArea.textContent = "まだデータがありません。タイトルとカテゴリを入力して追加してみましょう。";
    return;
  }

  if (viewMode === "all") {
    statusArea.textContent = "すべての項目を表示中です。完了ボタンで状態を切り替えられます。";
  } else if (viewMode === "todo") {
    statusArea.textContent = "未完了の項目だけを表示中です。";
  } else if (viewMode === "done") {
    statusArea.textContent = "完了した項目だけを表示中です。";
  }
}
JavaScript

状態(items の件数、viewMode)を見て、
メッセージを切り替えることで、
ユーザーが「今どういう状態なのか」を理解しやすくなります。


今日いちばん深く理解してほしいこと

1件のデータを
「複数のプロパティを持つオブジェクト」
として扱うことで、

  • タイトル
  • カテゴリ
  • 完了フラグ

といった情報をまとめて管理できるようになりました。

そして、

  • viewMode(表示モード)
  • done(完了状態)

といった「状態」を
条件分岐と filter で組み合わせることで、
「同じデータを、違う視点で見せるアプリ」
になりました。

入力
一覧
削除
条件分岐

これらが、
「オブジェクト × 配列 × 状態 × 条件分岐」
という一本の軸でつながってきたのが、
今日の一番大きなポイントです。

明日 6日目・7日目では、
このミニ総合アプリを
「自分で説明できる」「自分なりにアレンジできる」
レベルまで仕上げていきます。

タイトルとURLをコピーしました