三部作も早いもので、もう5作目まできました(これ如何に)。
前回はイベント委譲についてやりました。
html-css-javascript.hatenadiary.com
もうタイトルでは何をやってたのか全くわからなくなってしまいましたね。
これ、どっかでタグ付けでもしておきます。
それはさておき。
イベント委譲。
イベントクリックを一つ上の要素であるcandidateListにさせておき、実際にはその一つ下の子要素にやらせる、つまり委譲するというやり方でした。
で、こんな感じに書きました。
candidateList.addEventListener("click", function () { sortedCandidate[index].vote += 1; localStorage.setItem("candidates", JSON.stringify(candidates)); render(); });
で、これに問題があるという話でした。
どこが問題なのかというと、ここ。
sortedCandidate[index].vote += 1;
更に言うとindex。
これが問題なわけです。
indexって順番を示すものではあるんですけど、この先どういう拡張とかをするかはわかりません。
「上位10人だけ表示」とか「投票数がない人は非表示」とかみたいにフィルターをかけたりすると、indexの数値が信用できなくなる、というか完全におかしなことになるわけです。
だったらどうすればいいのでしょうか。
答えはindexではなくidで紐づけすることです。
IDによる紐づけ
IDを紐づけるというのはどういうことなのか。
これは候補者に識別子を割り振るという意味です。
「おい、それindexとどうちがうんだよ。
あれも番号をつかって識別子を割り振ってただろうが」
という反論があるかもしれませんが、こっちでの番号の割り振りは絶対に変わることはありません。
たとえば座席指定のチケットを買ったとします。
チケットには座席番号が書かれていますし、皆さんは指定された座席に座りますよね。
(私はほとんど人がいなければ窓際に移ったりしますけど。)
そのチケットに書かれた座席番号がIDです。
たとえ席を誰かと交換したとしても、チケットの座席番号が変わることはありません。
対してindexは座席を交換するとき、チケットも一緒に交換するイメージです。
なのでindexを使うと座席を交換した瞬間にその座席番号が誰を指しているのかがわからなくなります。
「座席番号A-22の方」と言われても、もう誰がもともとその座席番号だったのかがわからないので、特定することができなくなるわけです。
しかしIDは違います。
上述のとおり席を変わったとしてもチケットは交換しません。
なので「座席番号B-13の方」と呼ばれれば、もともとのチケットをそのまま持っているわけですから、すぐに特定することができます。
ちなみに同意があれば座席の交換はOKだそうです。
ただし、同じ車両、同じ区間のみ。
これを候補者に置き換えると、候補者の順位が投票で移動しようが、上位10位のみとか投票数0は弾く、などでフィルターにかけたりしようが、候補者につけられたIDはかわらないので、どこまで言ってもIDが追ってきて、特定されてしまうわけです。
なんか指紋みたいな感じともいえますね。
必ず特定されるので、逃げることはできません。
さて、IDを使うことの有用性がわかったところで、それでは実際にはどのように使っていくのかを見ていきます。
IDの紐づけの方法
とは言ったものの、これ、前の記事でほとんど同じことをやってます。
html-css-javascript.hatenadiary.com
イベント委譲のところです。
voteBtn.dataset.index = index;
これで投票ボタンにIDを付けて、候補者順にindexで番号も割り振れるというものでした。
で、これをIDに変えるってんですから、indexの部分をidに変えたらいいんじゃないのと思うのですが、その通りでした。
voteBtn.dataset.id = index;
これで解決です。
めっちゃあっけない。
何か罠があるんじゃないかと思ってしまうほどです。
ですが、最初に割り振った番号(index)をIDで固定してしまうわけですからこれでいけます。
と思ったのですが。
これじゃダメですね。
右辺がindexのままだと、結局のところindexを割り振ることになります。
なので右辺もidにする必要があります。
voteBtn.dataset.id = candidate.id;
こんな感じですね。
で、いったいこのcandidateはどこから出てきたんだよ、ってことになるから定義が必要になります。
今まで出てきたcandidateはforEachを回すための一時的な変数なのでスコープ的に届きません。
なので定義を作ります。
const candidate = candidates.find(candidateObj => candidateObj.id === id);
また出てきました。
知らない関数。
find()。
find()とは
これはそのまんま、配列candidatesにある中で条件が合うものを探し出す関数です。
名前だったり年齢だったり。
で、今回はIDが合うものを探しています。
で、アロー関数を使って、IDが合致したものをtrueとして、その要素をcandidateにぶち込んだわけです。
わかりやすくするためにcandidateObjという変数を使いましたが、これは何でもいいのでcにしときます。
const candidate = candidates.find(c => c.id === id);
で、candidateに返されるのはオブジェクト(要素)なので、trueではなくtruthyです。
配列candidatesのうち、idが一致した要素を返しています。
なのでこれがtrueになるかをif文をつかって判定させ、trueになったら処理を実行するようにします。
やり方は簡単。
ifをつかってcandidateがtrueの場合のみ処理を実行する、という風にします。
if (candidate){ candidate.vote += 1; localStorage.setItem("candidates", JSON.stringify(candidates)); render(); }
これでできあがりです。
if (candidate){}と書いてますが、ifのカッコの中身は判定時のみオブジェクトをboolean、つまりtrueかfalseのどちらであるかの判定に変換されます。
これはJavaScriptの仕様です。
さて、ここでもう一つ問題があります。
このidは以下のコードから出てきたものと比較しています。
const id = target.dataset.id;
で、このdatasetがすさまじく曲者であることが判明しました。
この野郎のせいで、idは文字列になっています。
このあと、idには数値を放り込んでいるんですが、こいつのせいで文字列化しているんです。
なのでfindで配列から狙ったIDを引っ張り出すには、この文字列化したidをもう一度数字に戻す必要があります。
ちゅうか、これまで数値のつもりで取得していた値はすべて文字列として扱われてました。
たとえば年齢の項目に28と入力して送信しても、JavaScriptのほうでは文字列として扱われるので、計算とかで数値として扱う場合は数値に戻す必要があります。
全く知らなかった・・・。
その方法は次回に回します。
いつになったら終わるんだろ。