プログラミングの理解が遅すぎる初心者がJavaScript、Node.jsで投票型掲示板を作ろうとしてます

トップページでは記事の順番がごちゃごちゃなので、記事もくじをご覧いただければと思います。

投票ページの作成⑬候補者追加の動作を作成⑨配列の整列Ⅵ

ついに三部作が6作目まで来てしまいました。

4作目のときに謝ったので、もう謝罪はしません。

前回の記事はこちら。

html-css-javascript.hatenadiary.com

HTMLから送られてくる数値は、すべて文字列に変換されてしまうのが問題だというところまででした。

だったらこちらも文字列を数値に変換してやればいいことになります。

const id = Number(target.dataset.id);

これでidは無事に数値に戻されたので、findで見つけることができるようになりました。

余計な手間をかけさせやがって。

さらに、ここでは「絶対に上書きをしようがない」というIDを付けてしまうのが良いと判断しました。

もし何らかの間違いでIDが上書きされてしまったら、せっかく付与したIDがごっちゃになりますので。

で、どうするかというとDate.now()を使います。

Date.now()とは

見ての通り、オブジェクトとプロパティです。

で、Dateがどういうオブジェクトなのかというと、「今」を表します。

「今」っていろんな表し方がありますよね。

たとえば今、これを書いてるのは2026年2月26日午前2時36分です(なんちゅう時間や)。

で、「何年」「何月」「何時」「何分」などをプロパティで指定して取得するわけです。

まあこれらはインスタンスメソッドなんですけど。

こういう感じで作るやつです。

const d = new Date();
d.getFullYear()   // 年を取得
d.getMonth()      // 月を取得(0〜11)
d.getDate()       // 日にちを取得

詳しくは(私でもわかるように)以下の記事に書いてあるのでご参照ください。

html-css-javascript.hatenadiary.com

普通の教材でクラスとかインスタンスとかコンストラクタとかがわからない人は、これでわかるようになります(と思います。高確率で。)

閑話休題。

ではnow()とはどういうプロパティなのかというと、1970年1月1日 00:00:00 UTC(時差に依存しない世界共通時間)からミリ秒で数えた数値です。

1970年からずーっと数えてるわけです。

表示は秒までですけど、中身ではミリ秒で数えてますので、取得できます。

今、大体約17億7千万秒くらいです。

たしか『Dr.STONE』で千空が石化解除まで数え続けた秒数は、1173億5489万3870秒でした。

JavaScriptではこれからどれくらいの秒数を数えられるのか、この先カンストするんじゃないかという心配は全く無用で「9,007,199,254,740,992(9京)秒」、だいたい285万年以上数えられます。

閑話休題っていったそばから話が外れていってますが、何をいいたいのかというと、ミリ秒をIDに使うということです。

何月何日何時何分何秒何ミリ秒に付与したID、という具合にです。

これなら絶対にIDがかぶったりすることはありませんし、IDを識別できます。

というわけでこれを使っていきます。

Date.now()を使う

まあこれはもうそのまんまです。

voteBtn.dataset.id=Date.now();

終了!!

なんですけど、やっぱり一応先のことは考えておきましょう。

おそらく、というかほぼ確実に「こいつのIDなんだっけ?」って時が出てくるはずです。

なので、登録者のオブジェクトのプロパティに追加しておくことにします。

というか、candidate.idとして使ってるんだから必須でしたね。

   const newCandidate = {
      name,
      age,
      vote: 0,
      id:Date.now(),
    };

で、idがこれで定義されたので、上のやつに放り込みます。

voteBtn.dataset.id=id;

これでもしIDの中身が知りたいとしたら、

console.log(newCandidate.id);

とでも書けばよくなりました。

ただですね。

さっきも上述のとおり、このIDってミリ秒で示されるんです。

17億秒とかを超える数値をIDでとして私が認識するのは不可能に近いです。

なのでこれをわかりやすく変換することにします。

で、どうするかというと36進数にします。

もう0から9という普通の数字はもちろん、アルファベットのaからzまでも全部使ってしまいましょう。

これで17億秒は「s44we8」というなんともIDらしい表記になりました。

これはもうデバッグ用といわず、普通にオブジェクトに追加しておきましょう。

数字を36進数にする方法はtoString(36)を使います。

変換したあとのIDはshortIdとしましょう。

したがって以下のようになります。

    const newCandidate = {
      name,
      age,
      vote: 0,
   id: Date.now(),                 // 内部処理用ID
   shortId: Date.now().toString(36) // 表示用ID
    };

