// UDMv4.5 // Scrolling Menus extension v2.01 //
/***************************************************************\

  ULTIMATE DROP DOWN MENU Version 4.5 by Brothercake
  http://www.udm4.com/

\***************************************************************/


//scrolling menu parameters
um.scroll = [
	"400px", 		// maximum menu height ["px"|"%"]
	"2", 			// minimum scrolling speed ["n" pixels per iteration]
	"25", 			// maximum scrolling speed ["n" pixels per iteration]
	"110%", 		// acceleration ["%" per iteration]
	"up.gif", 	// scrollUP arrow ["image.gif"]
	"scroll up", 		// scrollUP alt text ["text"]
	"down.gif", 	// scrollDOWN arrow ["image.gif"]
	"scroll down", 		// scrollDOWN alt text ["text"]
	"Click to scroll %speed%", // vocabulary pattern-match for "click to scroll (slowly|quickly)"
	"slowly", 		// vocabulary for "slowly" [as in "click to scroll slowly"]
	"quickly", 		// vocabulary for "quickly" [as in "click to scroll quickly"]
	"yes", 			// show menus to browsers that don't support this extension ["yes"|"no"|"try"]
	];


/***************************************************************\
\***************************************************************/






//global object
var scr=new Object;

//identify it to the menu script
//but exclude mac/ie5, msn and opera < 7.5
um.scr=(!(um.mie||(um.o7&&!um.o75)));

//semi-supported browsers can use overflow:scroll but not animated clip scrolling
scr.se=(um.wie50);

//if not supported and we're not showing menus to unsupported browsers
//or semi-supported and we're not implementing that
//disable menus and arrows
if((!um.scr&&um.scroll[11]!='yes')||(scr.se&&um.scroll[11]=='no')){um.nm=true;um.nr=true;}

//initially null scrolling menu reference
scr.me=null;


//if scrolling is supported
if(um.scr)
{
	//add receivers for menu opening and closing events
	um.addReceiver(applyScrolling,'060');
	um.addReceiver(resetScrolling,'070');

	//if extension is fully supported
	if(!scr.se)
	{
		//add receiver for item focus event
		um.addReceiver(focusScrolling,'040');

		//cache arrow button images
		scr.ar=[];
		for(var i=0;i<2;i++)
		{
			scr.ar[i]=new Image;
			scr.ar[i].src=um.baseSRC+um.scroll[i*2+4];
		}

		//scrolling timer reference
		scr.ti=null;

		//timeout speed [milliseconds]
		scr.sp=55;
	}
}





//get max scrolling height method
scr.gsh=function()
{
	//window height
	scr.wh=um.getWindowDimensions().y;

	//if scrolling height is in percent,convert to
	//a number (in pixels) as percentage of current viewport size
	//otherwise just convert to number (in pixels)
	scr.sh=(um.scroll[0].indexOf('%')!=-1)?((um.pi(um.scroll[0])/100)*scr.wh):um.pi(um.scroll[0]);

	//if final height is still taller than window height,constrain to that
	if(scr.sh>scr.wh){scr.sh=scr.wh;}

	return scr.sh;
};


//get menu height method for calculating dropshadow and re-positioning figures
scr.gmh=function(me)
{
	//get menu height
	scr.he=me.offsetHeight;

	//get max scrolling height
	scr.ma=scr.gsh();

	//if menu height exceeds max height,return max height
	//otherwise return actual height
	return (scr.he>scr.ma?scr.ma:scr.he);
};


//remove generated elements from a menu node
scr.rge=function(node)
{
	//array of elements to check
	//we need this for win/ie5 and safari 1.0
	//which don't support the '*' collection
	//and it's marginally more efficient anyway
	scr.tg=['span','iframe'];

	//for each element
	for(var j=0; j<2; j++)
	{
		//look for elements of this type
		scr.el=node.getElementsByTagName(scr.tg[j]);

		//for each element [using length,because it's changing]
		for(var i=0;i<scr.el.length;i++)
		{
			//store classname, converting null reference for kde's benefit
			scr.cn=um.es(scr.el[i].className);

			//if it has the classname 'udmS' for a shadow layer
			//or 'udmC' for an iframe cover layer
			//or 'scrollXX' for a scroll button cluster
			if(/udmS|udmC|scroll[A-Z]{2}/.test(scr.cn))
			{
				//remove it
				scr.el[i].parentNode.removeChild(scr.el[i]);
			}
		}
	}
};


