みっちぇのWebデザイン研究所

はてなブログのカスタマイズを中心に、ウェブデザインについて研究するブログ

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要素取得

f:id:micche-labo:20211017133905p:plain
li要素を取得

const lis = document.querySelectorAll('nav > ul > li'); // NodeList
const li = [...lis]; //ただの配列に置換

2. 子要素をもつliで絞り込み、class付与

f:id:micche-labo:20211017134103p:plain
子要素<ul>をもつ<li>

子要素をもっているかどうかは、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にクラスが付与されてしまい、うまくいきませんでした

やりたかったこと

f:id:micche-labo:20211017134530p:plain
やりたかったこと

実際の結果

f:id:micche-labo:20211017134728p:plain
実際の結果

原因は、子要素ってaタグも含むので、メニュー項目の各リスト全てリンクになってるため、全部trueとなり、全てのli要素にクラス付与されてしまうのでした。

方法その2:子要素から指定してみる

反対に、先に子要素にクラスをつけ、そのあと親要素にクラス付与してはどうか?と考えての行動。

1.子要素のulにクラスをつける

nav要素の中のul要素の中のli要素の中のulにクラスを先につける。

f:id:micche-labo:20211017133552p:plain
子要素ulにクラスをつける

const child = document.querySelectorAll('nav ul li ul');
child.forEach((addClass)=>{
addClass.classList.add('sub-menu');
});

一応console.logでチェック

f:id:micche-labo:20211017135049p:plain
クラス付与チェック

念押しで全部のulをチェック

f:id:micche-labo:20211017135345p:plain
全部の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にのみクラスが付与されたかチェック

f:id:micche-labo:20211017141725p:plain
クラス付与されたかチェック

無事、子要素を持つ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です。

f:id:micche-labo:20211017150127p:plain
ホバーで表示

できなかったこと

  • li要素全て取得後にhasChildNodesで子要素ulをもつliにのみクラス付与

aタグも子要素にあるため全てのliに子要素があることになり不可能

developer.mozilla.org

できたこと

  • 子要素ul取得後に親要素liを取得しクラス付与

→ 実現可能

ひとりごと

javascriptでできることが増えてきました。

勉強しはじめたのが 9/23 だったので、本日 10/17 ということで3週間ほどがたちました。

まだまだできることを増やしていきたいので引き続き勉強がんばります〜〜!