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

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

JavaScriptの常識⑤event.target

(今回は怪獣八号ネタです。)

本編を進めていくと、やっぱり「ここ、理解が足りてないな」と思うところがあります。

今回はevent.targetです。

あっさり行ける人もいるんでしょうけど、タイトルの通り私は理解が遅いのでここで一旦備忘録の記事を挿入することにします。

私はいちいち「ターゲット」と書かないといけない理由が分かっていませんでした。

例えば、HTMLのページにAとBとCのボタンがあったとします。

Bを押したら、「Bを押したんだからターゲットなんかつけなくてもBに決まってるだろ。なんでそこからさらにターゲットなんて指定しないといけないんだ」と、そういう認識でした。

自分の疑問を言葉にするのがちょっと難しかったのですが、いろいろ考えてみたところ、自分が分からなかったのはこういう感じなんだろうと思います。

なのでここできっちり片を付けようと思います。

event.target

ChatGPT先生に教えてもらうとevent.targetを付ける理由は単純でした。

まあそれでも腑に落ちるまでには時間がかかりましたが。

おそらくプログラミング学校とかで質問したらぶち切れされるくらい質問をしまくりました。

targetを付ける理由は、先ほどAとBとCのボタンのうち、Bを押したらBとわかるだろと思っていましたが、そうじゃありませんでした。

例えば以下のような場合。

HTML

<div id="candidateList">
  <button>日比野カフカ</button>
  <button>市川レノ</button>
  <button>亜白ミナ</button>
  <button>保科宗四郎</button>
</div>

<p id="log"></p>

JavaScript

const candidateList = document.getElementById('candidateList');
const log = document.getElementById('log');

candidateList.addEventListener('click', (event) => {
  // 押された「要素そのもの」
  const clickedElement = event.target; //targetが出てくるところ

  // ボタン以外は無視
  if (clickedElement.tagName !== 'BUTTON') return;

  log.textContent = `${clickedElement.textContent} を押しました`;
});

これで仮にtargetを抜いたとします。

そうするとaddEventListenerclickで、クリックしたことは伝わるのですが、いったいどこをクリックしたのかまでは伝わらないんです。

これが最初の骨組みであるHTMLのままであれば、順番とかが変わることがないので問題がないこともあります。

しかし基本的にJavaScriptではHTMLをDOM操作します。

つまりHTMLをいじくりまくるわけです。

そうなると、event.targetを使わなければ、いったいどのボタンを指しているのかがわからなくなるんです。

とくにこの掲示板では配列を作り、その配列を常に送信してDOM操作をしますので、targetは必須ということになります。

どうやってtargetの先を決めているのか

さて、私が抱いた次の疑問は、ボタンが「日比野カフカ」と「市川レノ」と「亜白ミナ」と「保科宗四郎」があった場合、どうやってtargetの先を決めているのか、ということでした。

だって、どのボタンを押してもJavaScriptが認識するのは「クリックされた」ということだけなんですし。

それで同じようにevent.targetと書いたところで「どこをターゲットにしてるんだよ」と思いました。

その答えはイベントオブジェクトにありました。

さっきからevent.targetと書いてるのの左側のeventのところです。

ボタンをクリックすると同時に、イベントオブジェクトが作成されます。

このイベントオブジェクトには、DOM操作したHTMLからの情報がブラウザから送信されます。

つまり、たとえば保科宗四郎のボタンを押したのであれば、「保科宗四郎のボタンを押した場合のイベントオブジェクト」が作成されるんです。

従って、イベントオブジェクトの中に「保科宗四郎を押した」という情報が含まれています。

具体的には以下のように。

event = {
  type: "click",

  target: <button>保科宗四郎</button>, //event.target

  currentTarget: <div id="candidateList">...</div>,

  timeStamp: 123456.78,

  bubbles: true,

  cancelable: true,

  defaultPrevented: false
}

見てのとおり、プロパティの中にtargetが入ってます。

つまり保科宗四郎のボタンを押した場合、イベントオブジェクトのプロパティであるevent.targetは「保科宗四郎」というボタンそのものということになります。

