/** 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. 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: .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 an already-opened tab, but it is technically possible for the events to be fired in that case if client code does some backhanded things. .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. 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: - Lots of internal refactoring/cleanups. - Added .onshow/.onhide support. - Can now programmatically activate a tab via tabElement.activateTab(). 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("
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.currentTab = null; // tab jQ object }; self.holder = new TabsInnerHolder(); /** Perform tab switch. tab='#TabIdentifier' or '#TabIdentifier-tabtrigger'. Always returns false. */ function switchTabs(tabID) { if( tabID[0] != '#' ) tabID = '#'+tabID; // kludge tabID = tabID.replace(/-tabtrigger/, ''); dbg('switchTabs('+tabID+')'); if( self.currentTab ) { var oldid = self.currentTab.attr('id'); if( '#'+oldid == tabID ) { dbg("Skipping tab activated: 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 ); } } // if( self.currentTab ) { // if( self.currentTab.attr('id') // } var tab2show = null; for( var key in self.holder.buttons ) { var span = self.holder.buttons[key]; if( key == tabID ) { tab2show = jQuery(key); 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); var tabdef = tabdefs[key]; if( tabdef['selected'] ) tab2select = key; tab.hide(); this.append(tab); var lbl = jQuery(""); self.holder.buttons[key] = lbl; // reminder: lbl.attr('href') returns a different value than lbl[0].href!!! 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 ); }; lbl[0].tabElem = tab[0]; lbl.html( tabdef['label'] ) .css('cursor','pointer'); lbl.click(function(){ return this.tabElem.activateTab(); }); this.before(lbl); } switchTabs(tab2select); }; /* initBogoTabs() */