/* Code by Dave Mathiesen (C) 2007 davem.com ALL RIGHTS RESERVED. -- meaning: you're not supposed to use, modify or create derived products from this code without explicate written permission by the author, Dave M. "If my automated tool finds your site in violation of this... ew.. you're going to be in deep doo doo!" */ /* first you make the 'root' container a menu object with NO parent. then you attach the next container to an element_id in a menu object. if you want the menu spawned by a click of a specific element, then register that element. With the root menu object. MyCustomRootMenuObject.spawnOnClick(elementTo Span On click of); REQUIRED FOR Menus to have sub menus, YOU MUST ASSIGN an ID to each
  • !!! -- if you don't, how do you expect to assign a submenu to it?! */ function menuObject(object_id, parentMenuObject, parent_element){ this.branchToShow = false; this.branchToHide = false; this.transitionDelay = 5; this.hideTransistionDelay = 30; // 5 times longer than the transisiton between menus this.exposedChild = false; // which of MY children is currently visible? this.parent_element = false; this.children = new Object(); // an object propigated with objects in a keyed array such that when an object of id is moused over, or clicked, it will trigger // [menu_element] = object; this.parent_element = parent_element; this.parent_element_obj = document.getElementById(parent_element); this.parent_obj = parentMenuObject; this.allowHide = this.parent_obj ? true : false; //feedback(" ALLOW HIDE: " + this.allowHide); this.html_obj = document.getElementById(object_id); if(!this.html_obj){ alert(object_id + " Does not exist"); return null; } // Attach this container to an object's element this.attachToObjectElement = function(object, element_id){ this.children[element_id] = object; } this.addMenu = function addMenu(container_obj, parentMenuObject, parentMenuElementId){ // this adds a menu to the current object this.parent_menu_element = document.getElementById(parentMenuElementId); this.parent_obj = parentMenuObject; parent_obj.sub_container = container_obj; /* this does nothing? children = container_obj.childNodes; parent_menu_element.parentNode; var me = this; for(i in children){ if(children[i].tagName=="LI"){ this.parentNode = } } */ } this.initialize = function(){ //feedback("initializing: " + this.html_obj.id + ""); //feedback("has parent: " + (this.parent_obj ? "true" : "false")); if(this.parent_obj){ //feedback ('getting parent\'s globals'); // this is as good as a pointer, because it's an object assignment! this.globals = this.parent_obj.globals; //feedback("my parent_object: " + this.parent_element_obj.id); if(this.parent_element){ this.parent_obj.attachToObjectElement(this, this.parent_element); } }else{ // I have no parent, so I am the host of the 'globals' object. this.globals = new Object(); this.globals.hovering = false; this.globals.currentlyShowing = false; this.globals.branchToShow = false; } childs = this.html_obj.childNodes; // which set of children do i hand off? // register each li element's onclick, onmouseover onmouseout events. for(i in childs){ if(childs[i].tagName) if(childs[i].tagName.toLowerCase()=="li"){ this.remapChildrenMouseFunctions(childs); break; }else if(childs[i].tagName.toLowerCase()=="tbody"){ subChildren = childs[i].childNodes; for(ii in subChildren){ if(subChildren[ii].tagName) if(subChildren[ii].tagName.toLowerCase()=="tr"){ mahChildren = subChildren[ii].childNodes; this.remapChildrenMouseFunctions(mahChildren); } } }else if(childs[i].tagName.toLowerCase().match(/t/)){ //.this.remapChildrenMouseFunctions(childs[i]); break; } } //feedback("initializing spawn location"); this.spawnLocation('usedefaults'); // hehe, it's not going to find any matches in this // so it'll use default... by default :-) // yes I know it's the long route, but so long as // I continue to use the spawnLocation function, // things won't get out of whack. } this.remapChildrenMouseFunctions = function(childs){ var me = this; for(i in childs){ if(childs[i].id){ // we have an id, we might actually do something with this element! el_id = childs[i].id; /* childs[i].onmouseover=function(){me.onmouseover(me,el_id);}; childs[i].onmouseout=function(){me.onmouseout(me, el_id);}; childs[i].onclick=function(){me.onclick(me, el_id);}; */ this.closureIfyMouseOver(childs[i], me, el_id); this.closureIfyMouseOut(childs[i], me, el_id); this.closureIfyMouseClick(childs[i], me, el_id); } } } this.closureIfyMouseOver = function(obj, me, el){ obj.onmouseover=function(){me.onmouseover(me, el);}; } this.closureIfyMouseOut = function(obj, me, el){ obj.onmouseout=function(){me.onmouseout(me, el);}; } this.closureIfyMouseClick = function(obj, me, el){ obj.onclick=function(){me.onclick(me, el);}; } // if position is *just* calling this function with null arguments, // makes the deafult position bottomleft (perfect for the initial // spawning menu) this.spawnLocation = function(position){ if(!position){ position = ''; } if(position.toLowerCase().match(/top|upper/)){ this.spawn_y = 'top'; }else{ this.spawn_y = 'bottom'; } if(position.toLowerCase().match(/right/)){ this.spawn_x = "right"; }else{ this.spawn_x = 'left'; } } /* ======================================================= ======================================================= You likely don't care about what's below this line ======================================================= =======================================================*/ /* Re Map'd - ON CLICK event */ this.onclick = function(obj, element_id){ if(!element_id) return null // there's nothing to do! obj.click(element_id); } this.click = function(element_id){ this.elementOpen(element_id); } /* Re Map'd - ON MOUSE OVER event */ this.onmouseover = function(obj, element_id){ //feedback("hovering"); obj.hover(element_id); } this.hover = function(element_id){ // event triggered as soon as a mouse hover starts. (it's restarted between mouse off and mouse back on) //feedback(element_id + ", hover: " + this.open_delay); if(this.open_delay>0) // already hovering return null; this.hovering = true; this.globals.hovering=true; this.branchToShow = element_id; this.open_delay = 1; this.delayedOpen(this.branchToShow); // argument is for the sake of sanity check incase of race condition } /* Re Map'd - ON MOUSE OUT event */ this.onmouseout = function(obj, element_id){ obj.mouseoff(element_id); } this.mouseoff = function(element_id){ //feedback("off"); this.hovering = 0; this.globals.hovering=0; this.open_delay = 0; // reset the count-down (counter) this.hide_delay = 1; // reset the count-down (counter) this.delayedClose(); } // the open event is triggered after the element is either clicked or the 'delay' has passed. // Open is fired after the user has hovered long enough, OR clicked. // if there is nothing attached to this node, nothing will occure, // except for maybe your hard-coded LINK to another page, or HARD CODED "onOpen" script action. this.elementOpen = function(element_id){ // get details from this element; if(!this.hovering) return NULL; elm = document.getElementById(element_id); execString = null; if(elm){ // does the element even exist? if(elm.onOpen){ // i would have done (x=.... || x=... || x=... ) but javascript craps out after the first variable doesn't exist. execString = elm.onOpen; }else if(elm.onopen){ execString = elm.onopen; } } if(this.exposedChild) if(!this.inBranch(this.exposedChild)) this.exposedChild.hide(); //feedback("open"); if(execString){ eval(execString); }else{ if(this.children[element_id]){ if(this.children[element_id]!=this.exposedChild){ //feedback("hiding exposed child"); this.hideExposedChild(); } if(this.children[element_id].show)// it's a menu object! this.children[element_id].show(); // do the show bit :-) }else{ if(this.exposedChild){ //feedback("Exposed child of " + this.html_obj.id + " is " + this.exposedChild.html_obj.id); //if(!this.inBranch(this.exposedChild)){ this.hideExposedChild(); //feedback("hiding exposed childe"); } } } } this.delayedOpen = function(element_id){ ////feedback("delayed_open tick: " + this.open_delay); // are any of my children being hovered over? //feedback(element_id + ", hover: " + this.open_delay); if(this.open_delay){ if(this.open_delay>this.transitionDelay){ // execute "open" this.elementOpen(element_id); this.open_delay = 0; }else{ this.open_delay++; me = this; setTimeout(function(){me.closure_DelayedOpen(me, element_id)}, 60); } }else{ // no hide delay, do nothing // nothing. } } this.closure_DelayedOpen = function(obj, element_id){ obj.delayedOpen(element_id); } this.inBranch = function(branch_object){ //feedback("branch object" . branch_object); if(this.hasParent(branch_object)) // i'm a decendent of... return true; if(branch_object.hasParent(this)) // i'm a parent of... return true; return false; } this.killAll = function(){ // go up until we find the top most parent. if(this.parent_obj){ this.parent_obj.killAll(); }else{ // then start killing children this.hide(); } } this.hasParent = function(parent){ if(this.parent_obj){ if(this.parent_obj==parent) return true; if(this.parent_obj.hasParent) return this.parent_obj.hasParent(); } return false; } // this is spawned ONLY because the mouse moved off the menu structure! // and therefore closes EVERYTHING! this.delayedClose = function(){ if(this.globals.hovering){// mouse moved back on, ALL STOP on the close this.hide_delay = 0; return false; }else if(this.hide_delay==this.hideTransistionDelay){ this.hide(); }else{ this.hide_delay++; me = this; setTimeout(function(){me.closure_DelayedClose(me)}, 50); } } this.closure_DelayedClose = function(obj){ obj.delayedClose(); } this.getOffsetTop = function(dom_node){ var top = dom_node.offsetTop; if(dom_node.offsetParent) top += this.getOffsetTop(dom_node.offsetParent); return top; } this.getOffsetLeft = function(dom_node){ var left = dom_node.offsetLeft; if(dom_node.offsetParent) left += this.getOffsetLeft(dom_node.offsetParent); return left; } this.getCoordsOf = function(lateral, vertical, menu_element){ element = document.getElementById(menu_element); var coords = new Object(); coords['y'] = this.getOffsetTop(element); if(lateral.toLowerCase()!='top'){ coords['y'] += element.offsetHeight; } coords['x'] = this.getOffsetLeft(element); if(lateral.toLowerCase()!='left'){ coords['x'] += element.offsetWidth; } return coords; } this.hideExposedChild = function(){ //Kill my sibilings! if(this.parent_obj){ if(this.parent_obj.exposedChild) if(this.parent_obj.exposedChild.hide) this.parent_obj.exposedChild.hide(); }else{ if(this.exposedChild) if(this.exposedChild.hide) this.exposedChild.hide(); } } this.show = function(custom_x,custom_y){ // a function that spills out my child container (if i have one) this.hideExposedChild; // where to i display? this.globals.currentlyShowing = this; this.html_obj.style.position='absolute'; // if we have a parent object, then we spawn where we're supposed to spawn if(this.parent_obj && this.parent_element && this.spawn_x && this.spawn_y){ coords = this.parent_obj.getCoordsOf(this.spawn_x, this.spawn_y, this.parent_element); x = coords['x']; y = coords['y']; }else if(custom_x, custom_y){ // use custom x,y coords. x = custom_x; y = custom_y; }else{ // get X/Y click coorindates } /* here should be some logic for should this object be rendered on the 'opposite side the rest were rendered on' because this object, being rendered, will go beyond the boundry of the webpage this the parent object was also rendered on the reverse side. */ // X this.parent_obj.spawn_reverse ? this.html_obj.style.right = x : this.html_obj.style.left = x; // Y this.html_obj.style.top = y; this.html_obj.style.display = 'block'; this.showing = true; this.open_delay = 0; this.branchToShow = false; if(this.parent_obj) this.parent_obj.exposedChild = this; } this.hide = function(){ // function that hides this and all of it's children if(this.exposedChild) // do i have any subchildren? this.exposedChild.hide(); if(this.allowHide){ //feedback("hiding"); this.html_obj.style.display='none'; } } this.initialize(); }