で、ボタンそのものですから<button>と同じ意味になり、event.targetはDOM要素ということになります。

しつこいですが、event.target<button>ですので、event.target.tagNameまたはclickedElement.tagNameBUTTONになります。

なぜか大文字になりますが、これは仕様だそうです。

おわりに

今回はevent.targetについてでした。

eventと書かずeだけのときもありますが、イベントオブジェクトであることはすぐにわかるので問題はないかと思います。

いずれにしてもこれでtargetの役割と、なぜそれが必要なのかが理解できたと思います。

これでやっと本編に戻れます。

投票ページの作成⑥候補者追加の動作を作成②ローカルストレージへの保存とロード

前回までで配列を作りました。

一応コードをここに書いておきます。

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,[email protected],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>

CSS

@charset "UTF-8";
body {
  font-size: 1rem;
  font-family: sans-serif;
  margin: 0;
  padding: 0;
}

header {
  position: relative;
  background-color: #fff;
  padding: 1rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

nav {
  position: absolute; /*ヘッダーを基準に書いていく*/
  display: none; /*最初はメニューが見えないようにしておく*/
  top: 100%; /*ヘッダーの下に配置*/
  right: 0; /* 右端はヘッダーに合わせる*/
}
nav.show {
  display: block; /* show クラスがついたら表示*/
}
nav ul {
  list-style: none; /* マーカーを消す*/
  padding: 0; /* ul の内側の余白を消す*/
}
nav ul li {
  margin-bottom: 0.5rem;
}
nav ul li a { /* リンクの表示の扱い*/
  display: block; /* li 全体をクリック可能に*/
  padding: 0.5rem 1rem; /* 内側の余白 */
  text-decoration: none; /* リンクの下線を消す*/
  color: #333; /* ダークグレー*/
}
nav ul li a:hover { /* ホバーしたときの扱い*/
  background-color: #f0f0f0; /* ホバー時の色 */
}

.voteMain {
  width: 100%;
  padding: 1rem;
  box-sizing: border-box; /*paddingなども含めて幅を計算する*/
}

.voteTable {
  width: 100%;
  border-collapse: collapse; /*セルの境界線を重ねる*/
  margin-bottom: 1rem;
  table-layout: fixed; /*項目の横幅を固定する*/
}
.voteTable th, .voteTable td {
  border: 1px solid #ccc; /*薄いグレーの境界線*/
  padding: 0.5rem;
  text-align: left;
}
.voteTable th {
  background-color: #f9f9f9; /*薄いグレーの背景色*/
}
.voteTable th:nth-child(1) {
  width: 10%; /*順位の幅*/
}
.voteTable th:nth-child(2) {
  width: 45%; /*候補者の幅*/
}
.voteTable th:nth-child(3) {
  width: 20%; /*投票数の幅*/
}
.voteTable th:nth-child(4) {
  width: 25%; /*投票ボタンの幅*/
}

#addCandidateForm {
  display: flex;
  flex-direction: column;
  width: 300px;
  margin: 2rem auto;
  gap: 1rem;
}/*# sourceMappingURL=style.css.map */

JavaScript

const candidates=[];

document.getElementById('addCandidateForm').addEventListener('submit', function(event) {
    event.preventDefault();

    const name = document.getElementById('candidateName').value.trim();
    const age = document.getElementById('candidateAge').value.trim();  
    const candidateList = document.getElementById('candidateList');
    if (!name || !age) {
        alert('input the name and age of candidates');
        return;
    }   
    console.log(name, age);

const newCandidate = {
    name,
    age,
    vote: 0,
};  
candidates.push(newCandidate);

const listItem = document.createElement('li');
listItem.textContent = `名前: ${newCandidate.name}, 年齢: ${newCandidate.age}, 投票数: ${newCandidate.vote}`;
candidateList.appendChild(listItem);
});

前回も言いましたが、別のHTMLのページから配列にアクセスするには、ブラウザのローカルストレージに保存する必要がありますが、そのままの形では保存することができません。

で、保存するためには配列をJSON形式の文字列にする必要があります。

