/**
BogoTabs is a rudimentary tabbed panels implementation for
jQuery. It is "feature-lite" but also quite simple to use.
ACHTUNG: this UI element does not degrade gracefully when JS is not
available.
It was inspired by the idTabs plugin, but i had problems with that
plugin in the Konqueror browser and i find its overall technique a
bit iffy because it uses implied tab identifiers instead of
explicit ones, making the code more difficult to understand (IMO).
BogoTabs is used like this:
$(selector).initBogoTabs( { TAB_DEFINITIONS (see below) }, {options} );
The $(selector) should resolve to a single element which is
(ideally) empty. That element will become the parent element of the
tabbed "panels".
A tab definition looks like:
'#TabID' : { label:'Tab Label' ...optional fields... }
The .label field may contain HTML, but adding an A element to catch
a click might interfere with the tab selection click handling. It
is legal to use, e.g., an IMG element.
Optional fields for a tab definition:
.selected = boolean. If this is set to a true value, that tab will
be the one which gets initially selected. If multiple tabs define
this, only the last one to define it is selected. If no tabs define
it, the first tab is selected by default.
.suppressButton = bool (default=false). If this is set to true then
no button is generated for navigating to this tab! This can be useful
when you want to have certain tabs that are only activated via custom UI
components.
.onhide = function(tabJQObject) is called just AFTER a tab is hidden,
and is passed a jQuery object wrapping the tab.
.onshow = function(tabJQObject) is called just AFTER a tab is shown,
and is passed a jQuery object wrapping the tab.
.onselect = function(tabJQObject) is called just before .onshow is
(or would be) called, and it is passed the tab jQuery object which
is about to be shown.
Some care is taken to ensure that the .onXXX functions are not
fired when re-selecting the currently active tab.
The general options (2nd argument) are all optional:
{
activeLabelClass:'CSSClassNameForActiveLabel', // default='bogoTabsActiveLabel'
inactiveLabelClass:'CSSClassNameForInactiveLabel' // default='bogoTabsInactiveLabel'
}
An example:
======================================================================
$('#TabMarker1').initBogoTabs({
'#Tab1': {
label:'First Tab'
},
'#Tab2': {
label:'Second Tab'
},
'#Tab3': {
label:'Third Tab',
onselect: function(tab){ tab.append("added by onselect() handler
"); }
}
},{
activeLabelClass:'activeTabLabel',
inactiveLabelClass:'inactiveTabLabel'
});
...
The first tab.
The second tab.
The third tab.
======================================================================
It is possible to programmatically activate a tab by doing:
$('#MyTabID')[0].activateTab()
That can be added to, e.g., an A or BUTTON element:
My tab
This is functionally equivalent to clicking on the tab header
associated with the tab.
When tabs are initialized, the following happens, in no specific
order:
a) Each tab label is added inside a new SPAN element directly
BEFORE the tab container. Each SPAN gets a click handler installed
which will trigger the tab switching process. The look/feel of
these SPAN elements can be customized via the activeLabelClass and
inactiveLabelClass options.
b) All tabs associated with the given IDs (from the tab
definitions) are hidden and then appended, in the order of their
definition, to the tab container. This means they can be defined
anywhere in the HTML code, and they will be relocated during
initialization.
When a tab is selected ("activated"), the following happens:
1) If tab is already selected, no side effects happen and
processing stops here.
2) Active tab is hidden, then onhide callback (if any) is called.
3) The label associated with the selected tab has the
options.activeLabelClass class added to it and the
options.inactiveLabelClass class removed from it.
4) The labels NOT associated with the selected tab have the
options.activeLabelClass class removed from them and the
options.inactiveLabelClass class added to them.
5) The onselect callback, if any, is called for the new active
tab.
6) New tab is shown, then the onshow callback, if any, is called
for the new active tab.
BogoTabs home page: http://wanderinghorse.net/computing/javascript/
License: Public Domain
Author: stephan beal (http://wanderinghorse.net/home/stephan/)
Terse revision history (newest at the top):
20070715:
- Added .suppressButton option.
20070714:
- Lots of internal refactoring/cleanups.
- Added .onshow/.onhide support.
- Can now programmatically activate a tab via tabElement.activateTab().
- First release via the official JQ plugin repository.
20070712: initial release
======================================================================
TODO:
- Consider re-doing the way the links are created, so that it will
degrade gracefully. This probably isn't gonna happen, though.
*/
jQuery.fn.initBogoTabs = function( tabdefs, props ) {
props = jQuery.extend({
activeLabelClass:'bogoTabsActiveLabel',
inactiveLabelClass:'bogoTabsInactiveLabel',
debugBogoTabs:false
}, props);
var self = this;
self.dbgdiv = null;
/** Internal debuggering function. */
function dbg(msg) { if( self.dbgdiv ) self.dbgdiv.append("BogoTabs: "+msg+"
"); };
if( props.debugBogoTabs ) {
this.after("BogoTabs debugging area
");
self.dbgdiv = jQuery('#bogoTabsDebugDiv');
self.dbgdiv.css('border','1px dashed #000');
dbg("debugging activated.");
}
/** Internal holder type to keep our scoping straight. */
function TabsInnerHolder() {
var self = this;
self.buttons = []; // map: #TabID => jQ_tab_label_obj
self.funcs = {}; // map: #TabID => { onselect:func,onshow:func,onhide:func}
self.tabs = {}; // map: #TabID => jqTabObject
self.currentTab = null; // tab jQ object
};
self.holder = new TabsInnerHolder();
/** Perform tab switch. tab='#TabIdentifier'. Always returns false. */
function switchTabs(tabID) {
if( tabID[0] != '#' ) tabID = '#'+tabID; // kludge
var tab2show = self.holder.tabs[tabID];
if( ! tab2show ) {
throw new Error("bogotabs: internal error: could not find tab '"+tabID+"'.");
// throwing is reportedly "Not the jQuery Way", but i really want to throw here.
}
dbg('switchTabs('+tabID+')');
if( self.currentTab )
{
var oldid = self.currentTab.attr('id');
if( '#'+oldid == tabID )
{
dbg("Skipping tab activation: tab '"+tabID+"' already active.");
return false;
}
self.currentTab.hide();
var oh = self.holder.funcs['#'+oldid];
if( oh.onhide ) {
dbg("Calling onhide handler for tab "+ oldid+".");
(oh.onhide)( self.currentTab );
}
}
for( var key in self.holder.buttons ) {
var span = self.holder.buttons[key];
if( ! span ) continue; // suppressButton was used
if( key == tabID ) {
span.removeClass(props.inactiveLabelClass)
.addClass(props.activeLabelClass);
} else {
span.removeClass(props.activeLabelClass)
.addClass(props.inactiveLabelClass);
}
}
var funcs = self.holder.funcs[tabID];
if( funcs.onselect ) {
(funcs.onselect)(tab2show);
}
tab2show.show();
if( funcs.onshow ) {
dbg("Calling onshow handler for tab "+tabID+".");
(funcs.onshow)(tab2show);
}
self.currentTab = tab2show;
return false;
};
var tab2select = null;
for( var key in tabdefs ) {
if( ! tab2select ) tab2select=key;
var tab = jQuery(key);
self.holder.tabs[key] = tab;
var tabdef = tabdefs[key];
if( tabdef['selected'] ) tab2select = key;
tab.hide();
this.append(tab);
self.holder.funcs[key] = {
'onselect': tabdef.onselect,
'onshow': tabdef.onshow,
'onhide': tabdef.onhide
};
tab[0].activateTab = function(){
dbg(key+": activateTab(): "+this.id);
return switchTabs( this.id );
};
if( tabdef['suppressButton'] ) {
continue;
}
var lbl = jQuery("");
self.holder.buttons[key] = lbl;
lbl[0].tabElem = tab[0];
lbl.html( tabdef['label'] ? tabdef['label'] : key )
.css('cursor','pointer')
.click(function(){
return this.tabElem.activateTab();
});
this.before(lbl);
}
switchTabs(tab2select);
}; /* initBogoTabs() */