diff --git a/dist/index.html b/dist/index.html index 4cbd19d..e1349fc 100644 --- a/dist/index.html +++ b/dist/index.html @@ -36,7 +36,7 @@

FOUR

- + diff --git a/docs/javascript/vanilla-js-tabs.min.js b/docs/javascript/vanilla-js-tabs.min.js index a45a50d..9334d95 100644 --- a/docs/javascript/vanilla-js-tabs.min.js +++ b/docs/javascript/vanilla-js-tabs.min.js @@ -2,4 +2,4 @@ * Vanilla JavaScript Tabs v1.1.0 * https://zoltantothcom.github.io/vanilla-js-tabs */ -class Tabs{constructor(e){this.titleClass="js-tabs__title",this.activeClass="js-tabs__title-active",this.contentClass="js-tabs__content";var t=document.getElementById(e.elem);null!=t&&(this.elem=t,this.open=e.open||0,this.tabsNum=this.elem.querySelectorAll("."+this.titleClass).length,this.render())}render(e){this.elem.addEventListener("click",this.onClick);var t=null==e?this.checkTab(this.open):this.checkTab(e);for(let e=0;e{e.style.display="none"}),[].forEach.call(this.elem.querySelectorAll("."+this.titleClass),e=>{e.className=this.removeClass(e.className,this.activeClass)})}removeClass(e,t){t=new RegExp(`(\\s|^)${t}(\\s|$)`,"g");return e.replace(t,"")}checkTab(e){return e<0||isNaN(e)||e>=this.tabsNum?0:e}openTab(e){this.reset();e=this.checkTab(e);this.elem.querySelectorAll("."+this.titleClass)[e].className+=" "+this.activeClass,this.elem.querySelectorAll("."+this.contentClass)[e].style.display=""}update(e){this.destroy(),this.reset(),this.render(e)}destroy(){this.elem.removeEventListener("click",this.onClick)}} \ No newline at end of file +let Tabs=function(e){var t=document.getElementById(e.elem);if(!t)throw new Error(`Element with ID "${e.elem}" not found`);const n=t;let l=e.open||0;const r="js-tabs__title",a="js-tabs__title-active",c="js-tabs__content",o=n.querySelectorAll("."+r).length;function s(e){n.addEventListener("click",i);var t=d(null==e?l:e);for(let e=0;e{e.style.display="none"}),[].forEach.call(n.querySelectorAll("."+r),e=>{e.className=function(e,t){t=new RegExp(`(\\s|^)${t}(\\s|$)`,"g");return e.replace(t,"")}(e.className,a)})}function d(e){return e<0||isNaN(e)||e>=o?0:e}function f(e){u();e=d(e);n.querySelectorAll("."+r)[e].classList.add(a),n.querySelectorAll("."+c)[e].style.display=""}function y(){n.removeEventListener("click",i)}return s(),{open:f,update:function(e){y(),u(),s(e)},destroy:y}}; \ No newline at end of file diff --git a/src/index.pug b/src/index.pug index 114e28a..2bb7661 100644 --- a/src/index.pug +++ b/src/index.pug @@ -43,7 +43,7 @@ html(lang="en") script(src="vanilla-js-tabs.min.js") script. - var tabs = new Tabs({ + var tabs = Tabs({ elem: 'tabs', open: 1 }); diff --git a/src/javascript/vanilla-js-tabs.ts b/src/javascript/vanilla-js-tabs.ts index f5a0c4d..cdba8a4 100644 --- a/src/javascript/vanilla-js-tabs.ts +++ b/src/javascript/vanilla-js-tabs.ts @@ -1,100 +1,154 @@ +/** + * @fileOverview + * @author Zoltan Toth + * @version 2.0.0 + */ + +/** + * @description + * Vanilla Javascript Tabs + * + * @class + * @param {string} options.elem - HTML id of the tabs container + * @param {number} [options.open = 0] - Render the tabs with this item open + */ + interface TabsOptions { elem: string; open?: number; } -class Tabs { - private elem: HTMLElement; - private open: number; - private titleClass: string = "js-tabs__title"; - private activeClass: string = "js-tabs__title-active"; - private contentClass: string = "js-tabs__content"; - private tabsNum: number; +interface Tabs { + open: (n: number) => void; + update: (n: number) => void; + destroy: () => void; +} - constructor(options: TabsOptions) { - const elem = document.getElementById(options.elem); +let Tabs = function (options: TabsOptions): Tabs { + const el: HTMLElement | null = document.getElementById(options.elem); + if (!el) throw new Error(`Element with ID "${options.elem}" not found`); - if (elem == null) { - return; - } + const elem = el; - this.elem = elem; - this.open = options.open || 0; - this.tabsNum = this.elem.querySelectorAll("." + this.titleClass).length; - this.render(); - } + let open: number = options.open || 0; + const titleClass: string = "js-tabs__title"; + const activeClass: string = "js-tabs__title-active"; + const contentClass: string = "js-tabs__content"; + const tabsNum: number = elem.querySelectorAll(`.${titleClass}`).length; + + render(); - private render(n?: number): void { - this.elem.addEventListener("click", this.onClick); + /** + * Initial rendering of the tabs. + */ + function render(n?: number): void { + elem.addEventListener("click", onClick); - const init = n == null ? this.checkTab(this.open) : this.checkTab(n); + const init = n == null ? checkTab(open) : checkTab(n); - for (let i = 0; i < this.tabsNum; i++) { - this.elem - .querySelectorAll("." + this.titleClass) - [i].setAttribute("data-index", i.toString()); - if (i === init) this.openTab(i); + for (let i = 0; i < tabsNum; i++) { + (elem.querySelectorAll(`.${titleClass}`)[i] as HTMLElement).setAttribute( + "data-index", + i.toString() + ); + if (i === init) openTab(i); } } - private onClick(e: MouseEvent): void { - if (!(e.target instanceof HTMLElement)) return; - if (e.target.className.indexOf(this.titleClass) === -1) return; + /** + * Handle clicks on the tabs. + * + * @param {object} e - Element the click occured on. + */ + function onClick(e: MouseEvent): void { + const target = e.target as HTMLElement; + if (!target || target.className.indexOf(titleClass) === -1) return; e.preventDefault(); - - const target = e.target.getAttribute("data-index"); - if (target == null) { - return; - } - - this.openTab(parseInt(target)); + openTab(parseInt(target.getAttribute("data-index") || "0")); } - private reset(): void { + /** + * Hide all tabs and re-set tab titles. + */ + function reset(): void { [].forEach.call( - this.elem.querySelectorAll("." + this.contentClass), + elem.querySelectorAll(`.${contentClass}`), (item: HTMLElement) => { item.style.display = "none"; } ); [].forEach.call( - this.elem.querySelectorAll("." + this.titleClass), + elem.querySelectorAll(`.${titleClass}`), (item: HTMLElement) => { - item.className = this.removeClass(item.className, this.activeClass); + item.className = removeClass(item.className, activeClass); } ); } - private removeClass(str: string, cls: string): string { + /** + * Utility function to remove the open class from tab titles. + * + * @param {string} str - Current class. + * @param {string} cls - The class to remove. + */ + function removeClass(str: string, cls: string): string { const reg = new RegExp(`(\\s|^)${cls}(\\s|$)`, "g"); return str.replace(reg, ""); } - private checkTab(n: number): number { - return n < 0 || isNaN(n) || n >= this.tabsNum ? 0 : n; + /** + * Utility function to remove the open class from tab titles. + * + * @param n - Tab to open. + */ + function checkTab(n: number): number { + return n < 0 || isNaN(n) || n >= tabsNum ? 0 : n; } - public openTab(n: number): void { - this.reset(); + /** + * Opens a tab by index. + * + * @param {number} n - Index of tab to open. Starts at 0. + * + * @public + */ + function openTab(n: number): void { + reset(); - const i = this.checkTab(n); + const i = checkTab(n); + elem.querySelectorAll(`.${titleClass}`)[i].classList.add(activeClass); ( - this.elem.querySelectorAll("." + this.titleClass)[i] as HTMLElement - ).className += " " + this.activeClass; - ( - this.elem.querySelectorAll("." + this.contentClass)[i] as HTMLElement + elem.querySelectorAll(`.${contentClass}`)[i] as HTMLElement ).style.display = ""; } - public update(n: number): void { - this.destroy(); - this.reset(); - this.render(n); + /** + * Updates the tabs. + * + * @param {number} n - Index of tab to open. Starts at 0. + * + * @public + */ + function update(n: number): void { + destroy(); + reset(); + render(n); } - public destroy(): void { - this.elem.removeEventListener("click", this.onClick); + /** + * Removes the listeners from the tabs. + * + * @public + */ + function destroy(): void { + elem.removeEventListener("click", onClick); } -} + + return { + open: openTab, + update, + destroy, + }; +}; diff --git a/test/spec/tabs.spec.ts b/test/spec/tabs.spec.ts index c83f4a7..08a9697 100644 --- a/test/spec/tabs.spec.ts +++ b/test/spec/tabs.spec.ts @@ -2,7 +2,7 @@ const fixturePath: string = "base/test/spec/fixtures"; const tabsFixture: string = "tabs.fixture.html"; interface Tabs { - openTab: (n: number) => void; + open: (n: number) => void; update: (n: number) => void; destroy: () => void; } @@ -12,10 +12,10 @@ describe("TABS", function () { jasmine.getFixtures().fixturesPath = fixturePath; loadFixtures(tabsFixture); - const tabsInstance: Tabs = new Tabs({ + const tabsInstance = Tabs({ elem: "tabs", open: -123, - }); + }) as Tabs; this.tabs = tabsInstance; }); @@ -47,8 +47,8 @@ describe("TABS", function () { }); describe("methods", function () { - it("should have .openTab() method", function () { - expect(typeof this.tabs.openTab).toBe("function"); + it("should have .open() method", function () { + expect(typeof this.tabs.open).toBe("function"); }); it("should have .update() method", function () { @@ -65,21 +65,21 @@ describe("TABS", function () { jasmine.getFixtures().fixturesPath = fixturePath; loadFixtures(tabsFixture); - const tabsInstance: Tabs = new Tabs({ + const tabsInstance: Tabs = Tabs({ elem: "tabs", }); this.tabs = tabsInstance; }); - it("should default to 1st tab when .openTab() argument is invalid", function () { - this.tabs.openTab(-123); + it("should default to 1st tab when .open() argument is invalid", function () { + this.tabs.open(-123); expect($(".js-tabs__title")[0]).toHaveClass("js-tabs__title-active"); }); - it(".openTab(2) should open the 3rd tab", function () { + it(".open(2) should open the 3rd tab", function () { expect($(".js-tabs__title")[2]).not.toHaveClass("js-tabs__title-active"); - this.tabs.openTab(2); + this.tabs.open(2); expect($(".js-tabs__title")[2]).toHaveClass("js-tabs__title-active"); }); @@ -119,7 +119,7 @@ describe("TABS", function () { }); it("should ignore any clicks in the content blocks", function () { - this.tabs.openTab(2); + this.tabs.open(2); expect($(".js-tabs__title")[2]).toHaveClass("js-tabs__title-active"); const spyEvent = spyOnEvent(".js-tabs__content", "click");