JavaScriptでliに子要素がある時に絞り込みしようとしてうまくいかなかった時の原因
先日WordPressでテーマ制作する際、ナビメニューを子要素なしのシンプルなものにしたんですが(フレームワークの関係上)、子要素があったときにドロップダウンするようにCSSを書こうと思いまして、WordPressでのナビメニューのマークアップを確認。
WordPressテーマ制作の記事は以下 micche-labo.hatenablog.com
だいたい簡易版ですがこんな感じでした。
<nav> <ul> <li><a href="#">メニュー項目</a></li> <li class="menu-item-has-children"><a href="#!">ドロップダウンする項目</a> <ul class="sub-menu"> <li><a href="#!">中身</a></li> <li><a href="#!">中身</a></li> <li><a href="#!">中身</a></li> </ul> </li> </ul> </nav>
ポイントは、親となるli
と、子となるul
にそれぞれクラスが割り当てられているところ。
これを素直にjavascriptなりcssで受け取って書けばよいんですが、自分で仮の環境を作るにあたって、
- htmlタグに直接クラスを記入するのはめんどくさい
- だからjavascriptでクラスを追加したい
と考えました。
そこで何を思ったか、「そうだ子要素を持つ親要素li
を絞り込んでそこにクラスを付与し、さらにクラス付与のあるli
の子要素ul
を検索してクラス付与するシステムをjavascriptで書いてみよう」
といろいろコードを散々いじり倒しました。
最終的にうまくいったので、失敗例と成功例をそれぞれまとめてみました。
方法その1:絞り込みでやってみる
失敗パターン。
全部のli
要素取得してから、子要素のある親要素にクラスをつけようとしたが、a
タグも子要素に含まれるためうまくいかずだめだった。
1. 全部のli要素取得
const lis = document.querySelectorAll('nav > ul > li'); // NodeList const li = [...lis]; //ただの配列に置換
2. 子要素をもつliで絞り込み、class付与
子要素をもっているかどうかは、hasChildNodes
でいけると聞き早速やってみました。
//forEachで配列の中身分繰り返す lis.forEach((serch)=>{ if(serch.hasChildNodes){ serch.setAttribute('class','menu-item-has-children'); } });
※注
classList.add('menu-item-has-children')
だとうまく機能しなくて、setAttribute('class','menu-item-has-children')
にしました。
ちなみに結果は、すべてのli
にクラスが付与されてしまい、うまくいきませんでした。
やりたかったこと
実際の結果
原因は、子要素ってa
タグも含むので、メニュー項目の各リスト全てリンクになってるため、全部true
となり、全てのli
要素にクラス付与されてしまうのでした。
方法その2:子要素から指定してみる
反対に、先に子要素にクラスをつけ、そのあと親要素にクラス付与してはどうか?と考えての行動。
1.子要素のulにクラスをつける
nav
要素の中のul
要素の中のli
要素の中のul
にクラスを先につける。
const child = document.querySelectorAll('nav ul li ul'); child.forEach((addClass)=>{ addClass.classList.add('sub-menu'); });
一応console.log
でチェック
念押しで全部のul
をチェック
無事に子要素ul
にのみクラス付与されています。
ここまではOK。
次、ul
の親要素を見つけてもらいます。
2.親要素を取得(parentNode
使用)しクラス付与
親要素はparentNode
で探せるときいたので、それでやってみます。
forEach
をつかって、NodeList(ul.sub-menu)
の親要素を取得後、li
にクラスを付与します。
child.forEach((has)=>{ const parent = has.parentNode; //各中身の親要素 parent.classList.add('menu-item-has-children'); //親にクラス付与 });
子要素を持つli
にのみクラスが付与されたかチェック
無事、子要素を持つli
にのみクラス付与され、やりたかったことができました!
2-2. closest
使用バージョン
はじめ、parentNode
で付与しようとして全然うまくいかず、undefind
と怒られ続けたので、closest
使用してうまくいったverも載せておきます。
child.forEach((has)=>{ const parents = has.closest('li'); parents.classList.add('menu-item-has-children'); });
IE非対応なので注意ですが。 developer.mozilla.org
closest
を使うと、自分を内包している要素全てから希望するセレクタを参照できるので便利だなと思いました。
まとめ
ということで、li
要素すべて取得してから、子要素をもつli
のみにしぼりこむのは無理だったけど、先に子要素ul
を取得してから親要素li
にクラス付与することはできました。
<nav> <ul> <li><a href="#">menu1</a></li> <li><a href="#!">menu2</a> <ul> <li><a href="#!">menu2-1</a></li> <li><a href="#!">menu2-2</a></li> <li><a href="#!">menu2-3</a></li> </ul> </li> <li><a href="#!">menu3</a> <ul> <li><a href="#!">menu3-1</a></li> <li><a href="#!">menu3-2</a></li> <li><a href="#!">menu3-3</a></li> </ul> </li> <li><a href="#">menu4</a></li> </ul> </nav>
//子要素<ul>を取得 const child = document.querySelectorAll('nav ul li ul'); //クラス付与 child.forEach((addClass)=>{ addClass.classList.add('sub-menu'); //子要素クラス付与 const parent = addClass.parentNode; //親要素取得 parent.classList.add('menu-item-has-children'); //親要素クラス付与 });
ピンクの部分がli.menu-item-has-children
です。
できなかったこと
li
要素全て取得後にhasChildNodes
で子要素ul
をもつli
にのみクラス付与
→ a
タグも子要素にあるため全てのli
に子要素があることになり不可能
できたこと
- 子要素
ul
取得後に親要素li
を取得しクラス付与
→ 実現可能
ひとりごと
javascriptでできることが増えてきました。
勉強しはじめたのが 9/23 だったので、本日 10/17 ということで3週間ほどがたちました。
まだまだできることを増やしていきたいので引き続き勉強がんばります〜〜!