JavaScript | 1 日 90 分 × 7 日アプリ学習:DOM・イベント・ブラウザUI メモ帳アプリ編

Web APP JavaScript
スポンサーリンク
スポンサーリンク

メモ帳アプリ 1日目のゴール

1日目のテーマは 「ブラウザ上で動く、シンプルなメモ帳アプリの“最初の形”を作る」 ことです。

重点として挙がっている

  • querySelector() / querySelectorAll()
  • addEventListener()
  • createElement() / append() / remove()
  • classList
  • dataset
  • localStorage

を使いながら、今日はここまでを目指します。

  • 画面に「メモ入力欄」と「追加ボタン」と「メモ一覧」を作る
  • メモを追加すると、一覧に表示される
  • メモを削除できる
  • メモ一覧を localStorage に保存する(ページを閉じても残る)

ToDoアプリで学んだ DOM 操作と localStorage を、 「メモ帳」という別の題材でもう一度体に入れていくイメージです。

1日目の全体ステップ

1日目の流れはこんな感じです。

  1. メモ帳アプリの画面レイアウトを決める(HTML)
  2. JavaScriptで DOM 要素を querySelector() でつかまえる
  3. メモ一覧を管理する配列を用意する
  4. メモを追加する関数を作る
  5. メモ一覧を画面に描画する関数を作る
  6. メモを削除するボタンを作る
  7. localStorage に保存・読み込みする関数を作る
  8. ページ読み込み時に保存データを読み込んで表示する

メモ帳アプリの画面レイアウト(HTML)

まずは、シンプルなメモ帳の画面を作ります。

  • 上に「タイトル」
  • その下に「メモ入力欄」と「追加ボタン」
  • さらに下に「メモ一覧」

という構成です。

<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <title>メモ帳アプリ 1日目</title>
  <style>
    /* メモの本文を少し見やすくするためのスタイル */
    .memo-item {
      border: 1px solid #ccc;
      padding: 8px;
      margin-bottom: 8px;
    }

    .memo-item-title {
      font-weight: bold;
      margin-bottom: 4px;
    }

    .memo-item-body {
      white-space: pre-wrap; /* 改行をそのまま表示する */
    }
  </style>
</head>
<body>
  <h1>メモ帳アプリ(1日目)</h1>

  <div>
    <label for="memoTitleInput">タイトル:</label>
    <input id="memoTitleInput" type="text" placeholder="メモのタイトルを入力してください">
  </div>

  <div>
    <label for="memoBodyInput">本文:</label><br>
    <textarea id="memoBodyInput" rows="4" cols="40" placeholder="メモの本文を入力してください"></textarea>
  </div>

  <div>
    <button id="addMemoButton">メモを追加</button>
  </div>

  <p id="errorMessage" style="color: red;"></p>

  <h2>メモ一覧</h2>
  <div id="memoList"></div>

  <script>
    // ここに JavaScript を書いていきます
  </script>
</body>
</html>

ポイント:

  • タイトル用に input、本文用に textarea を用意
  • メモ一覧は <div id="memoList"></div> に、 JavaScriptから createElement() / append() で追加していく

アプリの状態と定数を定義する

次に、JavaScript側で「メモ一覧」を管理する配列と、 localStorage 用のキー名を定義します。

// --- アプリの状態(State) ---
// メモ一覧(1件ごとにオブジェクトを持つ)
let memos = [];

// 次に使うメモのID(1, 2, 3, ... と増えていく)
let nextMemoId = 1;

// localStorageで使うキー名(このアプリ専用の保存箱の名前)
const STORAGE_KEY = "my_memo_app_data";
JavaScript

深掘りポイント:

  • メモを「オブジェクト」で表現します。 例えば、javascript{ id: 1, title: "買い物リスト", body: "牛乳\nパン\n卵", createdAt: 1710000000000 }
  • nextMemoId を持っておくことで、 追加するたびにユニークなIDを振ることができます。
  • STORAGE_KEYlocalStorage の「このアプリ専用の名前」です。

DOM要素を querySelector / querySelectorAll で取得する

画面上の要素を JavaScript から操作できるようにします。

// --- DOM参照(画面上の要素をつかまえる) ---
const memoTitleInput = document.querySelector("#memoTitleInput");
const memoBodyInput = document.querySelector("#memoBodyInput");
const addMemoButton = document.querySelector("#addMemoButton");
const errorMessage = document.querySelector("#errorMessage");
const memoList = document.querySelector("#memoList");
JavaScript

ここでは querySelector() だけですが、 後で「タグ付きメモ」などに発展させるときに querySelectorAll() が活きてきます。

localStorage に保存・読み込みする関数

保存関数(saveToStorage)

// --- ストレージ:状態をlocalStorageに保存する ---
// memos配列とnextMemoIdをまとめて保存する
function saveToStorage() {
  // 保存したいデータを1つのオブジェクトにまとめる
  const data = {
    memos: memos,
    nextMemoId: nextMemoId
  };

  // オブジェクトをJSON文字列に変換する
  const json = JSON.stringify(data);

  // localStorageに文字列として保存する
  localStorage.setItem(STORAGE_KEY, json);
}
JavaScript

