RailsでHTMLCollection・NodeListを使う
今回、getElementByClassNameやquerySelectorAllで要素を取得してイベントを登録する際に引っかかったっ点を備忘録として記述しておく。
まず、要素を取得する時点からミスっていたのでその原因の究明から先に行った。
まず、Railsから要素が取得できていなかったので次のようなコードを書いた。
var menuSeconds = document.getElementsByClassName('menu__second');
var profiles = document.querySelectorAll('.profile-lists li a');
console.log(menuSeconds);
console.log(profiles);
getElementsByClassNameでは戻り値がHTMLCollection、querySelectorAllでは戻り値はNodoListsとなる。
ちなみに矢印をクリックすると中の要素が表示されるがHTMLCollectionの中身は全て表示され、lengthも表示される。NodeListsは中身もなく、lengthも0と表示される。
しかし、HTMLCollectionで取得できた要素ですら、いざ使用しようと思うとなぜかできない。
念の為、console.logでそれぞれのlengthを出してみよう。
console.log(menuSeconds.length);
console.log(profiles.length);
するとどちらの要素もlengthが0で何も取得できていないことがわかる。
なぜか。簡潔に言うとこれはJavaSctiptがDOMがrenderされる前に走っているから。つまりHTMLのbodyが読み込まれる前にスクリプトが読み込まれているから。Rails上で先にjsファイルが読み込まれていた。
解決策は
1.HTMLのbodyの最後にscriptタグで囲んで書く。
2.下記のようにDOMが読み込まれるのを待つ記述をする。
(function() {
'use strict';
document.addEventListener('DOMContentLoaded', function(e) {
var menuSeconds = document.getElementsByClassName('menu__second');
var profiles = document.querySelectorAll('.profile-lists li a');
console.log(menuSeconds.length);
console.log(profiles.length);
});
})();
上のようにちゃんと取得される。
また、NodeListsから一つの要素を取り出す際は、profile[0]のように配列と同じでいいが、HTMLCollectionから取り出す際は、menuSeconds.item[0]と、itemをつける必要がある。
イベントリスナーを登録したい
取得した要素にイベントリスナーを登録したいときの処理も備忘録のため。
まず前提としてHTMLCollectiondにはforEachは使えない。よってfor文を使おう。
for (var i = 0; i < menuSeconds.length; i++) {
menuSeconds.item[i].addEventListener('mouseover', function() {
のように書き始めていた。が、
これだとクロージャが生成されずカウンター変数が保持されないためどこから参照してもlengthの値しかカウントされないらしい。
よって中で即時関数を定義することで解決できる。
for (var i = 0; i < menuSeconds.length; i++) { function(n) {
menuSeconds.item[n].addEventListener('mouseover', function() {
.
.
.
}, false);
})(i);
これでちゃんとイベントが登録されているはず。
NodeListsはforEachが使えるので、
profiles.forEach( pf => {
pf.addEventListener('mouseover', function() {
.
.
.
などとかけば登録できる。
まだまだ未熟者なので至らない箇所があればぜひコメントください。