'Tabber' is a ui widget where a set of buttons toggle content visible in a display area, see examples below
Here's the html for the tabber
<div class="Tabs">
<div class="Tabs-buttons">
<button class="Tabs-button js-Tabber-button is-active" data-tab-index="0">Tab 1</button>
<button class="Tabs-button js-Tabber-button" data-tab-index="1">Tab 2</button>
<button class="Tabs-button js-Tabber-button" data-tab-index="2">Tab 3</button>
</div>
<div class="Tabs-bodies">
<div class="Tabs-body js-Tabber-body is-active" data-tab-index="0">Tab Content 1</div>
<div class="Tabs-body js-Tabber-body" data-tab-index="1">Tab Content 2</div>
<div class="Tabs-body js-Tabber-body" data-tab-index="2">Tab Content 3</div>
</div>
</div>
class Tabber {
constructor (el) {
this.el = el;
this.tabs = [];
const buttons = this.el.querySelectorAll('.js-Tabber-button');
const contents = this.el.querySelectorAll('.js-Tabber-body');
for (const button of this.buttons) {
for (const content of this.contents) {
if (button.dataset.tabIndex === content.dataset.tabIndex) {
this.tabs.push({button, content});
}
}
}
for (const tab of this.tabs) {
tab.button.addEventListener('click', () => {
this.activateAndUpdateAll(tab);
});
}
}
activateTab(tab) {
tab.button.classList.add('is-active');
tab.content.classList.add('is-active');
}
deactivateTab(tab) {
tab.button.classList.remove('is-active');
tab.content.classList.remove('is-active');
}
activateAndUpdateAll(tabToActivate) {
for (const tab of this.tabs) {
if (tab !== tabToActivate) {
this.deactivateTab(tab);
}
}
this.activateTab(tabToActivate);
}
}
Initialized with
new Tabber(document.querySelector('.js-oop-tabber'));
const getTabIndex = R.path(['dataset', 'tabIndex']);
const tabIndexEq = R.useWith(R.equals, [R.identity, getTabIndex]);
const activateElsWithTabIndex = R.curry((els, idx) => els.filter(tabIndexEq(idx)).map(addClass('is-active')));
const deactivateElsWithoutTabIndex = R.curry((els, idx) => els.filter(R.compose(R.not, tabIndexEq(idx))).map(removeClass('is-active')));
const initTabber = el => {
const tabEls = [buttons, contents] = R.map(R.compose(Array.from, R.flip(qsAll)(el)))(['.js-Tabber-button', '.js-Tabber-body']);
const toRun = R.ap([activateElsWithTabIndex, deactivateElsWithoutTabIndex], tabEls);
const handler = R.compose(R.juxt(toRun), getTabIndex);
Array.from(buttons).forEach(el => el.addEventListener('click', () => handler(el)));
}
Initialized with
initTabber(qs('.js-fp-tabber')(document));
R refers to Ramda. I also wrote a couple general helper functions for the functional version.
const qsAll = R.invoker(1, 'querySelectorAll');
const qs = R.invoker(1, 'querySelector');
const addClass = R.curry((cls, el) => {
el.classList.add(cls);
});
const removeClass = R.curry((cls, el) => {
el.classList.remove(cls);
});