あと、更に重要な問題もありまして。

ページをリロード瞬間にもメモリの変数は全部消去されてしまうんです。

なので、もともとあった配列をそのまま処理するんじゃなく、いったんローカルストレージに保存して、そこから取り出してそれをいじる必要があります。

ではまず配列をJSON文字列にする方法から。

配列をJSON文字列に変換する

配列を文字列に変換するメソッドはこれです。

JSON.stringify(配列やオブジェクト)
// → JSON形式の「文字列」に変換

文字列をローカルストレージに保存する

で、文字列をローカルストレージに保存するメソッドが以下の通り。

localStorage.setItem('candidates', JSON.stringify(candidates));

第二引数は上でcandidatesを文字列化したものだということは明らかですが、第一引数はキーです。

つまりこの二つはキーと値ということになります。

さて、ローカルストレージに保存したので、ここから文字列から配列に戻します。

ローカルストレージから配列に戻す

戻す方法は以下のようにします。

localStorage.getItem('candidates');

これでキーを指定して文字列としてローカルストレージから必要な値を取得しました。

値は文字列なので、これを配列に戻します。

これにはparseを使います。

JSON.parse(localStorage.getItem('candidates'));

これで、ローカルストレージに文字列として保存されていた値は配列に戻りました。

さて、これを配列として変数にしたいのですが、そのまま最初に定義したcandidatesにはできません。

なぜかというとこの変数はconstで定義しているから。

なのに中身を入れ替えようとしたらその時点でエラーが発生します。

・・・。

だったらconstを使ったらあかんやん・・・。

今回みたいに一つの変数を上書きしていくことが前提ならconstじゃなくletを使わないといけませんね。

なのでcandidates

let candidates=[];

と書き直しておきます。

さて、前提を変えて書いていきます。

candidates = JSON.parse(localStorage.getItem('candidates')) || [];

||で区切られていますが、コレがなかなか便利で、左側がtruthyなら左側が採用、falsyなら右側が採用されます。

・・・いや、なんて?>自分

ChatGPT先生に言われたことをさも自分が説明してるかのように言ってますけど、一回読んだだけでは意味がわかりませんのでちゃんと自分でもわかるように整理します。

truthyとかfalsyは、それぞれ「truth(真)っぽい」「false(偽)っぽい」という意味です。

もっと簡単にいうなら、「中になにか入ってる」「0じゃない」「使えそう」というときはtruthy、「何も入ってない」「0」「null」ならfalsyになります。

ちなみに[]は中が空っぽなのでfalsyにも見えますが、中身がないだけでオブジェクトですので「ある」、つまりtruthyになります。

で、なので左側のparseが行われ、その結果が真(っぽい)ときは左側がそのまま採用されてcandidatesが上書きされます。

他方、偽(っぽい)とき、具体的には初期状態でローカルストレージに何も入っていなくてnull状態だった場合は右側の[ ]が採用されることになります。

まあこれは初期状態のときだけですけど。

で。

ということは、このままcandidatesを定義してしまっても構いませんね。

let candidates = JSON.parse(localStorage.getItem('candidates')) || [];

初期状態でcandidatesがnullだったら右側が採用されて、結果的に配列の箱が作られることになります。

なのでこのようにまとめてしまいましょう。

おわりに

今回は配列をJSON文字列に変換してローカルストレージに保存、ローカルストレージからロードして配列に戻す、ということをやりました。

次はやっと配列を操作するところになりますね。

候補者が追加されたらpush、順位に沿ってsort、というように。

本当は今回でそこまで行けると思ったのですが、予定通りにはならないものです。

投票ページの作成⑤候補者追加の動作を作成

前回は投票ページの候補者を追加するためのフォームを作りました。

次はフォームに名前と年齢を入力して追加ボタンを押すことで、投票ページの候補として追加されるようにします。

この辺についても以前に記事にしていますので、それを参照してさらっと終わらせたいと思います。

html-css-javascript.hatenadiary.com

前回作成したフォームは以下。

  <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 type="submit">追加</button>
    </form>

