Today’s Rule-of-Thumb
You may have understood it: this morning’s comment was somehow related to an earlier comment I made. Tonight’s post is therefore going to illustrate what I was going to say this morning…
Here is the code I received from web designers. Well, not really, this is a highly-simplified sample:
- I used the “original” MM function which had been modified in the original code to take into accounts timeouts;
- I rewrote the menus with lists rather than tables;
- I removed timeouts as well as sub(sub)menus;
- I changed design and colours for the sake of the designers’ anonymity.
In that example, the appearance of submenus is triggered by moving the mouse onto the corresponding menu item. Two problems arise immediately by looking (a) at the JavaScript code and (b) at the CSS code. Every menu item has a onmouseover event which hides the other submenus and opens the current submenu. It means that, for everything single menu item I am going to add (for example the item number 3 on the sample), I will have to add such an event to the newly-created item, and modify all the other so that the new submenu is hidden when going onto another menu item. In that example, there is also a style associated to every submenu which, amongst other things, place it in an absolute manner.
Now, think dynamic. Before starting, I don’t know whether I am dealing with 3 or with 50 items in my menu. I only know I am dealing with a collection which might (or might not) have submenus. It means that I will have to code on the server-side (a) the client-side which is going to deal with the menus and (b) the relevant CSS style. It is always been something awful to me, and not only because it is ugly but also because the resulting code is just a nightmare to maintain. And that’s a real pain in the arse to code such a thing, to be honnest.
The first obvious thing to do is to replace the id
s used in the submenus by class
es – which will then be used to close the whole lot.
<ul class=“nav2”>
<li class=“menu” id=“menu1”>My Sweet Home</li>
<li class=“menu” id=“menu2”>My Ugly Kids</li>
<li class=“menu” id=“menu3”>Linux Rulez</li>
</ul><div id=“submenu1” class=“submenu” style=“position: absolute; top: 120px; left: 20px;”>
<ul>
<li>Lots of things</li>
<li>Some more</li>
</ul>
</div><div id=“submenu2” class=“submenu” style=“position: absolute; top: 120px; left: 191px;”>
<ul>
<li>Beetle Juice</li>
<li>The Gimp</li>
</ul>
</div>
Why did I put the position as in-line styles? Simply because this will be very simple to compute server-side. Then, you have to add a getElementsByClass
function to be able to find all the elements bearing the same class name – and hide them. I found the getElementsByClass
function on Daniel Glazman’s blog (just think of removing the parenthesis after the getElementByClass in his code).
document.getElementsByClass = function (needle)
{ var my_array = document.getElementsByTagName(”*”); var retvalue = new Array(); var i = 0; var j = 0; for (i = 0, j= 0; i < my_array.length; i++) { var c = “ “ + my_array[i].className + “ “; if (c.indexOf(” “ + needle + “ “) != -1) retvalue[j++] = my_array[i]; } return retvalue; }
This function must be called in a function which hides every element of a particular class:
function hideClass(className) { var listeEle = document.getElementsByClass(className); for (i = 0; i < listeEle.length; i++) { var ele = listeEle[i]; if (ele.style) ele = ele.style; ele.visibility = "hidden"; } }
Now, the aim is to hide everything and to show the proper submenu; for that, yet another function:
function hideAndShow(idToShow, classToHide) { hideClass(classToHide); // And for the fun of reusing code: MM_showHideLayers(idToShow, '', 'show'); }
For the moment, nothing too tough. Now, the real big deal is to handle the events without linking to the elements. That way, it is going to be much easier to integrate with dynamic code: the JavaScript on one side, the loops to display the menus on another one. For that purpose, I just referred to my favourite article on the matter on Sitepoint.
function attachObjectEvent(objet, fonction, event) { //setup initialisation function if(typeof objet.addEventListener != ‘undefined’) { objet.addEventListener(event, fonction, false); } //.. win/ie else if(typeof window.attachEvent != ‘undefined’) { objet.attachEvent(‘on’ + event, fonction); }
}function initMenus() { var menuList = getElementsByClass(“menu”); for (i = 0; i < menuList.length; i++) { var ele = menuList[i]; attachObjectEvent(ele, new Function(“evt”, “hideAndShow(‘sub” + ele.id + “,‘submenu’);”), “mouseover”); }
}attachObjectEvent(window, initMenus, “load”);
The HTML code can be simplified as follows:
<ul class=“nav”>
<li class=“menu” id=“menu1”>My Sweet Home</li>
<li class=“menu” id=“menu2”>My Ugly Kids</li>
<li class=“menu” id=“menu3”>Linux Rulez</li>
</ul><div id=“submenu1” class=“submenu” style=“position: absolute; top: 52px; left: 20px;”>
<ul>
<li>Lots of things</li>
<li>Some more</li>
</ul>
</div><div id=“submenu2” class=“submenu” style=“position: absolute; top: 52px; left: 191px;”>
<ul>
<li>Beetle Juice</li>
<li>The Gimp</li>
</ul>
</div>
Developer, just imagine how easy your task has become! You insert your loop (with a foreach
or a <logic:iterate>
or whatever) and that’s it. You can have a look at the “final” code here.
Now, I know there are lots of improvements to be made to this example – I am not even sure (cough) this works properly on every browser since I kind of quickly modified Aaron’s code, not taking into account the test window vs. document – but that is a start.
You must also have realised that this method requires many more lines of code. It is partly due to the fact that the example is pretty straightforward. In a much more complicated (real-life) one, the number of JavaScript lines added is counter-balanced by the amount of styles removed. It also looks very expensive time-wise; it is, but imbricating client and server code often ends up in massive time loss, both in development and maintenance. And once the key functions have been defined, such as getElementsByClass
and attachObjectEvent
, it all goes much faster…
Feel free to comment, improve, etc. I am a developer who does not want to think too much when it comes to integrating HTML.