深掘りポイント:

  • localStorage は「文字列しか保存できない」
  • 配列やオブジェクトを保存したいときは JSON.stringify() で文字列に変換する
  • memosnextMemoId を一緒に保存しておくことで、 ページを再読み込みしてもIDがかぶらない

読み込み関数(loadFromStorage)

// --- ストレージ:localStorageから状態を読み込む ---
function loadFromStorage() {
  // localStorageからJSON文字列を取り出す
  const json = localStorage.getItem(STORAGE_KEY);

  // まだ何も保存されていない場合は何もしない
  if (json === null) {
    return;
  }

  try {
    // JSON文字列をオブジェクトに変換する
    const data = JSON.parse(json);

    // ざっくり形をチェックしてから代入する
    if (Array.isArray(data.memos) && typeof data.nextMemoId === "number") {
      memos = data.memos;
      nextMemoId = data.nextMemoId;
    }
  } catch (error) {
    // 保存データが壊れていた場合は無視する
    console.error("保存データの読み込みに失敗しました:", error);
  }
}
JavaScript

メモを追加する関数(addMemo)

次に、メモを1件追加する関数を作ります。

// --- 操作ロジック:メモを追加する ---
function addMemo(title, body) {
  const newMemo = {
    id: nextMemoId,
    title: title,
    body: body,
    createdAt: Date.now() // 追加した時刻を記録しておく
  };

  // 次に使うIDを1つ進める
  nextMemoId = nextMemoId + 1;

  // 新しいメモを配列に追加
  memos.push(newMemo);

  // 状態が変わったら保存
  saveToStorage();
}
JavaScript

ポイント:

  • メモは「タイトル」と「本文」を持つ
  • createdAt を持たせておくと、 後で「新しい順」「古い順」などの並び替えに使える

メモ一覧を画面に描画する関数(renderMemos)

ここが DOM 操作のメインです。 配列 memos をもとに、画面上にメモを表示します。

// --- 描画:状態から画面(DOM)を作り直す ---
function renderMemos() {
  // 一覧をいったん空にする
  memoList.innerHTML = "";

  // memos配列の1件ごとにDOMを作る
  memos.forEach(function(memo) {
    // メモ全体を包むdiv
    const memoDiv = document.createElement("div");
    memoDiv.classList.add("memo-item");

    // このメモがどれかを識別するためのIDをdata-idに持たせる
    memoDiv.dataset.id = String(memo.id);

    // タイトル部分
    const titleDiv = document.createElement("div");
    titleDiv.classList.add("memo-item-title");
    titleDiv.textContent = memo.title;

    // 本文部分
    const bodyDiv = document.createElement("div");
    bodyDiv.classList.add("memo-item-body");
    bodyDiv.textContent = memo.body;

    // 削除ボタン
    const deleteButton = document.createElement("button");
    deleteButton.textContent = "削除";

    // 削除ボタンが押されたとき:このメモを削除する
    deleteButton.addEventListener("click", function(event) {
      // クリックされたボタンから親のmemoDivを探す
      const memoElement = event.target.closest(".memo-item");

      // memoDivのdata-idからIDを取り出す
      const idText = memoElement.dataset.id;
      const id = Number(idText);

      // 状態を削除して再描画
      deleteMemo(id);
    });

    // memoDivの中に要素を追加する
    memoDiv.append(titleDiv);
    memoDiv.append(bodyDiv);
    memoDiv.append(deleteButton);

    // memoListの中にmemoDivを追加する
    memoList.append(memoDiv);
  });
}
JavaScript

深掘りポイント:

  • createElement() で新しい要素を作り、 append() で親要素に追加していく
  • classList.add() で CSS クラスを付けることで、 見た目を整えつつ「意味」も持たせられる
  • dataset.id にIDを持たせておくことで、 「どのメモを削除するか」を簡単に特定できる

メモを削除する関数(deleteMemo)

削除ボタンから呼ばれる関数です。

// --- 操作ロジック:メモを削除する ---
function deleteMemo(id) {
  // 指定したID以外のメモだけを残した新しい配列を作る
  const newMemos = memos.filter(function(memo) {
    return memo.id !== id;
  });

  // 元の配列の中身を入れ替える(参照を保ちつつ差し替え)
  memos.length = 0;
  newMemos.forEach(function(memo) {
    memos.push(memo);
  });

  // 状態が変わったら保存
  saveToStorage();

  // 新しい状態で画面を作り直す
  renderMemos();
}
JavaScript

「メモを追加」ボタンのイベント設定

ユーザーの操作とロジックをつなぎます。

