// 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.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; } };