🦓
[Rails]タブメニュー
はじめに
tailwindcss-stimulus-components
を使ってタブメニューを実装していきます。
まだインストールしてない場合こちらからご参考にしてみてください。
tailwindcss-stimulus-components
環境
Rails 7.0.4.3
ruby 3.2.1
タブモジュールを読み込む
app/javascript/controllers/index.js
import { Tabs } from "tailwindcss-stimulus-components"
application.register('tabs', Tabs)
stimulusコントローラーを作成する
bin/rails g stimulus tabs
create app/javascript/controllers/tabs_controller.js
データ属性を設定する
ソースコードを読みながら設定していきます。
app/views/profiles/show.html.erb
<div data-controller="tabs" data-tabs-active-tab="-mb-px border-l border-t border-r rounded-t">
# タブメニュー
<ul class="list-reset flex border-b">
<li class="-mb-px mr-1" data-tabs-target="tab" data-action="click->tabs#change">
<a class="bg-white inline-block py-2 px-4 text-blue-500 hover:text-blue-700 font-semibold no-underline" href="#">Active</a>
</li>
<li class="mr-1" data-tabs-target="tab" data-action="click->tabs#change">
<a class="bg-white inline-block py-2 px-4 text-blue-500 hover:text-blue-700 font-semibold no-underline" href="#">タブ1</a>
</li>
<li class="mr-1" data-tabs-target="tab" data-action="click->tabs#change">
<a class="bg-white inline-block py-2 px-4 text-blue-500 hover:text-blue-700 font-semibold no-underline" href="#">タブ2</a>
</li>
<li class="mr-1">
<a class="bg-white inline-block py-2 px-4 text-gray-300 font-semibold no-underline" href="#">タブ3</a>
</li>
</ul>
# タブコンテンツ
<div class="py-4 px-4 border-l border-b border-r" data-tabs-target="panel">
コンテンツ1
</div>
<div class="py-4 px-4 border-l border-b border-r" data-tabs-target="panel">
コンテンツ2
</div>
<div class="py-4 px-4 border-l border-b border-r" data-tabs-target="panel">
<iframe width="560" height="315" src="https://www.youtube.com/embed/y3niFzo5VLI" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>
</div>
</div>
data-controller="tabs
:コントローラーを指定します。
data-tabs-active-tab="-mb-px border-l border-t border-r rounded-t"
:アクティブのタブに追加されるクラス名です。
data-tabs-inactive-tab="inactive"
:インアクティブのタブに追加されるクラス名です。
data-tabs-target="tab"
:タブとなる要素に追加します。
data-action="click->tabs#change"
:タブをクリックされたらtabs
コントローラーのchange
アクションと紐付けます。
data-tabs-target="panel"
:アクションのターゲットとなる要素を指定します。タブのコンテンツの部分になります。
tabs_controller.js
を設定する
JSのソースコードも見ていきます。
static classes = [ "activeTab", "inactiveTab" ]
- 静的クラスプロパティとして、コントローラーが使用するCSSクラスのリストを定義しています。
-
activeTab
とinactiveTab
という2つのクラスを利用します。
static targets = ['tab', 'panel', 'select']
- コントローラーが操作するHTML要素のリストを定義しています。
-
tab
、panel
、select
という名前の要素を操作するので指定します。
static values = {
index: 0,
updateAnchor: Boolean
}
-
index
は現在のタブのインデックスとなります。初期値は0です。 -
updateAnchor
はBoolean
値を持ち、URLのアンカーを更新するかどうかを管理します。
connect() {
if (this.anchor) this.indexValue = this.tabTargets.findIndex((tab) => tab.id === this.anchor)
this.showTab()
}
-
connect()
メソッドは、コントローラーがDOMに接続されると呼び出されるメソッドです。this.anchor
が存在する場合、tabTargets
内の要素の中で、this.anchor
と一致するIDを持つ要素のインデックスをindexValue
に設定します。その後、showTab()
メソッドを呼び出して、現在のタブを表示します。
change(event) {
if (event.currentTarget.tagName === "SELECT") {
this.indexValue = event.currentTarget.selectedIndex
} else if (event.currentTarget.dataset.index) {
this.indexValue = event.currentTarget.dataset.index
} else if (event.currentTarget.dataset.id) {
this.indexValue = this.tabTargets.findIndex((tab) => tab.id == event.currentTarget.dataset.id)
} else {
this.indexValue = this.tabTargets.indexOf(event.currentTarget)
}
window.dispatchEvent(new CustomEvent('tsc:tab-change'))
}
-
change(event)
メソッドは、タブがクリックされたときに呼び出されるメソッドです。クリックされた要素の種類に応じて、indexValue
を更新します。 - "SELECT"要素の場合は、選択されたオプションのインデックスを
indexValue
に設定し、data-index
属性がある場合はその値を使います。 -
data-id
属性がある場合は、tabTargets
内の要素の中で、data-id
と一致するIDを持つ要素のインデックスをindexValue
に設定します。 - それ以外の場合は、クリックされた要素自体のインデックスを
indexValue
に設定します。 - 最後に、
tsc:tab-change
という名前のカスタムイベントをwindow
オブジェクトにdispatch
します。
プールダウンセレクト
<div data-controller="tab">
<select data-tabs-target="tab" data-action="change->tab#change">
<option value="tab1">Tab 1</option>
<option value="tab2">Tab 2</option>
</select>
<div id="tab1" data-tabs-target="panel">Tab 1 content</div>
<div id="tab2" data-tabs-target="panel">Tab 2 content</div>
</div>
data-index属性
<div data-controller="tab">
<div data-tabs-target="tab" data-action="click->tab#change" data-index="0">Tab 1</div>
<div data-tabs-target="tab" data-action="click->tab#change" data-index="1">Tab 2</div>
<div data-tabs-target="panel">Tab 1 content</div>
<div data-tabs-target="panel">Tab 2 content</div>
</div>
data-id属性
<div data-controller="tab">
<div data-tabs-target="tab" data-action="click->tab#change" data-id="tab1">Tab 1</div>
<div data-tabs-target="tab" data-action="click->tab#change" data-id="tab2">Tab 2</div>
<div id="tab1" data-tabs-target="panel">Tab 1 content</div>
<div id="tab2" data-tabs-target="panel">Tab 2 content</div>
</div>
nextTab() {
this.indexValue = Math.min(this.indexValue + 1, this.tabsCount - 1)
}
previousTab() {
this.indexValue = Math.max(this.indexValue - 1, 0)
}
firstTab() {
this.indexValue = 0
}
lastTab() {
this.indexValue = this.tabsCount - 1
}
- タブを切り替えるためのメソッドです。
nextTab()
は次のタブに、previousTab()
は前のタブに、firstTab()
は最初のタブに、lastTab()
は最後のタブに切り替えます。
indexValueChanged() {
this.showTab()
if (this.updateAnchorValue) {
location.hash = this.tabTargets[this.indexValue].id
}
}
-
indexValue
が変更されたときに呼び出されるメソッドです。 -
showTab()
を呼び出してタブを表示し、updateAnchorValue
がtrue
の場合、URLのアンカーを現在のタブのIDに更新します。
showTab() {
this.panelTargets.forEach((panel, index) => {
const tab = this.tabTargets[index]
console.log(tab)
if (index === this.indexValue) {
panel.classList.remove('hidden')
if (this.hasInactiveTabClass) tab?.classList?.remove(...this.inactiveTabClasses)
if (this.hasActiveTabClass) tab?.classList?.add(...this.activeTabClasses)
} else {
panel.classList.add('hidden')
if (this.hasActiveTabClass) tab?.classList?.remove(...this.activeTabClasses)
if (this.hasInactiveTabClass) tab?.classList?.add(...this.inactiveTabClasses)
}
})
if (this.hasSelectTarget) {
this.selectTarget.selectedIndex = this.indexValue
}
}
- タブとパネルの表示を切り替えるためのメソッドです。
panelTargets
とtabTargets
はそれぞれ静的ターゲットプロパティで指定したHTML要素のリストです。 -
this.indexValue
とindex
を比較して、現在のパネルを表示または非表示にします。 - タブに設定されたクラスを切り替えることで、アクティブなタブと非アクティブなタブを視覚的に区別します。
-
hasSelectTarget
がtrue
の場合、selectTarget
のselectedIndex
をindexValue
に設定します。
get tabsCount() {
return this.tabTargets.length
}
- タブの数を返すゲッターメソッドです。
tabTargets
の配列の長さを返します。
get anchor() {
return (document.URL.split('#').length > 1) ? document.URL.split('#')[1] : null;
}
- 現在のURLのアンカーを取得するゲッターメソッドです。
document.URL
を使って現在のURLを取得し、'#'
で分割した配列の2番目の要素(アンカー)を返します。アンカーが存在しない場合はnull
を返します。
終わりに
tailwindcss-stimulus-components
を使ったタブメニューでした。
デベロッパーツールを開いてブラウザの動きを確かめると分かりやすいですね。
Discussion