// --- イベント設定:メモを追加ボタン ---
addMemoButton.addEventListener("click", function() {
  const title = memoTitleInput.value;
  const body = memoBodyInput.value;

  // エラーメッセージを一度クリア
  errorMessage.textContent = "";

  // タイトルと本文の両方が空ならエラー
  if (title.trim() === "" && body.trim() === "") {
    errorMessage.textContent = "タイトルか本文のどちらかは入力してください。";
    return;
  }

  // 状態を更新
  addMemo(title, body);

  // 入力欄を空にする
  memoTitleInput.value = "";
  memoBodyInput.value = "";

  // 新しい状態で画面を作り直す
  renderMemos();
});
JavaScript

ポイント:

  • 「何も入力されていないメモ」は追加しないようにチェック
  • エラーメッセージは <p id="errorMessage"> に表示

ページ読み込み時の初期処理

最後に、ページが読み込まれたときの流れを整えます。

// --- 初期処理 ---
// 1. 保存されている状態を読み込む
loadFromStorage();

// 2. 読み込んだ状態を画面に反映する
renderMemos();
JavaScript

1日目の完成コード(JavaScript全体)

HTML の <script> タグの中身として、 ここまでのコードをひとつにまとめるとこうなります。

<script>
  // --- アプリの状態(State) ---
  let memos = [];
  let nextMemoId = 1;

  const STORAGE_KEY = "my_memo_app_data";

  // --- DOM参照 ---
  const memoTitleInput = document.querySelector("#memoTitleInput");
  const memoBodyInput = document.querySelector("#memoBodyInput");
  const addMemoButton = document.querySelector("#addMemoButton");
  const errorMessage = document.querySelector("#errorMessage");
  const memoList = document.querySelector("#memoList");

  // --- ストレージ:保存 ---
  function saveToStorage() {
    const data = {
      memos: memos,
      nextMemoId: nextMemoId
    };

    const json = JSON.stringify(data);
    localStorage.setItem(STORAGE_KEY, json);
  }

  // --- ストレージ:読み込み ---
  function loadFromStorage() {
    const json = localStorage.getItem(STORAGE_KEY);

    if (json === null) {
      return;
    }

    try {
      const data = JSON.parse(json);

      if (Array.isArray(data.memos) && typeof data.nextMemoId === "number") {
        memos = data.memos;
        nextMemoId = data.nextMemoId;
      }
    } catch (error) {
      console.error("保存データの読み込みに失敗しました:", error);
    }
  }

  // --- 描画 ---
  function renderMemos() {
    memoList.innerHTML = "";

    memos.forEach(function(memo) {
      const memoDiv = document.createElement("div");
      memoDiv.classList.add("memo-item");
      memoDiv.dataset.id = String(memo.id);

      const titleDiv = document.createElement("div");
      titleDiv.classList.add("memo-item-title");
      titleDiv.textContent = memo.title;

      const bodyDiv = document.createElement("div");
      bodyDiv.classList.add("memo-item-body");
      bodyDiv.textContent = memo.body;

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

      deleteButton.addEventListener("click", function(event) {
        const memoElement = event.target.closest(".memo-item");
        const idText = memoElement.dataset.id;
        const id = Number(idText);

        deleteMemo(id);
      });

      memoDiv.append(titleDiv);
      memoDiv.append(bodyDiv);
      memoDiv.append(deleteButton);

      memoList.append(memoDiv);
    });
  }

  // --- 操作ロジック:メモ追加 ---
  function addMemo(title, body) {
    const newMemo = {
      id: nextMemoId,
      title: title,
      body: body,
      createdAt: Date.now()
    };

    nextMemoId = nextMemoId + 1;

    memos.push(newMemo);

    saveToStorage();
  }

  // --- 操作ロジック:メモ削除 ---
  function deleteMemo(id) {
    const newMemos = memos.filter(function(memo) {
      return memo.id !== id;
    });

    memos.length = 0;
    newMemos.forEach(function(memo) {
      memos.push(memo);
    });

    saveToStorage();
    renderMemos();
  }

  // --- イベント設定:メモ追加ボタン ---
  addMemoButton.addEventListener("click", function() {
    const title = memoTitleInput.value;
    const body = memoBodyInput.value;

    errorMessage.textContent = "";

    if (title.trim() === "" && body.trim() === "") {
      errorMessage.textContent = "タイトルか本文のどちらかは入力してください。";
      return;
    }

    addMemo(title, body);

    memoTitleInput.value = "";
    memoBodyInput.value = "";

    renderMemos();
  });

  // --- 初期処理 ---
  loadFromStorage();
  renderMemos();
</script>

1日目で本当に掴んでほしいこと

  • DOM(画面の要素)と配列(状態)を結びつけると、ブラウザ上で「自分のデータ」を扱えるようになる
  • querySelector() / addEventListener() / createElement() / append() / remove() / classList / dataset は、 メモ帳のようなアプリでも自然に使える道具になっている
  • localStorageJSON.stringify() / JSON.parse() を組み合わせると、 ページを閉じても消えない「自分専用のメモ帳」が作れる

次のステップでは、

  • メモの編集機能
  • 検索機能
  • タグや色分け

などに広げていくことで、 「自分の生活に本当に役立つメモ帳アプリ」に育てていけます。

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