htmxでできる!タブメニュー実装のススメ:htmx.takeClass()を駆使して、洗練されたUIを実現


htmx.takeClass() は、HTMX の JavaScript API におけるメソッドの一つで、指定された要素とその兄弟要素間でクラスの独占性を制御するために使用されます。具体的には、以下の操作を行います。

  1. 指定された要素から class 属性で指定されたクラスを削除します。
  2. 兄弟要素から同じクラスを削除します。
  3. 削除したクラスを指定された要素にのみ追加します。

効果

このメソッドを使用することで、指定された要素のみが class 属性で指定されたクラスを持つようになり、他の兄弟要素との差別化が可能になります。例えば、ボタングループにおいて、現在アクティブなボタンのみをハイライトしたい場合などに有効です。

使用方法

htmx.takeClass(element, className);
  • className: 独占するクラス名を文字列で指定します。
  • element: クラスを独占する要素を指定します。

以下の例では、button 要素グループにおいて、クリックされたボタンのみが active クラスを持つように設定します。

<div class="button-group">
  <button hx-target="#content1" hx-post>コンテンツ1</button>
  <button hx-target="#content2" hx-post>コンテンツ2</button>
  <button hx-target="#content3" hx-post>コンテンツ3</button>
</div>

<script>
  htmx.on('hx-post', function(event) {
    htmx.takeClass(event.target, 'active');
  });
</script>

このコードを実行すると、いずれかのボタンをクリックすると、そのボタンのみが active クラスを持ち、他のボタンは非アクティブな状態になります。

  • すでに指定された要素が class 属性で指定されたクラスを持っている場合は、そのクラスは削除されません。
  • 複数のクラスを独占したい場合は、カンマ区切りで指定できます。
  • htmx.takeClass() は、兄弟要素間でのみクラスの独占性を制御します。親要素や子要素には影響を与えません。


HTML

<div class="tab-menu">
  <ul>
    <li><a href="#" hx-target="#content1" hx-post>コンテンツ1</a></li>
    <li><a href="#" hx-target="#content2" hx-post>コンテンツ2</a></li>
    <li><a href="#" hx-target="#content3" hx-post>コンテンツ3</a></li>
  </ul>
</div>

<div class="tab-content">
  <div id="content1">コンテンツ1の内容</div>
  <div id="content2" style="display: none;">コンテンツ2の内容</div>
  <div id="content3" style="display: none;">コンテンツ3の内容</div>
</div>

<script>
  htmx.on('hx-post', function(event) {
    const target = event.target;
    const tabContentId = target.getAttribute('hx-target');
    const tabContentElement = document.getElementById(tabContentId);

    // すべてのタブコンテンツを非表示にする
    const tabContents = document.querySelectorAll('.tab-content > div');
    for (const tabContent of tabContents) {
      tabContent.style.display = 'none';
    }

    // クリックされたタブに対応するコンテンツのみ表示する
    tabContentElement.style.display = 'block';

    // タブメニューのクラスを制御する
    htmx.takeClass(target.parentNode, 'active');
  });
</script>

説明

  1. HTML では、タブメニューとタブコンテンツを定義します。
  2. JavaScript では、htmx.on('hx-post', ...) イベントリスナーを使用して、タブメニューのクリックイベントを捕捉します。
  3. イベントハンドラ内では、以下の処理を実行します。
    • クリックされたタブ要素から hx-target 属性で指定されたコンテンツIDを取得します。
    • 対応するコンテンツ要素を取得し、表示状態を block に設定します。
    • 他のコンテンツ要素は非表示にします。
    • htmx.takeClass() を使用して、クリックされたタブ要素のみが active クラスを持つように設定します。

実行結果

このコードを実行すると、タブメニューをクリックすることで、対応するコンテンツが表示され、同時にアクティブなタブが視覚的に強調されます。

  • アコーディオンメニュー
  • 設定画面における項目切り替え
  • 商品ページにおけるタブ切り替え
  • htmx.takeClass() は、要素間でのクラスの独占性を制御するのみであり、要素自体のスタイルを変更する機能はありません。スタイル変更には、CSS を利用する必要があります。
  • この例では、コンテンツの表示/非表示を切り替えるシンプルな実装になっています。必要に応じて、CSS や JavaScript でさらに装飾や機能を追加することができます。


CSS による排他制御

最もシンプルな方法は、CSS を使用して、特定の要素のみが class 属性で指定されたクラスを持つように設定することです。例えば、以下のような CSS を記述できます。

.button-group .button {
  display: inline-block;
  margin-right: 10px;
}

.button-group .button.active {
  background-color: #007bff;
  color: #fff;
}

この CSS を適用すると、button-group クラスを持つ要素内の button 要素のうち、active クラスを持つ要素のみが青色でハイライトされます。

メリット

  • 追加の JavaScript コードが不要
  • シンプルでわかりやすい

デメリット

  • JavaScript による高度な制御が難しい
  • 動的なクラスの追加/削除には対応できない

JavaScript による排他制御

htmx.takeClass() に代わる方法として、JavaScript で独自にクラスの追加/削除を制御する方法があります。例えば、以下のような JavaScript コードを記述できます。

const buttons = document.querySelectorAll('.button-group .button');

buttons.forEach(button => {
  button.addEventListener('click', () => {
    buttons.forEach(otherButton => {
      otherButton.classList.remove('active');
    });

    button.classList.add('active');
  });
});

この JavaScript コードは、button-group クラスを持つ要素内のすべての button 要素に対して、クリックイベントリスナーを追加します。クリックされた要素のみが active クラスを持ち、他の要素は非アクティブな状態になるように処理しています。

メリット

  • JavaScript による高度な制御が可能
  • 動的なクラスの追加/削除に対応できる

デメリット

  • イベントハンドラを個別に記述する必要がある
  • htmx.takeClass() よりもコード量が多くなる

htmx 以外にも、要素間でのクラスの独占性を制御するためのライブラリがいくつか存在します。例えば、以下のようなライブラリが挙げられます。