コース目次 / 第8章

ミニアプリ

サイトの中に「行きたい場所メモ」を作ります。書いた内容はリロードしても残ります。

目安 約70分この章のゴール: 入力して追加でき、リロードしても消えず、削除もできるメモ機能が付く

コース1でいちばん長い章です。 エラーもいちばん出ます。ぜんぶ理解できなくて大丈夫です。写して、動かして、自分用に変えられれば合格です。時間がかかってもかまわないので、一気にやらず、途中で休んでください。

今日のゴール

サイトに「行きたい場所メモ」のコーナーができます。入力欄に文字を打って「追加」を押すと下に増え、ブラウザを閉じても消えません。いらなくなったら「削除」で消せます。

見るだけのサイトから、使えるアプリになる章です。

ミニ解説その1: 配列

配列 は「順番の付いた箱のリスト」です。メモを何件でも入れておけます。

let memos = ['直島', '尾道'];

memos.push('金沢');   // 末尾に足す → ['直島', '尾道', '金沢']
memos.splice(0, 1);   // 0番目から1個抜く → ['尾道', '金沢']

数え方は 0から です。1番目ではなく0番目、と覚えてください。プログラミングではずっとこの数え方です。

ミニ解説その2: forEach

forEach(フォーイーチ) は「1個ずつ順番に取り出して、同じことをする」命令です。

memos.forEach((memo, index) => {
  // memo  … 取り出した中身('直島' など)
  // index … それが何番目か(0, 1, 2 …)
});

メモが3件なら3回、10件なら10回、中身が実行されます。件数を数える必要はありません。

ミニ解説その3: JSでHTMLを作る

いままでは HTML ファイルに手で書いていましたが、JS からも部品を作れます。

書き方 意味
document.createElement('li') 新しい <li> を作る
.textContent = '直島' その中の文字を決める
.appendChild(部品) 別の部品の中に入れる

作っただけでは画面に出ません。appendChild でどこかに入れて、はじめて表示されます。

ミニ解説その4: localStorage

localStorage(ローカルストレージ) は、ブラウザの中にある小さなメモ帳です。ここに書いたものは、ブラウザを閉じても残ります。

ただし1つクセがあります。文字しか入れられません。 配列をそのまま入れられないので、変換します。

JSON.stringify(['直島', '尾道'])   // 配列 → 文字 '["直島","尾道"]'
JSON.parse('["直島","尾道"]')      // 文字 → 配列 ['直島', '尾道']

しまうときに stringify、出すときに parse この行き帰りだけ覚えれば十分です。

書き写すその1: HTML にメモ欄を足す

まず入れ物を作ります。<input> が入力欄、<button> が追加ボタン、<ul class="memo-list">からっぽの置き場です。ここに JS がメモを入れていきます。

index.html+1 / -0
         <a href="#likes">好きなもの</a>       <a href="#hobby">趣味</a>+      <a href="#memo">メモ</a>       <a href="#links">リンク</a>       <button class="theme-button" type="button">ダーク</button>
index.html+9 / -0
       </section>  +    <section id="memo">+      <h2>行きたい場所メモ</h2>+      <div class="memo-form">+        <input class="memo-input" type="text" placeholder="行きたい場所" />+        <button class="memo-button" type="button">追加</button>+      </div>+      <ul class="memo-list"></ul>+    </section>+      <section id="links">       <h2>リンク</h2>

書き写すその2: CSS で見た目を整える

メディアクエリより前に足します。

