/* 

pdx.prototype.gadgets.submenumgr: prototype-based submenu display
  v1.0, 2011/03/14, initial release
  v1.1, 2011/05/30, options.addActiveClass added, fixed to include descendants
    of the main control element in the hover action.

  CC BY-SA 2011. Andras Kemeny (http://www.pdx.hu/)
    http://creativecommons.org/licenses/by-sa/3.0/legalcode
  NO WARRANTIES! however, it's tested on IE7/8, Firefox 3.5.x, Opera 10+,
    Chrome & Safari, and it works.
  REQUIRES prototype.js to work.
    http://www.prototypejs.org/
  REQUIRES script.aculo.us to work, if you use appeareance effects.
    http://script.aculo.us/

general info:

scans for main menu items (which are A tags) that contain a startsubmenu="<id>"
attribute, and makes any blocks that have a corresponding id get displayed as
a submenu when the mouse is hovered above the main menu item (also, when it is
hovered above the open submenu). it does this with grace and style, with con-
figurable offsets and effects and opacity etc.

the submenus are removed from the main document flow and are positioned absolu-
tely as children to the body tag.

if you use the effects provided by this object, relax: they're queued and they
have their own scope.

a tree example:
  <ul role="mainMenu">
   <li><a href="<link>" startsubmenu="submenu1">main option 1</a>
    <ul id="submenu1" role="subMenu">
     <li><a href="<sublink1>">suboption 1/1</a></li>
     <li><a href="<sublink2>">suboption 1/2</a></li>
     ...
    </ul>
   </li>
   <li><a href="<link>" startsubmenu="submenu2">main option 2</a>
    <ul id="submenu2" role="subMenu">
     <li><a href="<sublink1>">suboption 2/1</a></li>
     <li><a href="<sublink2>">suboption 2/2</a></li>
     ...
    </ul>
   </li>
  </ul>

currently, it doesn't support nesting, but a future release might. (it was a
nightmare still to get it properly working on IE. seriously, IE developers,
WHAT THE HELL WERE YOU THINKING?!...end rant.)

usage:

1. prepare the structures above.
2. submenumgr does an auto-initialize phase (preinit()) at dom:loaded to clear
   up any misunderstandings.
3. on window.load call submenumgr.init([{options}]);
4. enjoy!
5. if you want to change the defaults, see comment at the end of this file.
 
submenumgr.options(: default):
 
  cleanSubMenus: true
    if true, it removes all whitespace clutter within the submenu. if 'full',
    it removes all text and comment nodes from between elements, too. if false,
    it leaves the internal structure of the submenus intact.
  offsetTop: 70
    top offset calculated from the top left position of the calling main menu
    item.
  offsetLeft: 8
    same as above, only for left offset.
  selectaMain: 'A[startsubmenu]'
    the CSS selector pattern for any main menu elements.
  selectaSub: '[role=subMenu]'
    the CSS selector pattern for any submenu containers.
  submenuOpacity: 0.9
    guess what! :)
  submenuZindex: 1500
    guess again! :)
  addActiveClass: 'activated'
    what classname to add for main menu options whose submenu is activated. if
    set to false, no classname is added.
  idleTime: 0.2
    time (in seconds) for which the submenu remains open even when the mouse is
    not hovering above the main menu option or the submenu. DO NOT SET IT TO
    ZERO! believe me, tiny gaps can make all the difference.
  opacityEffect: true
    do an opacity-based appear/disappear effect on the submenu container when
    popping up/plucking off? requires script.aculo.us effects loaded.
  blindEffect: false
    do a bind down/up effect on the submenu container when popping up/plucking 
    off? requires script.aculo.us effects loaded. WARNING! if you use this, do
    NOT set display: none as the default CSS/inline style for the container;
    otherwise, it just won't appear.
  effectDuration: 0.2
    how quick should the effect be, in seconds. don't go below this.

*/

