
/**
   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<br/>"); }
	}
    },{
        activeLabelClass:'activeTabLabel',
        inactiveLabelClass:'inactiveTabLabel'
    });

    ...

    <div id='#Tab1'>The first tab.</div>
    <div id='#Tab2'>The second tab.</div>
    <div id='#Tab3'>The third tab.</div>

   ======================================================================

   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:

   <a href='#' onclick="$('#MyTab')[0].activateTab()">My tab</a>

   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+"<br/>"); };
	if( props.debugBogoTabs ) {
		this.after("<div id='bogoTabsDebugDiv'>Debugging area</div>");
		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("<span/>");
		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() */