//start scrolling a menu
scr.start=function(down)
{
	//if we have a menu reference and the scroll timer is not already going
	if(scr.me!=null&&scr.ti==null)
	{
		//animate scrolling
		scr.ani(down,null);
	}
};


//animate scrolling a menu
scr.ani=function(down,to)
{
	//stop if we don't have a menu reference
	if(scr.me==null){return false;}

	//reset the timer reference,because
	//scr.stop doesn't get called when you get to the bottom of a menu
	//hence next scroll-button mouseover won't work
	scr.ti=null;

	//if to override value is set
	if(to!=null)
	{
		//set top value to defined amount
		scr.tp=to;

		//set top margin position accordingly
		scr.ps.now=scr.ps.def-scr.tp;
	}

	//or if override value is not set
	else
	{
		//increase [down] or decrease [up] the top clip point by current resolution (speed)
		scr.tp+=(down?scr.re.now:(0-scr.re.now));

		//decrease [down] or increase [up] the current top margin position by current resolution (speed)
		scr.ps.now-=(down?scr.re.now:(0-scr.re.now));

		//if no-acceleration flag is not set
		if(!scr.na)
		{
			//limit resolution to maximum,or increase current by acceleration rate
			scr.re.now=(scr.re.now>=scr.re.max?scr.re.max:scr.re.now*=scr.re.rate);
		}
	}

	//assume we don't loop again
	scr.ag=false;

	//if we've reached the bottom
	if(scr.tp+scr.ma >= scr.he)
	{
		//set top value to the precise amount
		scr.tp=(scr.he-scr.ma);

		//set top margin position to the precise amount
		scr.ps.now=scr.ps.def-scr.tp;

		//set the "reached extreme" flag
		//carrying the index of button to hide and button to show
		scr.rx=[1,0];
	}

	//or if we've reached the top
	else if(scr.tp<=0)
	{
		//reset top value to precisely zero
		scr.tp=0;

		//reset top margin position to default
		scr.ps.now=scr.ps.def;

		//set the "reached extreme" flag
		//carrying the index of button to hide and button to show
		scr.rx=[0,1];
	}

	//or we're still midway through a menu
	else
	{
		//loop again
		scr.ag=true;

		//show both scroll buttons
		for(i=0;i<2;i++){scr.bn[i].main.style.visibility='visible';}

		//set a null reference for the "reached extreme" flag
		//effectively resetting it
		scr.rx=null;
	}

	//if the "reached extreme" flag is set
	if(scr.rx!=null)
	{
		//if no-acceleration flag is not set
		if(!scr.na)
		{
			//reset current resolution
			scr.re.now=scr.re.min;
		}

		//remove active scrolling classname from inner button
		scr.bn[scr.rx[0]].inr.className=scr.bn[scr.rx[0]].inr.className.replace(/ scrollMOVING/g,'');

		//hide scroll button
		scr.bn[scr.rx[0]].main.style.visibility='hidden';

		//show scroll button,because we might have got here
		//having looped-round with keyboard navigation
		scr.bn[scr.rx[1]].main.style.visibility='visible';
	}

	//re-apply menu height
	scr.me.style.height=((scr.ma-scr.bd)+scr.tp)+'px';

	//re-apply clip
	scr.me.style.clip='rect('+scr.tp+'px,'+scr.width+'px,'+(scr.ma+scr.tp)+'px,0px)';

	//re-apply menu position
	scr.me.style.marginTop=scr.ps.now+'px';

	//if scrollto override value is not set and we're looping again
	if(to==null&&scr.ag)
	{
		//copy direction reference
		scr.dir=down;

		//start a new timeout
		scr.ti=window.setTimeout('scr.ani(scr.dir,null)',scr.sp);
	}

	return scr.ag;
};