var submenumgr = {
 options: {
  cleanSubMenus: true,
  offsetTop: 23,
  offsetLeft: 0,
  selectaMain: 'A[startsubmenu]',
  selectaSub: '[role=subMenu]',
  addActiveClass: 'activated',
  submenuOpacity: 1,
  submenuZindex: 1500,
  idleTime: 0.2,
  opacityEffect: true,
  blindEffect: false,
  effectDuration: 0.2
 },
 states: {},
 effects: {},
 masters: {},
 cleartimer: null,
 _preinitdone: false,
 preinit: function() {
 	if (submenumgr._preinitdone===true) return(true);
 	submenumgr._preinitdone=true;
 	$$(submenumgr.options.selectaSub).each(function(el) {
 		el.setAttribute('issubmenu','1');
 		el.style.display = 'none';
 		el.style.position = 'absolute';
 		el.style.top = '0px';
 		el.style.left = '0px';
 		el.style.zIndex = submenumgr.options.submenuZindex;
 		el.setOpacity(0);
		el.observe('mouseover',submenumgr.hovers);
		el.observe('mouseout',submenumgr.outs);
		el.descendants().each(function(ella) {
			el.observe('mouseover',submenumgr.hovers);
			el.observe('mouseout',submenumgr.outs);
		});
 		if (submenumgr.options.cleanSubMenus===true) {
 			el.cleanWhitespace();
 		}
 		else if (submenumgr.options.cleanSubMenus=='full') {
 			var textnodes = [];
 			var chi = el.childNodes;
 			if (chi.length>0) for(var i=0;i<chi.length;i++) {
 				if ((parseInt(chi[i].nodeType)==3)||(parseInt(chi[i].nodeType)==8))
 					textnodes.push(chi[i]);
 			}
 			if (textnodes.length>0) for(var i=0;i<textnodes.length;i++) el.removeChild(textnodes[i]);
 		}
 		document.body.appendChild(el);
 	});
 	$$(submenumgr.options.selectaMain).each(function(el) {
		var msm = el.getAttribute('startsubmenu');
		submenumgr.states[msm] = 'off';
		submenumgr.effects[msm] = null;
		submenumgr.masters[msm] = el;
		el.observe('mouseover',submenumgr.hovers);
		el.observe('mouseout',submenumgr.outs);
		el.descendants().each(function(cel) {
			el.observe('mouseover',submenumgr.hovers);
		});
 	});
 },
 resizedHap: function(ev) {
	$$(submenumgr.options.selectaMain).each(function(el) {
		$(el.getAttribute('startsubmenu')).clonePosition(el,{setWidth:false,setHeight:false,offsetLeft:submenumgr.options.offsetLeft,offsetTop:submenumgr.options.offsetTop});
	});
 },
 init: function() {
 	if (arguments[0]) Object.extend(submenumgr.options,arguments[0]);
 	if (submenumgr._preinitdone===false) submenumgr.preinit();
	Event.observe(window,'resize',submenumgr.resizedHap);
	submenumgr.resizedHap(null);
 },
 hide: function(elem) {
 	var id = elem.id;
 	var mast = submenumgr.masters[id];
 	if (submenumgr.options.addActiveClass!==false) {
 		if (mast.hasClassName(submenumgr.options.addActiveClass)) mast.removeClassName(submenumgr.options.addActiveClass);
 	}
 	if ((submenumgr.options.opacityEffect===false)&&(submenumgr.options.blindEffect===false)) {
 		elem.setOpacity(0);
 		elem.style.display = 'none';
 	}
 	else if ((submenumgr.options.opacityEffect===true)&&(submenumgr.options.blindEffect===false)) {
 		new Effect.Opacity(elem,{
 			from:submenumgr.options.submenuOpacity,
 			to:0,
 			duration:submenumgr.options.effectDuration,
 			queue:{position:'end',scope:id},
 			beforeSetup:function(eff){eff.element.setOpacity(submenumgr.options.submenuOpacity).style.display='block';},
 			afterFinish:function(eff){eff.element.style.display='none';}});
 	}
 	else if ((submenumgr.options.opacityEffect===false)&&(submenumgr.options.blindEffect===true)) {
 		new Effect.BlindUp(elem,{
 			duration:submenumgr.options.effectDuration,
 			queue:{position:'end',scope:id},
 			beforeSetup:function(eff){eff.element.setOpacity(submenumgr.options.submenuOpacity).style.display='block';},
 			afterFinish:function(eff){eff.element.style.display='none';}});
 	}
 	else if ((submenumgr.options.opacityEffect===true)&&(submenumgr.options.blindEffect===true)) {
		new Effect.Parallel([
			new Effect.Opacity(elem,{from:submenumgr.options.submenuOpacity,to:0,sync:true}),
 			new Effect.BlindUp(elem,{sync:true,
 				beforeSetup:function(eff){eff.element.setOpacity(submenumgr.options.submenuOpacity).style.display='block';},
 				afterFinish:function(eff){eff.element.style.display='none';}})
 			],{duration:submenumgr.options.effectDuration,queue:{position:'end',scope:id}});
 	}
 },
 show: function(elem) {
 	var id = elem.id;
 	var mast = submenumgr.masters[id];
 	if (submenumgr.options.addActiveClass!==false) {
 		if (mast.hasClassName(submenumgr.options.addActiveClass)===false) mast.addClassName(submenumgr.options.addActiveClass);
 	}
 	if ((submenumgr.options.opacityEffect===false)&&(submenumgr.options.blindEffect===false)) {
 		elem.setOpacity(submenumgr.options.submenuOpacity);
 		elem.style.display = 'block';
 	}
 	else if ((submenumgr.options.opacityEffect===true)&&(submenumgr.options.blindEffect===false)) {
 		new Effect.Opacity(elem,{
 			to:submenumgr.options.submenuOpacity,
 			from:0,
 			duration:submenumgr.options.effectDuration,
 			beforeSetup:function(eff){eff.element.setOpacity(0).style.display='block';},
 			queue:{position:'end',scope:id}
 			});
 	}
 	else if ((submenumgr.options.opacityEffect===false)&&(submenumgr.options.blindEffect===true)) {
 		new Effect.BlindDown(elem,{
 			duration:submenumgr.options.effectDuration,
 			beforeSetup:function(eff){eff.element.setOpacity(submenumgr.options.submenuOpacity).style.display='block';},
 			queue:{position:'end',scope:id}});
 	}
 	else if ((submenumgr.options.opacityEffect===true)&&(submenumgr.options.blindEffect===true)) {
 		new Effect.Parallel([
			new Effect.Opacity(elem,{to:submenumgr.options.submenuOpacity,from:0,sync:true}),
 			new Effect.BlindDown(elem,{sync:true,
 				beforeSetup:function(eff){eff.element.setOpacity(0).style.display='block';}})
 			],{duration:submenumgr.options.effectDuration,queue:{position:'end',scope:id}});
 	}
 },
 findHost: function(ev) {
	var el = Event.element(ev);
	var sm = null;
	if (el.hasAttribute('startsubmenu')) {
		sm = el.getAttribute('startsubmenu');
	}
	else if (el.up('[startsubmenu]')!==undefined) {
		sm = el.up('[startsubmenu]').getAttribute('startsubmenu');
	}
	else if (el.hasAttribute('issubmenu')) {
		sm = el.id;
	}
	else {
		var ups = el.up('[issubmenu]');
		if (ups!==undefined) sm = ups.id;
		else sm = el.id;
	}
	return(sm);
 },
 hovers: function(ev) {
 	if (submenumgr.cleartimer!==null) {
 		clearTimeout(submenumgr.cleartimer);
 		submenumgr.cleartimer = null;
 	}
	var sm = submenumgr.findHost(ev);
	if (submenumgr.states[sm]) {
		if (submenumgr.states[sm]=='shouldClear') {
			submenumgr.states[sm]='on';
		}
		else if (submenumgr.states[sm]=='off') {
			submenumgr.states[sm]='on';
			submenumgr.show($(sm));
		}
	}
	var t;
	for(t in submenumgr.states) {
 		if ((t!=sm)&&(submenumgr.states[t]!='off')) {
 			submenumgr.states[t]='off';
			submenumgr.hide($(t));
 		}
 	}
 },
 outs: function(ev) {
	var sm = submenumgr.findHost(ev);
	if (submenumgr.states[sm]) {
		if (submenumgr.states[sm]=='on') {
			if (submenumgr.cleartimer===null)
				submenumgr.cleartimer = setTimeout(submenumgr.clear,submenumgr.options.idleTime*1000);
			submenumgr.states[sm] = 'shouldClear';
		}
	}
 },
 clear: function() {
	for(var t in submenumgr.states) {
		if (submenumgr.states[t]=='shouldClear') {
			submenumgr.states[t] = 'off';
			submenumgr.hide($(t));
		}
	}
	submenumgr.cleartimer=null;
 }
}

/* by default, submenumgr.preinit() gets executed at dom:loaded stage to avoid
   unnecessary fuss. however, if you want to initialize at another stage, put
   in a javascript snippet BEFORE the </body> tag:
     document.stopObserving('dom:loaded',submenumgr.preinit);
   also, if you want to change the defaults (in submenumgr.options), make sure
   you do it before the DOM tree loads, also before the </body> tag. */
//document.observe('dom:loaded',submenumgr.preinit);
Event.observe(window,'load',submenumgr.preinit);

