patternjavascriptModerate
Optimize this jQuery tab controller further
Viewed 0 times
thistabfurthercontrollerjqueryoptimize
Problem
How do I optimize this code further? Goal is to edit "js-tab" in one place. This is what I tried to follow.
Full code here.
Full code here.
var ToggleTab = {
init: function() {
this.tab = $(".js-tab");
this.bindEvents();
},
bindEvents: function() {
this.tab.on("click", this.toggleTab);
//this.tab.on("click", $.proxy(this.toggleTab, this));
},
toggleTab: function(e) {
var showTab = $(this).data("tab-content");
e.preventDefault();
$(".js-tab").each(function() {
if ($(this).data("tab-content") === showTab) {
$(".js-tab-" + showTab).removeClass("hide");
} else {
$(".js-tab-" + $(this).data("tab-content")).addClass("hide");
}
});
}
};
$(function() {
ToggleTab.init();
});Solution
I've always been partial to writing JavaScript classes that take a root element:
Plus, I like to replace jQuery API calls that wrap cross browser native DOM methods -- e.g.
And to use:
While you technically only need one tabber per page, making this an instantiable class allows you to unit test your JavaScript.
This makes your code truly modular. You could go a step further and make the CSS class names and data attributes customizable:
Some sample HTML
JSFiddle: http://jsfiddle.net/CL8Ns/
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 that = this,
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) {
that.$element.find(".js-tab-" + showTab).removeClass("hide");
} else {
that.$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 = [
'Tab 1',
''
].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 that = this,
showTab = event.currentTarget.getAttribute(this.options.dataAttribute);
event.preventDefault();
this.$element.find("." + this.options.classPrefix).each(function() {
var tabContent = this.getAttribute(that.options.dataAttribute);
if (tabContent === showTab) {
that.$element.find("." + that.options.classPrefix + "-"
+ showTab).removeClass(that.options.hiddenClass);
} else {
that.$element.find("." + that.options.classPrefix + "-"
+ tabContent).addClass(that.options.hiddenClass);
}
});
}
};Some sample HTML
Tab 1
Tab 2
Content A
Content B
var tabs = new TabController().init(document.documentElement);
JSFiddle: http://jsfiddle.net/CL8Ns/
Code Snippets
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 that = this,
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) {
that.$element.find(".js-tab-" + showTab).removeClass("hide");
} else {
that.$element.find(".js-tab-" + tabContent).addClass("hide");
}
});
}
};// using document.documentElement means you don't need a jQuery dom-ready event handler
var tabs = new TabController().init(document.documentElement);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);
});
});
});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 that = this,
showTab = event.currentTarget.getAttribute(this.options.dataAttribute);
event.preventDefault();
this.$element.find("." + this.options.classPrefix).each(function() {
var tabContent = this.getAttribute(that.options.dataAttribute);
if (tabContent === showTab) {
that.$element.find("." + that.options.classPrefix + "-"
+ showTab).removeClass(that.options.hiddenClass);
} else {
that.$element.find("." + that.options.classPrefix + "-"
+ tabContent).addClass(that.options.hiddenClass);
}
});
}
};<ul>
<li class="js-tab" data-tab-content="content-a"><a href="#">Tab 1</a></li>
<li class="js-tab" data-tab-content="content-b"><a href="#">Tab 2</a></li>
</ul>
<div class="js-tab-content-a">
Content A
</div>
<div class="js-tab-content-b">
Content B
</div>
<script type="text/javascript">
var tabs = new TabController().init(document.documentElement);
</script>Context
StackExchange Code Review Q#47806, answer score: 12
Revisions (0)
No revisions yet.