Forum Moderators: open
The approach here supports older browsers, including NN 4.7. That alone makes this script a gem.
So enough from me -- here's BlobFisk:
JavaScript Menu System
using Layers
One of the more popular combinations of JavaScript and CSS-P (positioning using CSS) comes in the form of the javascript popup menu. More commonly known as dHTML, this is basically manipulation of the DOM (Document Object Model) using JavaScript.
The following JavaScript file does this; it allows us to toggle the visibility of layers on and off, thus allowing us to build a drop down menu system. There are added functions that allow us to set a timeout value for hover states, which means that once a user moves the mouse away from our drop down menu, after a certain time interval it disappears.
If only DOM1 compliant browsers need to be supported, the code would be half the size – but in the variable environment that is the Internet, the script accommodates many flavours of browser. I've tested this and it works in NN4.7x and above, IE4.0 and above, Mozilla 0.9 and above and Opera 5 and above.
OK, so lets dive in! I’ve broken this down into many functional units for ease of explanation. The first section to tackle is the external JS file.
function reDo() {
window.location.reload();
}window.onresize = reDo;
//Define global variables
var timerID = null;
var timerOn = false;
var timecount = 1000;
// Change this to the time delay that you desire
var what = null;
var newbrowser = true;
var check = false;
Here we are defining some global variables that we will use in the script. The most important one is the timecount variable – this is the delay (in milliseconds) that will be used to hide layers after the user has moused away from them.
The reDo() function is to accommodate browsers that will not dynamically regenerate the DOM when the browser window has been resized – so we force it to do so.
function init() {
if (document.layers) {
layerRef="document.layers";
styleSwitch="";
visibleVar="show";
screenSize = window.innerWidth;
what ="ns4";
}
else if(document.all) {
layerRef="document.all";
styleSwitch=".style";
visibleVar="visible";
screenSize = document.body.clientWidth + 18;
what ="ie4";
}
else if(document.getElementById) {
layerRef="document.getElementByID";
styleSwitch=".style";
visibleVar="visible";
what="dom1";
}
else {
what="none";
newbrowser = false;
}
check = true;
}
The function init() determines for us what browser version that we are dealing with and assigns a values to the variables what, layerRef, styleSwitch and visibleVar, which we will use in our layer visibility toggling functions. We also assign the Boolean value ‘true’ to the check variable.
Armed with this information we can now get stuck in to the meat of the script.
// Toggles the layer visibility on
function showLayer(layerName) {
if(check) {
if (what =="none") {
return;
}
else if (what == "dom1") {
document.getElementById(layerName).style.visibility="visible";
}
else {
eval(layerRef+'["'+layerName+'"]'+styleSwitch+'.visibility="visible"');
}
}
else {
return;
}
}
First, we make sure that the init() function has done it’s job with if(check) – this looks to see if the variable check is true, and then works it’s way through the function. Our first port of call is for DOM1 compliant browsers, where we set the visibility of the layer to visible. For all other browsers we make use of the information gleaned in the init() function. We evaluate the layerRef variable value (document.all for old IE browsers or document.layers for Netscape 4.x), with the layer name, add in the styleSwitch value (.style for old IE and nothing for NN4.x). This eval command allows us to dynamically build the correct command for the appropriate browser.
// Toggles the layer visibility off
function hideLayer(layerName) {
if(check) {
if (what =="none") {
return;
}
else if (what == "dom1") {
document.getElementById(layerName).style.visibility="hidden";
}
else {
eval(layerRef+'["'+layerName+'"]'+styleSwitch+'.visibility="hidden"');
}
}
else {
return;
}
}
The hideLayer function is almost identical to the showLayer function in it’s working, except that it changes the visibility property to hidden.
function hideAll() {
hideLayer('layer1');
hideLayer('layer2');
hideLayer('layer3');
//Put all layers used in the nav here.
//Copy the hideLayer() function above.
}
This function allows us to hide every layer used in out menu system in one fell swoop! This means that we can make sure that all layers are turned off, before making the appropriate layer visible. All you need to do is call the hideLayer function for each layer used in your menu system.
function startTime() {
if (timerOn == false) {
timerID=setTimeout( "hideAll()" , timecount);
timerOn = true;
}
}
This function starts the timer to hide every layer. We use this function to turn all layers off, if the user has moused away from them for more than out timecount value (set at 1000miliseconds in the global variable section)
function stopTime() {
if (timerOn) {
clearTimeout(timerID);
timerID = null;
timerOn = false;
}
}
function onLoad() {
init();
}
This final function runs the init function when the page loads. We also use an onLoad event in the body tag to double check this.
Ok, now on to the HTML pages. First call the menu.js file in the head of your document:
<script type="text/javascript" src="nav.js"></script>
Then, in the body tag, run the init() function:
onLoad="init();"
Then, for the object that is the trigger for the menu use the following code in the <a> tag:
onMouseOver="hideAll(); showLayer('layerName'); stopTime()" onMouseOut="startTime();"
hideAll() hides all layers specified in the hideAll() function in the .js file. (NB: Don’t forget to edit this and insert all layers that should be controlled by this function).
stopTime() stops the timer to hide all layers. This is when the user rolls out of the layer, after a time delay all layers hide.
showLayer('<layerName>') triggers the showing of the layer.
startTime() restarts the timer.
You need to create the layers which are acting as the drop down menu. The ID must be the same as that called in the hideAll() function and the showLayer() and hideLayer() functions.
Put whatever you need in these layers, and then position them so that they are underneath (for vertical menu popups) or beside (for horizontal menu popups) the trigger object.
[edited by: tedster at 4:02 am (utc) on June 12, 2003]
Just one edit (which I only just spotted). In the init() function there are two lines of code which are not needed (remnants from an older project):
screenSize = document.body.clientWidth + 18;
and
screenSize = window.innerWidth;
You can remove these without any effect on the script.
If anyone has any questions or enhancements - fire away!
It's a nice touch, and it doesn't need to be set very high to greatly improve the feel of the menu.
The one thing I look forward to is universal support of onmouseover and onmouseout events on the <div> tag. It means that you start and stop the timer only once per drop down - rather than sometimes having to put multiple instances of it in for each link element in your drop down navigation layer.
Firstly, give your menu <div>'s a very high z-index. Secondly, depending on where you are putting the <div>'s, don't forget that nested layers use the 0,0 position of the parent element.
So, if you have a central layer, nesting a menu <div> inside this, with an absolute position of top: 10 and left: 50 will mean that the menu div is 10 pixels below the top of the parent div and 50 pixels to the right of the left point.
Do you think that this may be a solution to the problem that you've outlined?
My tests show that nested div inherit their 0,0 points from the parent div. Try this CSS:
<style type="text/css">
#mainDiv {
top: 50px;
left:100px;
position: absolute;
width: 300px;
height: 200px;
border: 1px solid #000;
background: #fff;
color: #000;
}#nestDiv {
top: 10px;
left:20px;
position: absolute;
width: 50px;
height: 50px;
border: 1px solid #000;
background: #fcc;
color: #000;
}
</style>
And this HTML:
<div id="mainDiv">
<div id="nestDiv">
Test
</div>
</div>
Let me know what results you get.
I have a question about the browser sniffing portion of your code. By testing for 'document.all' before 'document.getElementById' the variable 'what' will always be set to ie4 if you are using Internet Explorer. Later you say "We evaluate the layerRef variable value (document.all for old IE browsers...".
My question is are you intentionally setting all IE browsers to not be 'dom1'?
Thanks,
Great script btw.
does a new showlayer and hidelayer function need to be created for each layer? I get the feeling I don't need to, but if not what replaces the layerName variable in those functions?
The layerName is the ID of menu div that you want to show/hide. So when you call the show function in your onMouseover you pass the function the ID of the menu you want to show.
Then in the hideAll() function you need to call the hideLayer for each menu you have.
funtion hideAll{
hideLayer('menu ID');
hideLayer('next menu ID')... and so on.
Excellent point - you are quite correct. The way the conditional in init() is set out at the moment does mean that all IE4 and above and Opera will have what as "ie4".
To correct this, just move the DOM1 conditional to the top. Thanks for the catch!
jfred1979: As Reflection says, you only need one function for showing the layers and one for hiding the layers. They layer that you wish to show or hide is fed into that function. So, to show a layer called aboutMenu:
showLayer('aboutMenu');
#1 The visibleVar is never actually used in the hide and show functions.
#2 Shouldnt you also require a 'hideVar', since NN4 visibitity properties are 'show' and 'hide', rather than 'visible' and 'hidden'?
I havent had a chance to test this with NN4 so I could be wrong :).
Quite right - you can remove the visibleVar and the screenSize variable declerations. I apologise, these are artifacts from the devlopment lifecycle that I forgot to remove. Thanks for the heads up.
The script does work in NN4.x (tested in 4.74), which does understand visible and hidden.
any help would be appreciated!
here is my code
<snip>
<snip>
thanks
[edited by: korkus2000 at 7:07 pm (utc) on July 24, 2003]
[edit reason] TOS #21 [/edit]
You will run into problems if you need to have the same link also hide the layer again. TO achieve this, you will need to write a function that detects the current visibility of the layer, and toggle it to visible/hide, depending.
and did I understand that first script reads function showLayer(layer1), then I call that function in function togglevisibility? if yes should I have the function for each layer? ( I think yes, but please correct my)
P.S. I am quaite new in javascript so forgive me for my "stupid" questions..
is this correct that I use: href="#" after <a>
I don't quite understand this, I'm afraid!
and did I understand that first script reads function showLayer(layer1), then I call that function in function togglevisibility?if yes should I have the function for each layer? ( I think yes, but please correct my)
You're right that you call the showLayer function in the toggle function - but you do not need to create a seperate function for each layer. All the functions take in a variable, which is the layer ID. By doing this, it allows us to use the function over and over again through the document.
I don't quite understand this, I'm afraid!
I mean when the link calls script shoud I have link like this:
<a href="#" onClick="hideAll();showLayer('first')">show hidden 1</a>
or
<a href="javascipt:;" onclick="hideAll();showLayer('second')"> show hidden 2</a> </div>
and I have evean more thought ...
then I run show/hide functions in togle script how does it reads which layer to show/hide if it calls the global show/hide functions?
one more:
do I call it by:
function hideLayer(layerName);
btw. Iam begining to se the light in this dark code tunnel.. :)