I've always been partial to writing JavaScript classes that take a root element:
function TabController() {
// Maintain reference to "this" in event handlers
this.toggleTab = this.toggleTab.bind(this);
}
TabController.prototype = {
$element: null,
constructor: TabController,
init: function(element) {
this.$element = $(element).on("click", ".js-tab", this.toggleTab);
return this;
},
toggleTab: function(event) {
var showTab = event.target.getAttribute("data-tab-content");
event.preventDefault();
this.$element.find(".js-tab").each(function() {
var tabContent = this.getAttribute("data-tab-content");
if (tabContent === showTab) {
this.$element.find(".js-tab-" + showTab).removeClass("hide");
} else {
this.$element.find(".js-tab-" + tabContent).addClass("hide");
}
});
}
};
Plus, I like to replace jQuery API calls that wrap cross browser native DOM methods -- e.g. $(foo).data("bar")
most times can be replaced with element.getAttribute("data-bar")
.
And to use:
// using document.documentElement means you don't need a jQuery dom-ready event handler
var tabs = new TabController().init(document.documentElement);
While you technically only need one tabber per page, making this an instantiable class allows you to unit test your JavaScript.
describe("TabController", function() {
var element, tabs, event;
function FauxEvent(type, target) {
this.type = type;
this.target = target;
}
FauxEvent.prototype.preventDefault = function() {};
describe("toggleTab", function() {
beforeEach(function() {
element = document.createElement("div");
tabs = new TabController().init(element);
});
it("shows a tab", function() {
element.innerHTML = [
'<a class="js-tab" data-tab-content="panel-1">Tab 1</a>',
'<div class="js-tab-panel-1 hide"></div>'
].join("");
event = new FauxEvent("click", element.firstChild);
tabs.toggleTab(event);
expect($(element.lastChild).hasClass("hide")).toBe(false);
});
});
});
This makes your code truly modular. You could go a step further and make the CSS class names and data attributes customizable:
function TabController() {
// Maintain reference to "this" in event handlers
this.toggleTab = this.toggleTab.bind(this);
this.options = {
classPrefix: "js-tab",
hiddenClass: "hide",
dataAttribute: "data-tab-content"
};
}
TabController.prototype = {
$element: null,
options: null,
constructor: TabController,
init: function(element, options) {
this.$element = $(element).on("click", "." + this.options.classPrefix, this.toggleTab);
jQuery.extend(this.options, options || {});
return this;
},
toggleTab: function(event) {
var showTab = event.target.getAttribute(this.options.dataAttribute);
event.preventDefault();
this.$element.find("." + this.options.classPrefix).each(function() {
var tabContent = this.getAttribute(this.options.dataAttribute);
if (tabContent === showTab) {
this.$element.find("." + this.options.classPrefix + "-" + showTab).removeClass(this.options.hiddenClass);
} else {
this.$element.find("." + this.options.classPrefix + "-" + tabContent).addClass(this.options.hiddenClass);
}
});
}
};