Skip to content

Instantly share code, notes, and snippets.

@manabuyasuda
Last active September 22, 2021 07:36
Show Gist options
  • Save manabuyasuda/3799c3e5f7e4564532740e75b0d1afea to your computer and use it in GitHub Desktop.
Save manabuyasuda/3799c3e5f7e4564532740e75b0d1afea to your computer and use it in GitHub Desktop.
Accordion(ES2015)
/**
* アコーディオンコンポーネントです。
* @author Manabu Yasuda
* @see https://www.w3.org/TR/wai-aria-practices-1.1/examples/accordion/accordion.html
* @param {CSS selectors} root ['.js-accordion'] - ルートに指定するクラス名です。
* @param {CSS selectors} tabs ['.js-accordion-tab'] - トリガーに指定するクラス名です。`button`タグを推奨します。
* @param {CSS selectors} tabpanels ['.js-accordion-panel'] - コンテンツに指定するクラス名です。
* @param {CSS selectors} initializedClass ['.js-accordion-initialized'] - 初期化完了時に付与するクラス名です。
* @param {boolean} useRole [false] - `role`属性が付与されます。
* @param {boolean} firstChildShow [true] - ルートの最初の要素が開きます。
* @param {boolean} multiselectable [true] - 複数の要素を同時に開きます。
* @param {String} tabsPrefix ['accordion-'] - `tabs`に付与するid属性のプレフィックスです。
* @param {String} tabpanelsPrefix ['accordion-panel-'] - `tabpabels`に付与するid属性のプレフィックスです。
*/
export default class Accordion {
constructor(options) {
const defaultOptions = {
root: '.js-accordion',
tabs: '.js-accordion-tab',
tabpanels: '.js-accordion-panel',
initializedClass: 'js-accordion-initialized',
useRole: false,
firstChildShow: true,
multiselectable: true,
tabsPrefix: 'accordion-',
tabpanelsPrefix: 'accordion-panel-',
}
this.options = Object.assign(defaultOptions, options)
Object.keys(this.options).forEach((key) => {
this[key] = this.options[key]
})
this.selector = {
root: Array.from(document.querySelectorAll(this.root)),
tabs: Array.from(document.querySelectorAll(this.tabs)),
tabpanels: Array.from(document.querySelectorAll(this.tabpanels)),
}
}
init() {
if (!this.selector.root.length) return
this.setAttributes()
if (this.useRole) {
this.setRole()
}
this.selector.root[0].classList.add(this.initializedClass)
if (this.firstChildShow) {
this.showFirstChild()
}
this.toggle()
}
setAttributes() {
this.selector.tabs.forEach((tab, index) => {
tab.setAttribute('id', this.tabsPrefix + (index + 1))
tab.setAttribute('aria-controls', this.tabpanelsPrefix + (index + 1))
tab.setAttribute('aria-expanded', false)
})
this.selector.tabpanels.forEach((tabpanel, index) => {
tabpanel.setAttribute('id', this.tabpanelsPrefix + (index + 1))
tabpanel.setAttribute('aria-labelledby', this.tabsPrefix + (index + 1))
tabpanel.setAttribute('aria-hidden', true)
})
}
setRole() {
this.selector.root.forEach((item) => {
item.setAttribute('role', 'tablist')
})
this.selector.tabs.forEach((tab) => {
tab.setAttribute('role', 'tab')
})
this.selector.tabpanels.forEach((tabpanel) => {
tabpanel.setAttribute('role', 'tabpanel')
})
}
showFirstChild() {
this.selector.tabs[0].setAttribute('aria-expanded', true)
this.selector.tabpanels[0].setAttribute('aria-hidden', false)
}
toggle() {
this.selector.tabs.forEach((tab) => {
tab.addEventListener('click', (e) => {
const tabID = document.getElementById(e.currentTarget.id)
const isExpanded = tabID.getAttribute('aria-expanded') !== 'false'
if (!this.multiselectable && !isExpanded) {
this.hideAll()
}
tabID.setAttribute('aria-expanded', !isExpanded)
document
.querySelector(`[aria-labelledby="${e.currentTarget.id}"]`)
.setAttribute('aria-hidden', isExpanded)
e.stopPropagation()
e.preventDefault()
})
})
}
hideAll() {
this.selector.tabs.forEach((hideTab) => {
const hideTabID = hideTab.getAttribute('id')
hideTab.setAttribute('aria-expanded', false)
document.querySelector(`[aria-labelledby="${hideTabID}"]`).setAttribute('aria-hidden', true)
})
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment