"use strict";
/**
*
* Hey there! Here is my *really first* plugin, written using Pure JavaScript / ES5
* Hope you'll enjoy using it!
*
* ToDo: Re-write a plugin using ES6
*
*/
/**
* Start writing a plugin using Immediately Invoked Function Expression (IIFE).
* Function is used to create a private scope: variable, created inside a function
* is not accessible outside the function
*/
(function () {
// Constructor function
const VanillaTabs = function (opts) {
this.options = Object.assign(VanillaTabs.defaults, opts);
this.elems = document.querySelectorAll(this.options.selector);
buildUI(this);
handleNavigation(this);
handleResponsive(this);
};
// skip building tabs if they were already initialized
function skipIfInitialized(tabsElem) {
// skip element if already initialized
if (tabsElem.classList.contains("tabs__initialized")) {
return;
}
}
// Private function to initialize the UI Elements
function buildUI(tabs) {
// walk on all tabs on the page
tabs.elems.forEach(function (el, i) {
let tabsElem = el,
childNodes = tabsElem.childNodes,
tabsTitles = [],
tabsStyle = tabs.options.type;
skipIfInitialized(tabsElem);
tabsElem.classList.add("style__" + tabs.options.type);
tabsElem.classList.add("tabs__initialized");
for (let i = 0; i < childNodes.length; i++) {
let tabItem = childNodes[i];
if (tabItem.nodeType != Node.TEXT_NODE) {
// add tab__content CSS class
tabItem.classList.add("tabs__content");
// grab tab title from data attribute
let tabTitle = tabItem.dataset.title ? tabItem.dataset.title : "";
tabsTitles.push(tabTitle);
// wrap tab content
let tabContent = tabItem.innerHTML;
tabItem.innerHTML =
'
' + tabContent + "
";
// insert nav link for accordion navigation
tabItem.insertAdjacentHTML(
"afterbegin",
'' + tabTitle + ""
);
}
}
// create horizontal / vertical tabs navigation elements
let navElemsHTML = "";
tabsTitles.forEach(function (title) {
navElemsHTML =
navElemsHTML + '' + title + "";
});
tabsElem.insertAdjacentHTML(
"afterbegin",
'' + navElemsHTML + ""
);
// set initial active tab
let activeTabIndex = Number(tabs.options.activeIndex);
// validate active tab index. but, you can specify -1 for accordion tabs to make all of them closed by defaults
if (tabsStyle != "accordion" && activeTabIndex != -1) {
if (activeTabIndex > tabsTitles.length - 1) {
console.warn(
"VANILLA TABS: Active tab number from settings is bigger than tabs count. Please remember, that index starts from Zero! To avoid crashes, activeIndex option was reverted to 0."
);
activeTabIndex = 0;
}
tabsElem
.querySelectorAll(".tabs__nav > .tabs__nav_link")
[activeTabIndex].classList.add("is__active");
tabsElem
.querySelectorAll(".tabs__content")
[activeTabIndex].classList.add("is__active");
tabsElem
.querySelectorAll(".tabs__content > .tabs__nav_link")
[activeTabIndex].classList.add("is__active");
}
});
}
// Navigation: assign click events
function handleNavigation(tabs) {
let tabsStyle = tabs.options.type;
// walk on all tabs on the page
tabs.elems.forEach(function (el, i) {
let tabsElem = el;
skipIfInitialized(tabsElem);
tabsElem.addEventListener("click", function (e) {
if (e.target && e.target.classList.contains("tabs__nav_link")) {
e.preventDefault();
let activeTabIndex;
// if we click on main navigation link
if (e.target.parentElement.classList == "tabs__nav") {
activeTabIndex = Array.prototype.slice
.call(e.target.parentElement.children)
.indexOf(e.target);
// if we click on accordion nav link
} else {
activeTabIndex =
Array.prototype.slice
.call(e.target.parentElement.parentElement.children)
.indexOf(e.target.parentElement) - 1;
}
let tabsContent = tabsElem.getElementsByClassName("tabs__content"),
mainNavLinks = tabsElem.querySelectorAll(
".tabs__nav > .tabs__nav_link"
),
accordionNavLinks = tabsElem.querySelectorAll(
".tabs__content > .tabs__nav_link"
);
// toggle accordion panel
if (
(tabsStyle == "accordion" ||
tabsElem.classList.contains("is__responsive")) &&
e.target.classList.contains("is__active")
) {
tabsContent[activeTabIndex].classList.remove("is__active");
mainNavLinks[activeTabIndex].classList.remove("is__active");
accordionNavLinks[activeTabIndex].classList.remove("is__active");
return;
}
// remove active class for inactive tabs
for (let i = 0; i < tabsContent.length; i++) {
tabsContent[i].classList.remove("is__active");
}
// add active class for a current (active) tab
tabsContent[activeTabIndex].classList.add("is__active");
// add active classes and remove inactive for main nav links
mainNavLinks.forEach(function (el) {
el.classList.remove("is__active");
});
mainNavLinks[activeTabIndex].classList.add("is__active");
// add active classes and remove inactive for accordion nav links
accordionNavLinks.forEach(function (el) {
el.classList.remove("is__active");
});
accordionNavLinks[activeTabIndex].classList.add("is__active");
}
});
});
}
// Responsive: tabs to accordion
function handleResponsive(tabs) {
const responsiveClassName = "is__responsive",
tabsStyle = tabs.options.type;
window.addEventListener("resize", function () {
// walk on all tabs on the page
tabs.elems.forEach(function (el, i) {
let tabsElem = el,
tabsContent = tabsElem.getElementsByClassName("tabs__content"),
mainNavLinks = tabsElem.querySelectorAll(
".tabs__nav > .tabs__nav_link"
),
accordionNavLinks = tabsElem.querySelectorAll(
".tabs__content > .tabs__nav_link"
);
skipIfInitialized(tabsElem);
if (window.innerWidth > Number(tabs.options.responsiveBreak)) {
tabsElem.classList.remove(responsiveClassName);
if (tabsStyle != "accordion") {
// set first active tab if all of tabs were closed in accordion mode
let openTabs = tabsElem.querySelectorAll(
".tabs__nav_link.is__active"
);
if (openTabs.length == 0) {
tabsContent[0].classList.add("is__active");
mainNavLinks[0].classList.add("is__active");
accordionNavLinks[0].classList.add("is__active");
}
}
} else {
tabsElem.classList.add(responsiveClassName);
}
});
});
// manually fire resize event
window.dispatchEvent(new Event("resize"));
}
// Attach our defaults for plugin to the plugin itself
VanillaTabs.defaults = {
selector: ".tabs",
type: "horizontal",
responsiveBreak: 840,
activeIndex: 0,
};
// make accessible globally
window.VanillaTabs = VanillaTabs;
})();