
/**
   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:

   .onselect = function(tabJQObject) is called just before the tab is
   displayed, and it is passed the tab jQuery object which is about to
   be shown. It is not fired when an already-selected tab is
   re-selected.

   .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>

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


   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, the following happens:

   1) The labels associated with the selected tab has the
   options.activeLabelClass class added to it and the
   options.inactiveLabelClass class removed from it.

   2) The labels NOT associated with the selected tab have the
   options.activeLabelClass class removed from them and the
   options.inactiveLabelClass class added to them.

   3) Inactive tabs are hidden.

   4) Active tab is shown.

   5) onselect handler is called, if any. Note that it is not called
   if we do not actually switch tabs (i.e. current tab is
   re-selected).


   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):

   20070712: initial release


  ======================================================================
  TODO:

  - Find a way to trigger tabs from other buttons/links, so that a
  user can link to a specific tab, or trigger a tab switch, from
  arbitrary code.

  - 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;
	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.tabs = [];
		self.links = [];
		self.onselect = [];
		self.currentTab = null;
	};
	self.holder = new TabsInnerHolder();
	/** Perform tab switch. tabID='#TabIdentifier' */
	function switchTabs(tabID) {
		dbg('switchTabs('+tabID+')');
		var tab2show = null;
		var tabs2hide = [];
		for( var k = 0; k < self.holder.tabs.length; ++k ) {
			var key = self.holder.tabs[k];
			if( key == tabID ) {
				tab2show = jQuery(key);
				(self.holder.links[key]).
					removeClass(props.inactiveLabelClass).
					addClass(props.activeLabelClass);
			} else {
				tabs2hide[tabs2hide.length] = jQuery(key);
				(self.holder.links[key]).
					removeClass(props.activeLabelClass).
					addClass(props.inactiveLabelClass);
			}
		}
		if( self.currentTab &&
		    (self.currentTab.attr('id') == tab2show.attr('id'))) {
			dbg("Skipping switch - tab already selected.");
			return;
		}

		jQuery(tabs2hide).each(function(){this.hide()});
		if( self.holder.onselect[tabID] ) {
			(self.holder.onselect[tabID])(tab2show);
		}
		tab2show.show();
		self.currentTab = tab2show;
	};

	var tab2select = null;
	for( var key in tabdefs ) {
		if( ! tab2select ) tab2select=key;
		var tab = jQuery(key);
		var attr = tabdefs[key];
		if( attr['selected'] ) tab2select = key;
		tab.hide();
		this.append(tab);
		var lbl = //jQuery("<a href='"+key+"'/>");
			jQuery("<span tabid='"+key+"'/>");
		// ^^^^ i don't think that adding custom fields is strictly legal,
		// but i don't see a nice workaround other than that due to scoping
		// issues (or my misunderstanding of them).
		lbl.css('cursor','pointer');
		self.holder.tabs[self.holder.tabs.length] = key; //lbl.attr('href');
		self.holder.links[key] = lbl;
		self.holder.onselect[key] = attr.onselect;
		// reminder: lbl.attr('href') returns a different value than lbl[0].href!!!
		//dbg( '.attr(href) = '+lbl.attr('href')+' but lbl[0].href='+lbl[0].href);
		//dbg('self.holder.tabs = '+self.holder.tabs.toSource());
		lbl.html( attr['label'] );
		lbl.click(function(){
			  dbg("Calling switch: "+jQuery(this).attr('tabid'));
			  switchTabs(jQuery(this).attr('tabid'));
			  return false;
		});
		this.before(lbl);
	}
	switchTabs(tab2select);

}; /* initBogoTabs() */