まずはJavaScriptで、追加ボタンをクリックしたら候補者名(candidateName)と候補者の年齢(candidateAge)を取得するようにします。

で。

重要なことを忘れていました。

候補者とか投票数とかを示す場所がありませんでした。

これを記しておかないと、JavaScriptで操作することができません。

なので以下を付け足します。

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

これでJavaScriptのほうでIDで取得することができるようになりました。

名前と年齢の取得

JavaScriptのファイル名は「candidateAdd.js」とでもしておきましょう。

要素の取得

document.getElementById('addCandidateForm').addEventListener('submit', function(event) {
    event.preventDefault();

     const name = document.getElementById('candidateName').value.trim();
    const age = document.getElementById('candidateAge').value.trim(); 
 const candidateList = document.getElementById('candidateList');

    if (!name || !age) {
        alert('候補者名と年齢を入力してください。');
        return;
    }   
  console.log(name, age);
});

送信ボタンを押してフォームのイベント発生ですから、IDはaddCandidateForm、トリガーはsubmitですね。

querySelector('form')でもいいんですけど、今後フォームが2つ以上出てくるときとかもあるかもしれないので、フォームについてはIDタグをつけるようにしていきます。

名前、年齢のどちらかが空欄の場合はアラートが出るようにしておきました。

htmlのほうでもrequiredを付けておきましたが、念のため。

trim()もつけておきましたので、余計なスペースなどもなくなりました。

あとこれも念の為、nameとageに正しい値が代入されているか確認するためにconsole.log(name, age);も入れておきました。

次は取得した要素を配列に入れていきます。

配列を作成

普通の掲示板の場合は、書き込んだ内容を次々に足していくだけでしたのでappendChild()だけで済んだのですが、今回は投票数に応じて並べ方なども変えていく必要があります。

なので配列を作ります。

これで条件による並べ方などは制御することができます。

また、今回はadd.htmlから送信したデータを、vote.htmlという別のHTMLファイルで反映させることも必要になります。

しかし配列を作っただけだと、add.htmlから開くJavaScriptとvote.htmlから開くJavaScriptは別物ですので、配列は渡すことはできません。

じゃあ配列はどこかに保存して、どちらのHTMLファイルからもアクセスできるようにするしかありません。

「どこかってどこよ?」というと、「ブラウザ」です。

ブラウザに保存するところがあります。

ストレージの中にブラウザに割いている部分があるという理解でいいです。

localStorage

ここに保存できます。

しかしこのlocalStorage、なんと配列はそのままでは保存できません

できるのは文字列のみ。

なら配列も文字列に置き換えるしかありません。

なのでやることは以下の通り。

配列を文字列に置き換えてlocalStorageに保存→必要なときにlocalStorageから取り出して文字列を配列に戻す。

めんどくせえ。

では配列を文字列にするのですが、この文字列はJSON形式です。

つまりJSONの書き方に従います。

JSONの形式についてはこちら。

html-css-javascript.hatenadiary.com

では配列を作ります。

const candidates=[];

(実際はここで書くのではなく、絶対に用意すべき箱ですのでイベントリスナーより前(一番上)に書きます。

※あと、コレは間違ってました。

次の記事で説明してます。

物理的にも、まず箱を用意してから中身を作るのと同じです。)

次に配列の要素の一つを作ります。

要素はオブジェクトになりますので、以下のような感じになります。

const newCandidate = {
    name: name,
    age: age,
    vote: 0,
};

ですね。

でもここでちょっと裏技というか省略できるところがあります。

キーと値が同じ場合、いちいち:で挟む必要がなく、その名前一つを書くだけで済みます。

name:name;

だったら

name

だけで済むことになります。

なので上の配列は

const newCandidate = {
    name,
    age,
    vote: 0,
};

にすることができます。

voteについては初期値が0なので省略できません。

んでから配列にフォームから送られてきた要素を次々足していくことになりますので、ここは配列の最後に要素を足すpushをつかいます。