//stop scrolling a menu
scr.stop=function()
{
	//if we have a menu reference
	if(scr.me!=null)
	{
		//stop the timer
		clearTimeout(scr.ti);
		scr.ti=null;

		//if the no-acceleration flag is not set,reset current resolution
		if(!scr.na){scr.re.now=scr.re.min;}

		//reset the no-acceleration flag
		//scr.na=false;
	}
};





//apply scrolling behavior
function applyScrolling(me)
{
	//get max height
	scr.ma=scr.gsh();

	//store menu height
	//which will also be used to see if we've scrolled to the bottom
	scr.he=me.offsetHeight;

	//if this menu has any child menus,don't continue
	//this is because scrolling is based on clip,
	//and since UDM is a true heirarchical menu
	//it's not possible to display a child menu
	//outside the clipping region of its parent
	if(me.getElementsByTagName('ul').length>0){return false;}

	//store parent parent classname, converting null reference for kde's benefit
	scr.pc=um.es(me.parentNode.parentNode.className);

	//whether this is a submenu or a child menu
	scr.is=scr.pc=='udm';

	//if menu height exceeds max,and this menu has no child menus
	if(scr.he>scr.ma)
	{
		//box model difference if we're not in quirks mode
		//which will be used to adjust menu height
		scr.bd=(um.q?0:((um.e[51]*2)+(um.e[55]*2)));

		//copy reference to menu
		scr.me=me;

		//if extension is only partially supported
		if(scr.se)
		{
			//set overflow-y and height
			me.style.overflowY='scroll';
			me.style.height=(scr.ma-scr.bd)+'px';

		}

		//otherwise extension is fully supported
		else
		{
			//copy and convert scrolling resolution (speed) and acceleration to numbers
			scr.re={'min':um.pi(um.scroll[1]),'max':um.pi(um.scroll[2]),'rate':(parseFloat(um.scroll[3],10)/100)};

			//initial current speed is minimum speed
			scr.re.now=scr.re.min;

			//copy and store the default top margin position
			scr.ps={'def':um.pi(me.style.marginTop)};

			//initial top position is default position
			scr.ps.now=scr.ps.def;

			//store the top clip point
			//which we'll update and refer to as we go along
			scr.tp=0;

			//set overflow and initial height
			//which is used to prevent a contra-scrollbar appearing
			//including the box model difference we calculated before
			//means the height and clip will be perfectly aligned
			//allowing the buttons to be hidden at either extreme
			me.style.overflow='hidden';
			me.style.height=(scr.ma-scr.bd)+'px';

			//store menu width
			scr.width=me.offsetWidth;

			//set initial clip
			me.style.clip='rect(0px,'+scr.width+'px,'+scr.ma+'px,0px)';

			//button properties
			scr.bn=[
				//scroll up
				{'x':me.offsetLeft,'y':me.offsetTop,'alt':um.scroll[5],'classname':'scrollUP'},
				//scroll down
				{'x':me.offsetLeft,'y':(me.offsetTop+scr.ma),'alt':um.scroll[7],'classname':'scrollDOWN'}
				];

			//create buttons
			for(var i=0;i<2;i++)
			{
				//outer container button
				scr.attrs={'class':'scrollBUTTON'};
				scr.bn[i].main=me.parentNode.appendChild(um.createElement('span',scr.attrs));

				//set container width
				scr.bn[i].main.style.width=scr.width+'px';

				//outer custom buttons
				scr.attrs={'class':scr.bn[i].classname};
				scr.bn[i].out=scr.bn[i].main.appendChild(um.createElement('span',scr.attrs));

				//inner custom buttons,including default title text
				scr.attrs={'class':'scrollINNER','title':um.scroll[8].replace('%speed%',um.scroll[9])};
				scr.bn[i].inr=scr.bn[i].out.appendChild(um.createElement('span',scr.attrs));

				//inner button arrows
				scr.attrs={'alt':scr.bn[i].alt,'title':''};
				scr.bn[i].arrow=scr.bn[i].inr.appendChild(um.createElement('img',scr.attrs));
				scr.bn[i].arrow.src=scr.ar[i].src;

				//set positions on outer container buttons,allowing for height of bottom button
				//for safari 1.0 child menus, reduce positions by menu border size
				//because menu repositioning routine does an equivalent tweak
				scr.bn[i].main.style.left=(scr.bn[i].x-(um.s&&!um.s1&&!scr.is?um.e[51]:0))+'px';
				scr.bn[i].main.style.top=((i==0?scr.bn[i].y:(scr.bn[i].y-scr.bn[i].main.offsetHeight))-(um.s&&!um.s1&&!scr.is?um.e[51]:0))+'px';

				//create a contains method for the inner buttons
				//not for ie,because we're re-creating an ie-proprietary method
				if(!um.ie)
				{
					scr.bn[i].inr.contains=function(nd)
					{
						if(nd==this){return true;}
						else if(nd==null){return false;}
						else{return this.contains(nd.parentNode);}
					};
				}

				//bind mouseover handler to inner buttons
				scr.bn[i].inr.onmouseover=function()
				{
					//start scrolling,passing 'down' argument derived from button parent classname
					scr.start(/scrollDOWN/.test(this.parentNode.className));

					//add active scrolling classname
					this.className+=' scrollMOVING';
				};

				//bind mouseout handler to inner buttons
				scr.bn[i].inr.onmouseout=function(e)
				{
					//convert event references
					if(!e){e=window.event;e.relatedTarget=e.toElement;}

					//if the button doesn't contain the related target
					if(!this.contains(e.relatedTarget))
					{
						//stop scrolling
						scr.stop();

						//remove active scrolling classname
						this.className=this.className.replace(/ scrollMOVING/g,'');
					}
				};

				//no acceleration override flag
				//so that user can take manual control of slow/fast scrolling
				scr.na=false;

				//bind click handler to inner buttons
				scr.bn[i].inr.onclick=function()
				{
					//set no acceleration override flag
					scr.na=true;

					//if resolution is at minimum
					if(scr.re.now==scr.re.min)
					{
						//set to maximum
						scr.re.now=scr.re.max;
					}

					//otherwise resolution is not at minimum
					else
					{
						//set resolution to minimum
						scr.re.now=scr.re.min;
					}

					//for each scrolling button
					for(var j=0; j<2; j++)
					{
						//set buttons title text as appropriate
						scr.bn[j].inr.title=um.scroll[8].replace('%speed%',um.scroll[(scr.re.now==scr.re.min?10:9)]);
					}

				};

				//if this is the down button,make it visible
				if(i==1){scr.bn[i].main.style.visibility='visible';}
			}
		}
	}

	return true;
};


