Update: I have created a new page dedicated to xaseTabs: xaseTabs.noelboss.ch
Tabs (re)evented
Today I released my «Xtensible And Simple Evented Tabs» Plugin on jQuery.com. This plugin does one thing – and one thing very good: creating tab functionality. It does not provide any styling or has any superfluous effects but instead focuses on extensibility and flexibility.
By default, the xaseTabs plugin turns a list of elements and a bunch of div’s into a tab module:
<div> <ul class="tabs"> <li>Tab 1</li> <li>Tab 2</li> <li>Tab 3</li> </ul> <div> Panel 1 </div> <div> Panel 2 </div> <div> Panel 3 </div> </div>
All you need to do, to have this behave as tabs, are the following lines of code:
$(document).ready(function(){
$('.tabs').xaseTabs();
});
The plugin is based on custom events which can be extended or overwritten without any changes of the plugin itself. The possibilities are endless; adding ajax to load the panel content, triggering a lightbox-resize after activating a tab, using the tabs as process-navigation and prevent clicks to the next steps… It’s all in your hands. Additionally you can provide some options as an object to customize the behavior of the plugin without writing you own extension.
Features
- As simple as it gets.
- Extremely extensible thanks to custom events.
- Preload specific tabs via URL-Hashes. This even works with multiple Tab instances on a single page since you can define what instance should be selected.
- Customizable trigger event: Define what event causes the trigger of activating a new tab. Changing tabs on hover? No problem.
- Completely control how your tabs are activated: You can even use the xaseTabs plugin to create acordeon-style modules by overwriting the “activate” event.
- Completely control how your tabs look: xaseTabs comes with no CSS and HTML requirements whatsoever.
- Nice Code. xaseTabs validate with jslint. and the development code is well commented.
- xaseTabs is very Lightweight. It’s less than 4KB packed
Download and Installation
You can download xaseTabs from google code. Then go ahead and include your copy of jQuery and xaseTabs at the bottom of your page. Replace the path of the xaseTabs Plugin with whatever folder you have put the plugin in. Then, add you custom code to initialize the xaseTabs:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js" type="text/javascript"> </script>
<script src="http://www.yourserver.com/js/jquery.xaseTabs.latest.js" type="text/javascript"> </script>
<script type="text/javascript">
$('.tabs').xaseTabs({
extend: xaseTabsExtension
});
</script>
You can also checkout a working copy from the xaseTabs SVN Repository.
Options & Defaults
xaseTabs comes with a set of options to allow maximum flexibility – besides the ability to rewrite nearly the entire plugin via custom events. You can alter the defaults by providing your options as an object to xaseTabs:
$('.tabs').xaseTabs({
tabSelector: 'li a',
activeClass: 'act',
autoInitialize: false
});
- namespace {string}
- Default: ‘xaseTabs’ – Namespace, will be added to the all events and url-hashes…
- tabSelector {string}
- Default: ‘li’ – The selector of tabs relative to the element the xaseTabs plugin is called on. Default is an “li” inside the selected tab ul.
- panelSelector {string}
- Default: ‘~ div’ – The Selector of panels relative to the element the xaseTabs plugin is called on. Default are siblings of the selected tab ul.
- activeAttribute {string}
- Default: ‘selected’ – Attribute to check if predefined tab should be selected via url-hash. Could also be “#”.
- activeClass {string}
- Default: ‘active’ – Class name that’s assigned to active tabs and panels.
- triggerEvent {string}
- Default: ‘click’ – Event that triggers the activate event. Namespace will be added.
- activateBindFunction {function} (not implemented yet)
- Default: null – If you want to bind the activate event on yourself you can use this function
- hashAttribute {string}
- Default: ‘.’ – CSS-Selector Attribute that will be checked to match a URL hash (#xaseTabsHASHATTR-1) for activating a specific tab. Alternative: “#”.
- autoInitialize {boolean}
- Default: true – If you want to trigger the initialization phase manually, set this flag to false.
- extend {function}
- Default: null – Here, you can pass a function that extends the default customEvents object with your own functions. Please reade the next chapters for detailed instruction on how to create such a function.
How to Extend the Plugin
xaseTabs is based on custom events – that’s why it’s so extensible. The custom events life inside a private customEvents object:
var customEvents = {
initialize: [function(options) {
this.bind("initialize." + options.namespace,
function(e) {
// ...
});
}],
setupPanels: [function(options) {
this.bind("setupPanels." + options.namespace,
function(e) {
// ...
});
}],
setupTabs: [function(options) {
this.bind("setupTabs." + options.namespace,
function(e) {
// ...
});
}],
activate: [function(options) {
this.bind("activate." + options.namespace,
function(e, selected) {
// ...
});
}]
};
For a detailed description of this functions, read the next chapter “Custom Events – here to serve you“. Those events will be bound onto the calling element upon initialisation. Before the binding happens, you can extend that array and add your own functions or disable the default functions altogether with e.preventDefault(); How would you do this? Simply create a function that gets the “customEvents” object passed and add this to the configuration object inside the “extend” key. Within the customEvents object you can push or unshift new functions into the function arrays. Lets say you have the Tabs-Plugin inside a lightbox – and that lightbox should adapt the size of the panel upon changing a tab. You would simply insert a function that calls the resize function after the default activate function has been triggered. So you push a function to the end of the customEvents.activate array that will be called after the tab has been activated:
// Create a functions that receives the customEvents
var xaseTabsExtension = function(customEvents){
// Select the desired function and add functionality:
customEvents.activate.push(function(options) {
// Bind your new extension to the Element that calls
// the tabs (not the individual tabs!)
this.bind("activate." + options.namespace,
function(e, selected) {
// Do whatever you like upon the "activate"
// event has been triggered
$.fancybox.resize();
});
});
// don't forget to return the modified event Object
return customEvents;
}
After you have done so, you can call the xaseTabs function and pass this function withhin your options-object inside the extend key:
$('.tabs').xaseTabs({
extend: xaseTabsExtension
});
If you’d like to add ajax functionality, you would unshift (instead of push) the customEvents.activate array, and add this ajax-function before the default function to loads the content dynamically…
For the concept behind evented progarming, read this article by Yehuda Katz.
Custom Events – here to serve you
The xaseTabs plugin has four events that you can extend. In every event, you have the following variables at hand:
- this {object}
- Contains the object on which the xaseTabs plugin was called. “this” contains also all relations to the individual tabs and panels.
- $(this).data(‘$tabs’) {object}
- Contains all tabs associated with this specific tabs instance. Each tab contains a reference to the according panel via $tab.data(‘$panel’)
- $(this).data(‘$panels’) {object}
- Contains all panels associated with this specific tabs instance.
- $(this).data(‘$tabs’).eq(0).data(‘$panel’) {object}
- Each tab contains a reference to the according panel via .data(‘$panel’). With this reference, you always know what panel belongs to which tab.
- initialize
- setupPanels
- setupTabs
- activate
Handles the initialization of the plugin: finding the panes and tabs, and triggering the setup events and activate the initial tab:
- options {object}
- Options provided to the Plugin including Defaults
- e {object}
- Event Object, call e.isDefaultPrevented() to check if Default is prevented
initialize: [function(options) {
this.bind("initialize." + options.namespace,
function(e) {
if (e.isDefaultPrevented()) {
return;
}
// caching an get cached elements from tabs-element
var $t = $(this);
var $tabs = $(options.tabSelector, $t);
var $panels = $(options.panelSelector, $t);
var $selected = $tabs.filter('.' + options.activeClass).eq(0);
// We cache the tabs and the panels directly onto the element so with
// $(this).data('$tabs') you always know what your tabs and panels are...
$t.data('$panels', $panels).data('$tabs', $tabs);
// setting up individual tabs
$t.trigger("setupPanels." + options.namespace);
// setting up individual panels
$t.trigger('setupTabs.' + options.namespace);
/**
* check for a hash like this: #xaseTabsTABID-2 where TABID is the Class of this tab-module
* and the number behind the - the Tab to be activated
*/
var hash = window.location.hash.split(options.namespace);
if ($.isArray(hash)) {
for (var i = hash.length - 1; i > 0; i--) {
// loop trough all modules
var module = hash[i].split('-');
// get Tab number and Module ID
if ($t.is(options.hashAttribute + module[0])) {
// if we are the the current module
// update selected Element and add class active
$selected = $(this).data('$tabs').removeClass(options.activeClass).eq(module[1] - 1).addClass(options.activeClass);
}
}
}
// search for predefined activate Elements
$selected = $selected.length < 1 ? $tabs.filter("[" + options.activeAttribute + "=" + options.activeAttribute + "]").eq(0) : $selected;
if ($selected.length < 1) {
// If no element selected, we activate Tab 1
$selected = $tabs.eq(0);
}
// trigger activate Event
if ($selected.length > 0) {
$t.trigger("activate." + options.namespace, $selected);
}
});
}]
Saves the panel to the according tab:
- options {object}
- Options provided to the Plugin including Defaults
- e {object}
- Event Object, call e.isDefaultPrevented() to check if Default is prevented
setupPanels: [function(options) {
this.bind("setupPanels." + options.namespace,
function(e) {
if (e.isDefaultPrevented()) {
return;
}
var $t = $(this);
var $tabs = $t.data('$tabs');
var $panels = $t.data('$panels');
$tabs.each(function(i) {
$(this).data('$panel', $panels.eq(i));
});
});
}]
Binds the custom activate event to the tabs. You can configure, what event triggers the activate-event via the options.triggerEvent option:
- options {object}
- Options provided to the Plugin including Defaults
- e {object}
- Event Object, call e.isDefaultPrevented() to check if Default is prevented
setupTabs: [function(options) {
this.bind("setupTabs." + options.namespace,
function(e) {
var $t = $(this);
var $tabs = $t.data('$tabs');
$tabs.bind(options.triggerEvent + "." + options.namespace,
function() {
$t.trigger("activate." + options.namespace, $(this));
return false;
});
});
}]
This event deactivates all tabs and activates the selected tab. You get a special variable selected that contains the selected tab:
- options {object}
- Options provided to the Plugin including Defaults
- e {object}
- Event Object, call e.isDefaultPrevented() to check if Default is prevented
- selected {object}
- DOM-Object; tab that was selected. You need to jQuerify it before using it: $(selected)
activate: [function(options) {
this.bind("activate." + options.namespace,
function(e, selected) {
if (e.isDefaultPrevented()) {
return;
}
var $t = $(this);
var $selected = $(selected);
$t.data('$panels').hide();
$t.data('$tabs').removeClass(options.activeClass);
$selected.data('$panel').show();
$selected.addClass(options.activeClass);
});
}]
Feedback, Bug-Reports & Feature Requests
If you have any feedback, go ahead and leave a comment, or write me an e-mail to “jquery at noelboss . ch”. If you would like to see a feature added, or if you have found a bug, let me know…
Submit your cool Extensions!
If you have written a cool extension for the xaseTabs plugin – let’s say it now produces coffee on every tab-click –then, send me a copy in order that I can link it here! Some ideas for extension:
- Adding keyboard support
- Saving state of the tabs via cookie
- Adding history support (with BBQ)
- …
F.A.Q.
» How do I manually activate a Tab?
Just select the Tab and call the click event (or whatever event you configured with options.triggerEvent)
$(.tabs li:eq(3)).click();
» How is this accordion thing done on this page?
With the xaseTabs-plugin – of-course…
$(document).ready(function(){
var xaseTabsExtension = function(customEvents) {
customEvents.activate.unshift(function(options) {
this.bind("activate."+options.namespace,
function(e, selected) {
e.preventDefault();
var $t = $(this);
var $selected = $(selected);
var $panel = $selected.data('$panel');
$t.data('$panels').not($panel).slideUp();
$panel.slideDown();
$t.data('$tabs').removeClass("active");
$selected.addClass("active");
});
});
return customEvents;
};
$('.tabs').xaseTabs({
extend: xaseTabsExtension
});
});
» Why don’t you add (insert your feature here) feature to the plugin?
xaseTabs is so extensible that you would be better of implementing that specific feature by yourself – I want to keep xaseTabs lean and clean. If you have a cool extension to this plugin, go ahead and comment or send me an e-mail or leave a comment…
» What does «xase» mean?
eXtensible And Simple Evented. It’s based on “evented programing“
» How can I contact you?
Write a comment or send me an e-mail to jquery at noelboss . ch
» xaseTabs is so cool, can I donate some money?
Course you can – go ahead!
The tab work is freakin bad ass. Attempted 5 different plug-ins before this. Thanks much. Let me know if you need help with bitch work. Owe you one.
Rory, pleased to hear that you like the plugin. Let me know if you need any help or have any suggestions for improvements. If you added some useful extensions to the plugin, please send it too. What version did you use and how did you learn about xaseTabs? Version 0.2.1 has some bugfixes. I might still have an IE6 issue with a-tags… Greetings, Noël
I have just released a new version (0.2.2 Beta) that fixes an IE6 bug and moved the xaseTabs Source over to google-code. I plan to release a new version soon that includes an $.xaseTabs.extend() function to extend the plugin for all tab instances. The goal would be to provide extensions as files that directly extend the plugin with no further code needed – just include the extension-file after the plugin and the plugin gets the new functionality.
Very simple to use and works well! Thanks!
I do have one question…
If I’m using this in a scenario where tabs/panels can be added dynamically, what is the best way to handle this? I don’t think I can just do
$(‘.tabs’).xaseTabs()
after tabs/panels are added.
Is there some way to “unbind” xaseTabs from the wrapped set that it has been applied to?
Thanks again!
mark
Hi Mark
Rebinding is very simple. xaseTab Events are all namespaces so it comes down to this:
$tabs.unbind(‘.xaseTabs’);
$tabs.xaseTabs();
You could also extend the initialize event but this would be a little more effort. I have created an example for you here:
http://cl.ly/656bf90e2f748cc53f41
I plan to release a website with a lot more examples and demos and also with kind of an extension repository. I’m still thinking about a good, simple and standardized way to add and extensions to de Plugin. If anyone has some suggestions let me know. I think of something like this:
$.xaseTabs.extension.ajax{ … };
Cheers,
Noël
Noel:
Thanks for the reply and the great example. I almost forgot that I had asked the question. That will teach me to submit comments on Friday afternoon!
One additional question, though. Using your example, I was able to unbind and rebind without trouble. However, I’m concerned about accumulating events over time, and am not sure that they are being completely unbound.
For example, if I slightly modify the xaseTabs code by putting a console.log(‘activated’); inside the activate: function, I see some strange behavior.
At first, when the page loads, clicking a tab will cause ‘activated’ to be logged to the console, once per click. If I unbind and rebind, however, each tab click will log ‘activate’ twice. If I unbind and rebind again, each tab click will log ‘activate’ three times. This will go on and on, as long as I unbind and rebind.
Does this seem like an issue to you? Any ideas?
I’ve seen this in recent versions of both Chrome and Firefox.
Thanks again,
Mark
Just an update:
I was able to get around it by explicitly unbinding $tabs inside the xaseTabs code. Probably not the best solution.
(~ line 270)
var $tabs = $t.data(‘$tabs’);
$tabs.unbind(); // <—————added this line
$tabs.bind(options.triggerEvent + "." + options.namespace,
Hey Mark,
Yes, one needs to unbind the Event on the individual tabs too. I’m going to release a fix for this later today.
Cheers.
Hey I am trying to use it with innerfade
http://medienfreunde.com/lab/innerfade/
The inner fade is a fading banner (not within xasetab)
they don’t seem to be happy with each other.
Hello Rory, please send me a link to the page you are trying to use those plugins so I can help or describe, what happens.
@ Mark, I have released Version 0.2.4 that add’s unbinding…
hi noel, i have a simple structure:
tab1
tab2
content should be here after tab-click
i tried to load the content to the inner-div (in id=”newsscroller”) but failed. i used the
panelSelector: ‘~ div#newsscroller div’
in different ways (above a sample, also tried just “panelSelector: ‘div#newsscroller div’ etc.
please help me. :-) its important to load the content to these inner-divs because i just want to make the div=newscroller be scrollable, the with tabs should be fixed on screen. :-)
thank you,
matt
mh, damn … the tags and stuff in my comment where deleted …
structure is this:
UL
LI=tab1
LI=tab2
/UL
DIV=#newsscroller
DIV=content from tabs here
/DIV
Hey Noel,
ich bin nun ein Stück weiter, kann die “Tab”-Leiste jetzt feststehen lassen und die Daten der Tabs landen wo sie sollen, allerdings werden alle Daten (habe fünf Tabs) in jeweils komplett untereinander, innerhalb DIV#newsscroller geladen/angezeigt, innerhalb vom DIV#newsscroller gibts allerdings fünf einzelne DIVs, je eins pro Tab.
Die eigentliche Tab-Funktion ist nun durch das setzen vom Panelselector nicht mehr vorhanden, da ständig einfach alle Daten gezeigt werden. :-(
Eine Idee?
Hey Noel,
um die fünf DIVs, in denen jeweils der Tab-Content angezeigt werden soll, ist ein großes, einzelnes Divs aussenherum.
Leider ist das Problem nun noch nicht gelöst. Der Content der in die verschiedenen Divs geladen werden soll, wird nur in eines geladen, und nicht aufgeteilt, somit ist die Klickfunktion auf die Tabs hinfällig, da der gesamte Content bereits angezeigt wird.
Mein Code weicht nur an der Stelle von der Vorgabe ab, dass die fünf Divs für den Content nicht direkt die nächsten Elemente nach dem Tab-Element sind, sondern ihrerseits nochmal von einem Tab umschlossen sind.
Hey, have you got RSS on your blog? I can’t find the link.
Yes, you’ll see it in the address-bar of your browser or – if you click on more (only visible on the main-site, not in article view) – you’ll find the links. Cheers…
I have created a new page to document the xaseTabs plugin. Please take a look at it: http://xasetabs.noelboss.ch/