それから配列の中身を取得してから、HTMLの

    に要素を入れるためにcreateElementを使います。

    listItemの中のtextにフォームで送信した内容を反映させて、これを配列の一番後ろに追加します。

    この辺、詳しくは以前のブログに記載しているので、そちらをご覧ください。

    html-css-javascript.hatenadiary.com

    candidates.push(newCandidate);
    
    const listItem = document.createElement('li');
    listItem.textContent = `名前: ${newCandidate.name}, 年齢: ${newCandidate.age}, 投票数: ${newCandidate.vote}`;
    candidateList.appendChild(listItem);
    

    ここまでをまとめます。

    const candidates=[];
    
    document.getElementById('addCandidateForm').addEventListener('submit', function(event) {
        event.preventDefault();
    
        const name = document.getElementById('candidateName').value.trim();
        const age = document.getElementById('candidateAge').value.trim();  
        const candidateList = document.getElementById('candidateList');
        if (!name || !age) {
            alert('input the name and age of candidates');
            return;
        }   
        console.log(name, age);
    
    const newCandidate = {
        name,
        age,
        vote: 0,
    };  
    candidates.push(newCandidate);
    
    const listItem = document.createElement('li');
    listItem.textContent = `名前: ${newCandidate.name}, 年齢: ${newCandidate.age}, 投票数: ${newCandidate.vote}`;
    candidateList.appendChild(listItem);
    });
    

    終わりに

    ここまでで配列とその中身を作りました。

    ほとんど以前に作った「JavaScript 掲示板を作る③書き込みを反映させる」と同じ内容ではありますが。

    次回からが重要で、今回つくった配列をローカルストレージに保存することで、別のHTMLファイルから読み込めるようにします。

投票ページの作成④候補者追加ページの作成

これまで、トップページ、投票ページを作成してきました。

で、投票ページには投票ボタンと投票結果を表示することにしたのですが、投票の候補者を追加できるようにする必要もあります。

今回はそのためのページを作成していくことにします。

候補者を追加するわけですので、普通に「add.html」というファイル名にでもしておきましょう。

送信する内容は、名前と年齢くらいでいいですかね。

投稿フォームにつきましては以前作成しましたので、そちらを参照していただければと思います。

html-css-javascript.hatenadiary.com

これを参考にして候補者とその年齢をデータとして送信するフォームを作成します。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>候補者追加</title>
</head>
<body>
    <form id="addCandidateForm" action="addCandidate" 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 type="submit">追加</button>
    </form>
</body>
</html>

今までもそうしてきましたけど、英単語をつなぐときにはハイフンにして「candidate-age」のようにしてもいいんですけど、これをやるとJavaScriptで書くときに不便なので「candidateAge」のように、二つ目以降の単語の頭を大文字にするということで統一します。

ここまでで使ったハイフンもすべて書き直します。

ハイフンを消してから、次の単語の頭を大文字にするだけですから難しいものではないと思いますが、ちゃんとコピペで再現できるようにまとめの記事で全部記載しておきます。

あと候補者年齢のほうですが、maxとminを付けることでめちゃくちゃな数字を入れられないようにしました。

requiredは入力必須の項目であることを意味します。

無記入では送信できないようにしました。

で、できたのがこちら。

Image
候補者追加フォーム1

当然このままじゃ不格好なのでCSSで揃えていくことにします。

まあそこまで難しいことをするわけでもなく、縦に並べるくらいでいいですね。

#addCandidateForm {
  display: flex;
  flex-direction: column;
  width: 300px;
  margin: 2rem auto;
  gap: 1rem;
}

これで以下のようになりました。

Image

とりあえず形はこれでよさそうです。

次は送信した内容を「vote.html」の投票するところに反映させるようにします。

投票ページの作成③CSSの作成

前回、投票ページのHTMLを作りました。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>投票ページ</title>
</head>
<body>
<header><!-- 後ほどJavaScriptで共通ページを引っ張ってくる-->
    <h1>投票ページ</h1>
</header>
    <main class="vote-main">
<table> <!-- テーブル-->
  <thead><!-- 項目を書く行-->
    <tr><!-- 以下のものを一行に収める-->
      <th>順位</th><!-- 内容(値)-->
      <th>候補者</th>
      <th>投票数</th>
      <th>投票ボタン</th>
    </tr>
  </thead>
  <tbody id="candidate-list">
    <!-- ここに行が追加されていく-->
  </tbody>
