JavaScriptを使って、表示されているユーザー名をクリックするとコメント表示、非表示ができる機能を実装するのに、めちゃくちゃ時間かかって、コード自体の記述量は少ないのに、しょうもないミスでハマりにハマり、まる二日潰すという自体に発展してしまいました。
JavaScript初心者なのでほんとに小さいミスや、理解が浅いところでかなり時間がとられてしまって、戒めもかねて、ハマった事をざっと書いていきたいと思います。
よろしくお願いします!
##前提として
これは実装の仕方を説明しているものではありません。
なのでコードの解説を逐一しませんが、読んでいただいている方になんとなくイメージを掴んでいただくために、こういうものを作るときにハマったんだよ〜って完成形のGIF貼っときます。
##エントリーNo.1 不器用でわがまま addEventListenerさん
これは前回に解説したのですが、class名から複数の要素を取り出し、addEventListenerを使って処理を行うときにハマったものです。
addEventListenerは一つの要素に対して使えるのですが、getElementsByClassNameやquerySellectorAllで取得した複数の値に対しては使用することができません。
そこで、繰り返し処理を用いて要素一つずつ取り出して、一つずつaddEventListenerをかけてやったのですが、次は、addEventListenerはgetElementsByClassNameが返す戻り値の「HTMLCollection」には使えないということが発覚。
配列に直してやって、やっと解決しました。
詳しくはこちらの記事にまとめてあります。↓
class取得した値にaddEventListenerは使えない
##エントリーNo.2 生真面目 終了タグさん
とりあえず、ハマっていた時のブラウザの表示を貼っておきます。
「テスト生徒」という名前の生徒の下にもう一人「テスト三」という生徒が表示されるはずなのですが、「テスト三」が隠れてしまっています。(理想の表示は先ほどのGIFを参照してください。)
CSSでは<ul>に display: none; をかけています。(コメントの表示を隠すため)
@progressesには表示すべきコメントたちが代入されています。
ツッコミどころ満載かと思いますが、その時のビューのコードです。
<div class = "progresses-box">
学習中生徒一覧
<% @studying_students.each do |studying_student| %>
<p class="student_name"><%= studying_student.student.name %></p>
<ul class = 'progresses-list hidden' > %>
<% @progresses.each do |progress| %>
<li class= 'progress-list' >
<%= l progress.created_at %><br>
P<%= progress.progress_page %> / P<%= @teachers_textbook.page %><br>
<%= progress.comment%>
</li>
<% end %>
<% end %>
</div>
はい。
普通に<ul>の終了タグがないのが原因です。
<ul>の終了タグがないのでその下全部に display: none;がかかってしまっていると言った感じですね。
全然違うところのコードを見つめて、考えて直して、を繰り返してこれだけで半日ぐらい潰してました。
エントリーNo.3 繰り返し職人 forさん
forさんのそもそもの使い方に問題があったというよりか、JSの性質の問題なのですがfor文の()の中に記述することで気付きにくかったということが言いたいです。
for文を使ってこのようなコード書き、「ユーザー名をクリックするとコメントを表示させる機能」を実装する際に、ユーザーが増えても対応できるようなコードを書きました。↓
for (indexNum = 0; indexNum < pullDownNames.length; indexNum++) {
pullDownNames[indexNum].addEventListener("click", function() {
pullDownComments[indexNum].setAttribute("style", "display:block;")
});
}
これはindexNum変数を設定するための、letを記述していなかったのが原因で、思った通りの挙動をなかなかしてくれませんでした。
JSに慣れていなくて、定数や変数を定義するためのconstやletを忘れがちです。
for文の()内に記述していたことでさらに見つけにくかったです。
エントリーNo.4 もはやお前なんなの? セミコロンさん
まずは先ほどのコードを改良し、if文と組み合わせて、もう一度コメント表示させているユーザー名をクリックすると、コメントを非表示にできる機能を実装しました。
for (let indexNum = 0; indexNum < pullDownNames.length; indexNum++) {
pullDownNames[indexNum].addEventListener("click", function() {
if (pullDownComments[indexNum].getAttribute("style") == "display:block;") {
// pullDownCommentsにdisplay:block;が付与されている場合(つまり表示されている時)実行される
pullDownComments[indexNum].removeAttribute("style", "display:block;")
} else {
// pullDownCommentsにdisplay:block;が付与されていない場合(つまり非表示の時)実行される
pullDownComments[indexNum].setAttribute("style", "display:block")
}
});
}
これもやばかった、めっちゃしょうもないのに、MENTA登録してメッセージ送りかけるところまでしました。(結局自己解決しましたが)
この記述だとif文で一番はじめに実行される処理はelseの後の記述なんですね。
初期状態(コメント非表示の状態)では、条件式から外れるので、elseのsetAttributeでstyle = display:blockが付与されます。
もう一度クリックすると、1回目にdisplay:blockが付与されているはずなので、ifの条件式に当てはまり、removeAttributeが実行されるはずです。
これがうまくいかない。。。
どうなってんだと思い、ifのtrueを返した時の処理のところにdebuggerを置いてみると、発動しない。
どうやらif文の条件式を満たしているはずなのfalseを返しているらしい。
一度クリックした状態(display:blockが付与されている状態)で仕方なくelseの下にdebuggerを置いて、くまなく調べました。
その時の様子↓
ほんとに意味がわからなかったです。
styleにdisplay:blockが付与されているのに、なんでifの条件式はfalseを返してくるの!?
ってはげそうでした。
お気づきだと思いますが、elseの方で付与していたのはdisplay:blockでif文の条件式の中で比べているのはdisplay:block; なんです。
セミコロンがあるかないかです。
ifはずっとセミコロンがあるからイコールじゃないよっとelseに処理をふっていたんですね。。。
ちなみにifの中のdisplay:blockの記述すべてのセミコロンを取っ払ってみたら、しっかりとif文が仕事しました。逆に、全てにセミコロンを付けてもうまくいきました。display:blockという記述自体はどちらの書き方でも効果を発揮するみたいです。
要はどちらかに統一しろって話ですね。
##終わりに
めちゃめちゃしょうもないミスでなかなか前に進まなかった二日間でしたが、一人ですべて解決できたということは自信につながりましたし、一人でいろんなメソッドを使って、自分なりにそれらを組み合わせて機能が実装できているのがなんだか驚きで少し成長を感じました。
ものすごくレベルが低いところで自信を持っているんだろうなあと思いながら、こういう小さな「できた!」を大切に自分を褒めてあげることも大事かなと。