これ、ちゃんと右辺もidに直しておかないとだめですね。

voteBtn.dataset.id = candidate.id;

ちなみに右辺をDate.now()にはできません。

時間は進んでいますので、レンダリングするたびにIDが変わってしまいますので。

おわりに

これで一応、候補者追加の動作の作成は終わったと思います・・・多分。

候補者の追加、投票ボタンの作成、ボタンを押すことで投票数を増やす、投票数順に候補者を並べていく。

ここまでができました。

ではまとめとしてここまでのHTMLとJavaScriptを記しておきます。

最初にいきなりコメントアウトがありますが、軌跡ということでこれは残しておきます。

//console.log("JS読み込み確認");
//localStorage.removeItem("candidates");
//console.log("ローカルストレージの candidates を削除しました");
//console.log("現在の candidates:", candidates);

let candidates = JSON.parse(localStorage.getItem("candidates")) || [];
const candidateList = document.getElementById("candidateList");
render();
console.log("現在の candidates:", candidates);
document
  .getElementById("addCandidateForm")
  .addEventListener("submit", function (event) {
    event.preventDefault();

    const inputName = document.getElementById("candidateName");
    const inputAge = document.getElementById("candidateAge");

    const name = inputName.value.trim();
    const age = inputAge.value.trim();

    inputName.value = "";
    inputAge.value = "";

    const newCandidate = {
      name,
      age,
      vote: 0,
      id: Date.now(), // 内部処理用ID
      shortId: Date.now().toString(36), // 表示用ID
    };
    candidates.push(newCandidate);
    localStorage.setItem("candidates", JSON.stringify(candidates));
    render();
  });

candidateList.addEventListener("click", function (event) {
  const target = event.target;
  if (target.tagName === "BUTTON") {
    //ボタンを押したら処理を実行
    const id = Number(target.dataset.id);

    console.log("ボタンの dataset.id:", target.dataset.id, typeof target.dataset.id);
    const candidate = candidates.find((c) => c.id === id);
    console.log("変更前:", candidate);
    if (candidate) {
      candidate.vote += 1;
      console.log("変更後:", candidate);
      localStorage.setItem("candidates", JSON.stringify(candidates));
      render();
    }
  }
});

function render() {
  candidateList.innerHTML = "";

  const sortedCandidates = [...candidates].sort((a, b) => b.vote - a.vote);

  sortedCandidates.forEach((candidate, index) => {
    const listItem = document.createElement("li");

    const infoSpan = document.createElement("span");
    infoSpan.textContent = `名前: ${candidate.name}, 年齢: ${candidate.age}, 投票数: ${candidate.vote}`;

    const voteBtn = document.createElement("button");
    voteBtn.textContent = "投票";

    // これで各候補者固有のIDをセット;
    voteBtn.dataset.id = candidate.id;

    listItem.appendChild(infoSpan);
    listItem.appendChild(voteBtn);
    candidateList.appendChild(listItem);
  });
}

HTMLは

<!doctype html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />

    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>投票ボタン付き 横棒グラフ</title>
    <link rel="stylesheet" href="style.css" />
    <link
      rel="stylesheet"
      href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&icon_names=menu"
    />
    <script src="js/script.js" defer></script>
    <script src="js/candidateAdd.js" defer></script>
  </head>
  <body>
    <header>
      <button id="menuBtn">
        <span class="materialSymbolsOutlined">menu</span>
      </button>
      <nav>
        <ul>
          <li><a href="#">ホーム</a></li>
          <li><a href="#">投票ページ</a></li>
          <li><a href="#">コメント</a></li>
          <li><a href="#">お問い合わせ</a></li>
        </ul>
      </nav>
    </header>

    <form id="addCandidateForm" action="#" method="post">
      <label for="candidateName">候補者名:</label>
      <input
        type="text"
        id="candidateName"
        name="candidateName"
        placeholder="候補者"
        required
      />
      <label for="candidateAge">候補者年齢:</label>
      <input
        type="number"
        id="candidateAge"
        name="candidateAge"
        min="0"
        max="150"
        placeholder="年齢"
        required
      />
      <button id="addBtn" type="submit">追加</button>
    </form>

    <ul id="candidateList"></ul>
  </body>
</html>

次回は・・・何をしようかな。

今回までで作ったのは、候補者を追加することができる管理人用のページなので、今度はユーザー用のページを作成していこうと思います。

一人あたり何票まで持てるかも指定したいと思います。