style.css+57 / -0
 }  +.memo-form {+display: flex;+gap: 8px;+}+ +.memo-input {+flex: 1;+border: 1px solid #d8d3c8;+border-radius: 8px;+padding: 8px 12px;+font-family: inherit;+font-size: 15px;+}+ +.memo-button {+background-color: #4a6b57;+color: #fbfaf7;+border: none;+border-radius: 8px;+padding: 8px 20px;+font-family: inherit;+font-size: 15px;+cursor: pointer;+}+ +.memo-list {+list-style: none;+margin: 16px 0 0;+padding: 0;+}+ +.memo-list li {+display: flex;+justify-content: space-between;+align-items: center;+gap: 12px;+background-color: #ffffff;+border-radius: 8px;+box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);+padding: 10px 16px;+margin-bottom: 8px;+}+ +.memo-list button {+background-color: transparent;+border: none;+color: #b4703a;+font-family: inherit;+font-size: 13px;+cursor: pointer;+}+ +body.dark .memo-list li {+background-color: #2e3238;+box-shadow: none;+}+  @media (max-width: 600px) { .site-header {

保存してリロードすると、入力欄と追加ボタンが出ます。まだ押しても何も起きません。 次でJSを書きます。

書き写すその3: script.js に4つの部品を足す

script.js の、いままで書いたダークモードの下に足していきます。4つの部品に分かれています。1つずつ、順番に写してください。

部品1: 使うものを取ってくる

入力欄・ボタン・置き場の3つと、保存済みのメモを取り出します。

// ここから「行きたい場所メモ」
const memoInput = document.querySelector('.memo-input');
const memoButton = document.querySelector('.memo-button');
const memoList = document.querySelector('.memo-list');

let memos = JSON.parse(localStorage.getItem('haruto-memos') || '[]');

|| '[]' は「まだ何も保存されていなければ、からっぽの配列として扱う」という保険です。これがないと、初めて開いた人のページでエラーが出て止まります。

部品2: 保存する係

function saveMemos() {
  localStorage.setItem('haruto-memos', JSON.stringify(memos));
}

function(ファンクション)は「やることに名前を付けてまとめておく」書き方です。名前を付けておけば、あとから何度でも呼び出せます。

部品3: 画面に並べる係

いちばん長いところです。やっていることは「いったん全部消して、配列の中身を1件ずつ並べ直す」だけです。

function showMemos() {
  memoList.textContent = '';

  memos.forEach((memo, index) => {
    const item = document.createElement('li');

    const text = document.createElement('span');
    text.textContent = memo;

    const deleteButton = document.createElement('button');
    deleteButton.textContent = '削除';
    deleteButton.addEventListener('click', () => {
      memos.splice(index, 1);
      saveMemos();
      showMemos();
    });

    item.appendChild(text);
    item.appendChild(deleteButton);
    memoList.appendChild(item);
  });
}

削除ボタンの中で showMemos() を呼んでいるのが少し不思議に見えるかもしれません。「配列から消したあと、もう一度並べ直す」という意味です。消すたびに全部並べ直すので、番号のずれを自分で考えなくて済みます。

部品4: 追加ボタンを動かす

memoButton.addEventListener('click', () => {
  if (memoInput.value === '') {
    return;
  }

  memos.push(memoInput.value);
  memoInput.value = '';
  saveMemos();
  showMemos();
});

showMemos();

if(イフ)は「もし〜なら」です。ここでは「入力欄がからっぽなら return(何もせず終わる)」としています。これがないと、空のメモがいくらでも増えてしまいます。

いちばん下の showMemos(); は、ページを開いた瞬間に1回だけ呼ぶためのものです。これがあるおかげで、前回書いたメモがリロード後も表示されます。

書き終わった script.js の全体

script.js
// ヘッダーのボタンを押したら、body に dark クラスを付けたり外したりする
const themeButton = document.querySelector('.theme-button');

themeButton.addEventListener('click', () => {
  document.body.classList.toggle('dark');
});

// ここから「行きたい場所メモ」
const memoInput = document.querySelector('.memo-input');
const memoButton = document.querySelector('.memo-button');
const memoList = document.querySelector('.memo-list');

let memos = JSON.parse(localStorage.getItem('haruto-memos') || '[]');

function saveMemos() {
  localStorage.setItem('haruto-memos', JSON.stringify(memos));
}

function showMemos() {
  memoList.textContent = '';

  memos.forEach((memo, index) => {
    const item = document.createElement('li');

    const text = document.createElement('span');
    text.textContent = memo;

    const deleteButton = document.createElement('button');
    deleteButton.textContent = '削除';
    deleteButton.addEventListener('click', () => {
      memos.splice(index, 1);
      saveMemos();
      showMemos();
    });

    item.appendChild(text);
    item.appendChild(deleteButton);
    memoList.appendChild(item);
  });
}

memoButton.addEventListener('click', () => {
  if (memoInput.value === '') {
    return;
  }

  memos.push(memoInput.value);
  memoInput.value = '';
  saveMemos();
  showMemos();
});

showMemos();

動かして確かめる

保存してリロードし、次の4つをこの順番で試してください。どこで失敗したかが分かれば、直す場所も絞れます。

  1. 入力欄に「直島」と入れて 追加 を押す → 下に「直島」が出る
  2. もう1件足してから リロード する → 2件とも残っている
  3. 1件目の 削除 を押す → その1件だけ消える
  4. 何も入力せずに追加 を押す → 何も起きない(増えない)

4つとも通れば完成です。

あなたの番

  • メモのテーマを自分のものに変えてください。 見出しの「行きたい場所メモ」と、入力欄の placeholder="行きたい場所" を書き換えます。「読んだ本メモ」「買うものリスト」「やることリスト」など、自分が使いたいものに。
  • 保存する名前も変えておきましょう。 script.js'haruto-memos' を、'my-books' のように自分用の名前に。2か所あるので両方変えてください(片方だけだと保存と読み込みがかみ合いません)。
  • 変えたら再公開して、スマホからも使ってみてください。
おまけ: おみくじボタンを作る(余裕がある人だけ)

配列と「ランダムに選ぶ」を組み合わせると、おみくじが作れます。HTML に <button class="omikuji">ひく</button><p class="omikuji-result"></p> を足して、script.js の最後に次を足します。

const omikujiButton = document.querySelector('.omikuji');
const omikujiResult = document.querySelector('.omikuji-result');
const kuji = ['大吉', '中吉', '小吉', '末吉'];

omikujiButton.addEventListener('click', () => {
  const number = Math.floor(Math.random() * kuji.length);
  omikujiResult.textContent = kuji[number];
});

Math.random() が 0以上1未満の数をでたらめに作り、Math.floor() が小数点以下を切り捨てます。かけ算する相手を kuji.length(配列の件数)にしておくと、件数を増やしてもそのまま動きます。

うまくいかないときは

追加ボタンを押しても何も起きない

開発者ツールの Console を開いて、赤い文字が出ていないか見てください。Cannot read properties of null が出ていたら、HTML の名札と JS の名札の綴りが違います.memo-input .memo-button .memo-list の3つを、HTML 側と1文字ずつ見比べてください。

リロードすると消えてしまう

2か所を確認します。

  1. script.js のいちばん下に showMemos(); がありますか。これがないと、保存はされていても画面に出ません。
  2. 追加ボタンの中で saveMemos(); を呼んでいますか。これがないと、画面には出ても保存されていません。
削除すると別のものが消える/全部消える

memos.splice(index, 1);1 は「1個だけ抜く」という意味です。ここが違う数字になっていないか確認してください。また spliceslice(cが1つ)は別物です。

もう疲れました

それが普通です。 この章はコース1でいちばん長く、いちばんエラーが出ます。このページの下のZIPをダウンロードして、script.jsindex.htmlstyle.css を丸ごと置き換えてしまってください。動いている状態から読み返すほうが、はるかによく分かります。

こうなっていればOK

うまくいかないときは、この章の完成ファイル一式(ZIP)をダウンロードして続きから進めても大丈夫です。

この章はまだ完了していません。