//auto-scroll menu in response to focus events
function focusScrolling(it)
{
	//if we have a menu reference
	if(scr.me!=null)
	{
		//if offsetTop comes back as 0 (which happens in win/ie5, because it has different positioning)
		//change the reference to its parent; can't do this for all, because then it breaks in other browsers
		if(it.offsetTop == 0) { it = it.parentNode; }

		//store focus position as item top + item height + button height
		scr.fpos=(it.offsetTop+it.offsetHeight+scr.bn[1].main.offsetHeight);

		//store half menu height accounting for box model difference
		scr.half=(scr.ma/2)-scr.bd;

		//if focus position is greater than half menu height + item height
		if(scr.fpos>(scr.half+it.offsetHeight))
		{
			//scroll menu accordingly - this will anchor it around the center
			scr.ani(null,(scr.fpos-scr.half-it.offsetHeight))
		}

		//otherwise it must be lower than that
		else
		{
			//scroll menu to the top
			scr.ani(null,0);
		}
	}
};


//reset scrolling behavior
function resetScrolling(me)
{
	//remove generated elements from the parent list item
	scr.rge(me.parentNode);

	//reset the no-acceleration flag
	scr.na=false;

	//if we have a menu reference
	if(scr.me!=null)
	{
		//remove remaining generated elements from the parent list item
		scr.rge(scr.me.parentNode);

		//restore auto height and overflow
		scr.me.style.overflow='auto';
		scr.me.style.height='auto';

		//reset clip
		scr.me.style.clip='rect(auto,auto,auto,auto)';

		//nullify menu reference
		scr.me=null;
	}
};