</table>
    </main>
</body>
</html>

これを整理するためにCSSを書いていきます。

で、CSSを進めていく上で必要なクラスやIDなどを付け足していきます。

その際はコメントアウトで付け足した部分をメモしていきます。

HTMLはトップページと投票ページを分けましたが、CSSは今のところは特に分ける意味もないので、トップページを書いたものを使います。

body {
  font-size: 1rem;
  font-family: sans-serif;
  margin: 0;
  padding: 0;
}

header {
  position: relative;
  background-color: #fff;
  padding: 1rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

nav {
  position: absolute; /*ヘッダーを基準に書いていく*/
  display: none; /*最初はメニューが見えないようにしておく*/
  &.show{
    display: block; /* show クラスがついたら表示*/
  }
  top: 100%; /*ヘッダーの下に配置*/
  right: 0; /* 右端はヘッダーに合わせる*/


  ul {
    list-style: none;/* マーカーを消す*/
    padding: 0;      /* ul の内側の余白を消す*/
    
    li {
      margin-bottom: 0.5rem;

      a { /* リンクの表示の扱い*/
        display: block; /* li 全体をクリック可能に*/
        padding: 0.5rem 1rem; /* 内側の余白 */
       text-decoration: none; /* リンクの下線を消す*/
        color: #333; /* ダークグレー*/

        &:hover { /* ホバーしたときの扱い*/
          background-color: #f0f0f0;  /* ホバー時の色 */
        }
      }
    }
  }
}

まずmainのレイアウトや見た目を整えていきます。

main

トップページのmainとこんがらがってはいけないので、HTMLの方にクラスをつけて<main class="vote-main"><!-- 投票用のメイン-->にします。

これでCSSを書いていきます。

.vote-main {
  width: 100%;
  padding: 1rem;
  box-sizing: border-box;/*paddingなども含めて幅を計算する*/
}

次にtableですね。

table

テーブルに関してはCSSでもJavaScriptでもほぼ確実に制御することになるので、こっちにもクラスを付けておきます。

クラス名は<table class="vote-table"> <!-- テーブル-->でいいですね。

では書いていきます。

.vote-table {
  width: 100%;
  border-collapse: collapse; /*セルの境界線を重ねる*/
  margin-bottom: 1rem;

  th, td {
    border: 1px solid #ccc; /*薄いグレーの境界線*/
    padding: 0.5rem;
    text-align: left;
  }

  th {
    background-color: #f9f9f9; /*薄いグレーの背景色*/
  }
}

まあこんな感じです。

thは項目(見出し)のセルを表し、tdはデータ(値)のセルを表します。

現時点ではテーブルといっても一行だけで、データではなく項目だけを表示したいのでtdは要らないのですが、今後JavaScriptでデータを追加していくので、ここは記載したままにしておきます。

次の問題は項目ごとの横幅です。

すべてを均等にするのではなく、順位、候補者名、投票数、投票ボタン、という項目なので、それに適した長さにする必要があります。

まずはテーブルレイアウトをfixedにして固定します。

で、要素の一つ下(子)を制御するのでnth-childを使い、長さの割合を一つずつ決めていきます。

nthとはn-th、つまりn番目を意味します。

thの中の何番目をどれくらいの割合の長さにするかを決めるので、以下のように書きます。

  th {
    background-color: #f9f9f9; /*薄いグレーの背景色*/
    &:nth-child(1) {
      width: 10%; /*順位の幅*/
    }
    &:nth-child(2) {
      width: 45%; /*候補者の幅*/
    }

    &:nth-child(3) {
      width: 20%; /*投票数の幅*/
    }
    
    &:nth-child(4) {
      width: 25%; /*投票ボタンの幅*/
    }
  }

vote-tableのところでレイアウトをfixedにするのも忘れないように。

.vote-table {
  width: 100%;
  border-collapse: collapse; /*セルの境界線を重ねる*/
  margin-bottom: 1rem;
  table-layout: fixed; /*項目の横幅を固定する*/

これで以下のようなレイアウトになります。

Image
投票結果の項目

実際の順位やら候補者やらを表示する箇所はJavaScriptで制御することになります。

投票ボタンを押したら数字を投票数を1増やし、それを順位順に並べる、といった制御です。

で、今思いついたんですけど、候補者を増やすフォームも必要ですね。

入力フォームに候補者名を書いて、送信ボタンを押せば候補者の名前が反映されてテーブルの行が増える、というような。

ただこれ、投票とは役割が違って「候補者を追加する」ことが目的ですから、このページに作るのは違うように思います。

なので候補者追加のページはまた別に作成します。

次回はそれを作っていくことにします。

投票ページの作成②HTMLの作成

前回言った通り、投票ページでは投票の項目を表すところと、投票の結果を表すところを作ります。

html-css-javascript.hatenadiary.com

投票結果はテーブルのような形になりますので、使うタグは<table>ですね。

<table> <!-- テーブル全体 -->
  <thead> <!-- 見出し(項目)の行 -->
    <tr> <!-- この中に1行分のセル(th)が収まる -->
      <th>順位</th>       <!-- 列の見出し -->
      <th>候補者</th>     <!-- 列の見出し -->
      <th>投票数</th>     <!-- 列の見出し -->
      <th>投票ボタン</th> <!-- 列の見出し -->
    </tr>
  </thead>

  <tbody id="candidate-list"> <!-- データ行が追加される場所 -->
    <!-- ここに JavaScript で <tr> が追加される -->
  </tbody>
</table>

コピペできるように全部を書くと以下のようになります。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>投票ページ</title>
</head>
<body>
<header><!-- 後ほどJavaScriptで共通ページを引っ張ってくる-->
    <h1>投票ページ</h1>
</header>
    <main>
<table> <!-- テーブル-->
  <thead><!-- 項目を書く行-->
    <tr><!-- 以下のものを一行に収める-->
      <th>順位</th><!-- 内容(値)-->
      <th>候補者</th>
      <th>投票数</th>
      <th>投票ボタン</th>
    </tr>
  </thead>
  <tbody id="candidate-list">
    <!-- ここに行が追加されていく-->
  </tbody>
</table>
    </main>
</body>
</html>

・・・あれ?

HTMLはこれで終わりですね。

またあっさりと終わってしまいました。

次はCSSに進みます。

投票ページの作成①方針を考える

前回まででトップページとそのヘッダーを作りました。

次はメインコンテンツである掲示板を作ります。

タイトルでもある投票型の掲示板です。

候補があって、候補の横に添えられている投票ボタンを押したら数値が増える、という感じで。

トップページでメインを「投票するところ」としておきましたけど、トップページにそんなものを作ってしまったら見づらくて仕方なくなると思うので、別のページにしようと思います。

とりあえずファイル名は「vote.html」にします。

ヘッダーはすべて共通にすることになりますので、ヘッダーもファイルを別につくってJavaScriptでそれを引っ張ってくることにします。

なのでここではメインである投票フォームを考えていこうと思います。

まずは構成。

構成の方針

今回も最低限のものだけを考えていくことにします。

上部に「順位」「候補」「投票数」「投票ボタン」という項目を作って、その下に候補を任意に増やしていき、行を追加していく感じにしようと思います。

Image
投票フォーム

こんな感じです。

書き方の方針

まずはHTMLで構成を作ることになります。

まず項目の箇所と追加していく場所を確保します。

それらをCSSのdisplayをflexにして横並びのレイアウトを構成します。

投票ボタンを押したときや候補を追加するとき、あとは順位を順番にそろえるあたりはJavaScriptで行うことになります。

操作をしたあとの再描写もJavaScriptで行うことになりますね。

この場合、再描写ってのはリロードじゃなく、DOM操作による部分的な変化を描写するという意味で使っています。

おわりに

短いですが、今回はここまでにしておきます。

この記事の方針に従ってページを作成していきます。

次はまずHTMLを作成します。

Image