homepage Welcome to WebmasterWorld Guest from 54.166.113.249
register, free tools, login, search, pro membership, help, library, announcements, recent posts, open posts,
Become a Pro Member

Home / Forums Index / Code, Content, and Presentation / JavaScript and AJAX
Forum Library, Charter, Moderator: open

JavaScript and AJAX Forum

    
JavaScript Jumpstart #3 - dHTML menus
show-hide layers menu system
tedster

WebmasterWorld Senior Member tedster us a WebmasterWorld Top Contributor of All Time 10+ Year Member



 
Msg#: 442 posted 7:24 pm on Jun 10, 2003 (gmt 0)

With this issue we welcome a guest writer, BlobFisk [webmasterworld.com], who shares with us his industrial strength script for creating the popular dHTML "cascading menu". If you've ever wanted to tear apart the pile of code involved and see what makes it tick - here's the article you need to guide you.

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! Ive 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 its job with if(check) this looks to see if the variable check is true, and then works its 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 its 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;
}
}

Similarly, the stopTime function stops the timer altogether.


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: Dont 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]

 

korkus2000

WebmasterWorld Senior Member korkus2000 us a WebmasterWorld Top Contributor of All Time 10+ Year Member



 
Msg#: 442 posted 12:37 pm on Jun 11, 2003 (gmt 0)

Excellent script and post BlobFisk!

BlobFisk

WebmasterWorld Senior Member blobfisk us a WebmasterWorld Top Contributor of All Time 10+ Year Member



 
Msg#: 442 posted 1:12 pm on Jun 11, 2003 (gmt 0)

Thanks korkus!

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!

tedster

WebmasterWorld Senior Member tedster us a WebmasterWorld Top Contributor of All Time 10+ Year Member



 
Msg#: 442 posted 4:12 am on Jun 12, 2003 (gmt 0)

Not a question, but a comment. I like the fact that you have a timer function. Some dHTML menu systems can be almost scary when a mouseout generates an instantaneous reaction, with stuff popping up and vanishing in a very uncomfortable and distracting fashion.

It's a nice touch, and it doesn't need to be set very high to greatly improve the feel of the menu.

BlobFisk

WebmasterWorld Senior Member blobfisk us a WebmasterWorld Top Contributor of All Time 10+ Year Member



 
Msg#: 442 posted 10:54 am on Jun 12, 2003 (gmt 0)

That was something that was quite important for me to build in day 1, as I feel there is quite a usability issue with menu's that vanish the millisecond you mouse out of them - if it's an accident you have to start from scratch!

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.

jfred1979

10+ Year Member



 
Msg#: 442 posted 7:15 pm on Jun 18, 2003 (gmt 0)

I'm trying to implement the drop down menu outlined in this thread, and I'm having problems positioning the div that will contain the menu items without it taking up space in the body of my document. My site uses a centered design so when I use absolute positioning the horizontal position of the menu is off. Am I not understand the basic way this menu system should be implemented?

Reflection

10+ Year Member



 
Msg#: 442 posted 11:02 pm on Jun 18, 2003 (gmt 0)

jfred

If you are using a centered design, the 'centered' position will vary by user's screen resolution etc. so your absolutely positioned menu's will not line up with your centered design.

You will either have to work out some relative positioning or change to a left-aligned design.

jfred1979

10+ Year Member



 
Msg#: 442 posted 2:04 am on Jun 19, 2003 (gmt 0)

I understand the reason for the problem, but using relative positioning causes the div to occupy screen space even when it's not visible. Is there way around this? I really like this script because it seems to be the cleanest coded solution for menus I can find, I prefer to use it rather than a few others I've tried....

BlobFisk

WebmasterWorld Senior Member blobfisk us a WebmasterWorld Top Contributor of All Time 10+ Year Member



 
Msg#: 442 posted 10:24 am on Jun 19, 2003 (gmt 0)

That's a very interesting question.

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?

jfred1979

10+ Year Member



 
Msg#: 442 posted 2:31 pm on Jun 19, 2003 (gmt 0)

That's what I'm doing right now BlobFisk, but when I use absolute positioning it's basing the 0 coordinates on the browser window, not the parent div. I thought it was only with relative positioning that the parent layer was used.

BlobFisk

WebmasterWorld Senior Member blobfisk us a WebmasterWorld Top Contributor of All Time 10+ Year Member



 
Msg#: 442 posted 8:34 pm on Jun 19, 2003 (gmt 0)

Hmmm - that's strange jfred1979.

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.

jfred1979

10+ Year Member



 
Msg#: 442 posted 8:47 pm on Jun 19, 2003 (gmt 0)

Well, you are absolutely right. The nested div uses the parent for it's base coordinates. I'll have to go back and check my code, must have made an error somewhere. What a revelation! This changes a lot of issues I was having trying to convert this originally table based site to CSS.

BlobFisk

WebmasterWorld Senior Member blobfisk us a WebmasterWorld Top Contributor of All Time 10+ Year Member



 
Msg#: 442 posted 9:31 pm on Jun 19, 2003 (gmt 0)

Oh yeah! You'll find that CSS is full of revelations!

Reflection

10+ Year Member



 
Msg#: 442 posted 10:43 pm on Jun 19, 2003 (gmt 0)

BlobFisk:

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.

jfred1979

10+ Year Member



 
Msg#: 442 posted 10:48 pm on Jun 19, 2003 (gmt 0)

One more question now that I have the basic function of the script working, 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? Sorry for the basic question but I'm just learning Javascript and I haven't done any sort of "real" programing for years.....

Reflection

10+ Year Member



 
Msg#: 442 posted 11:10 pm on Jun 19, 2003 (gmt 0)

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.

BlobFisk

WebmasterWorld Senior Member blobfisk us a WebmasterWorld Top Contributor of All Time 10+ Year Member



 
Msg#: 442 posted 10:15 am on Jun 20, 2003 (gmt 0)

Hey Reflection,

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');

jfred1979

10+ Year Member



 
Msg#: 442 posted 5:10 pm on Jun 24, 2003 (gmt 0)

Okay I think I have the whole thing sorted out now. This makes my code much cleaner than using the Dreamweaver produced menus as I was doing before....

BlobFisk

WebmasterWorld Senior Member blobfisk us a WebmasterWorld Top Contributor of All Time 10+ Year Member



 
Msg#: 442 posted 5:17 pm on Jun 24, 2003 (gmt 0)

Glad you got it working jfred1979!

Reflection

10+ Year Member



 
Msg#: 442 posted 5:46 pm on Jun 24, 2003 (gmt 0)

Couple things I just noticed...

#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 :).

BlobFisk

WebmasterWorld Senior Member blobfisk us a WebmasterWorld Top Contributor of All Time 10+ Year Member



 
Msg#: 442 posted 5:57 pm on Jun 24, 2003 (gmt 0)

Hey Reflection,

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.

captainbri

10+ Year Member



 
Msg#: 442 posted 7:05 pm on Jul 24, 2003 (gmt 0)

hi, im having soem trouble making this work in ns 4.xx

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]

BlobFisk

WebmasterWorld Senior Member blobfisk us a WebmasterWorld Top Contributor of All Time 10+ Year Member



 
Msg#: 442 posted 9:02 am on Jul 25, 2003 (gmt 0)

Hey captainbri,

If you could describe what is (or is not) happening, then we can try and get to the root of the problem.

carramba

10+ Year Member



 
Msg#: 442 posted 6:11 pm on Aug 13, 2003 (gmt 0)

It seams like very nice nav meniu, but I have another question:
I have tryed how can I use this code to show/hide layer on click? I the poin is that I have php meniu and I only want to swith layer on "extra meniu" also when user clicks on link layer changes when hi/she click agai u se onother layer.. problem is that when it click for second layer it show OK, but when user click on firs (visited) layer again it dont shows..
hoppe I make sense..

BlobFisk

WebmasterWorld Senior Member blobfisk us a WebmasterWorld Top Contributor of All Time 10+ Year Member



 
Msg#: 442 posted 8:50 am on Aug 14, 2003 (gmt 0)

Instead of using onMouseOver, use onClick to display the layer. The function works exactly the same way.

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.

carramba

10+ Year Member



 
Msg#: 442 posted 11:52 am on Aug 14, 2003 (gmt 0)

Its sound so simple when you say it.. but I have tryed.. but its to difficult to me.. ( or I just make it difficulf..) what arguments and function should I use do achiev it?

BlobFisk

WebmasterWorld Senior Member blobfisk us a WebmasterWorld Top Contributor of All Time 10+ Year Member



 
Msg#: 442 posted 12:42 pm on Aug 14, 2003 (gmt 0)

In pseudocode:


function toggleVisibility(layerName) {
if layerName visibility == visible {
then run the hideLayer function;
}
else {
then run the showLayer function;
}
}

That should do it.

carramba

10+ Year Member



 
Msg#: 442 posted 2:28 pm on Aug 14, 2003 (gmt 0)

yeah.. I am in real war with you script :)
is this correct that I use: href="#" after <a>

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..

BlobFisk

WebmasterWorld Senior Member blobfisk us a WebmasterWorld Top Contributor of All Time 10+ Year Member



 
Msg#: 442 posted 10:21 am on Aug 15, 2003 (gmt 0)


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.

carramba

10+ Year Member



 
Msg#: 442 posted 11:12 am on Aug 15, 2003 (gmt 0)

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.. :)

Global Options:
 top home search open messages active posts  
 

Home / Forums Index / Code, Content, and Presentation / JavaScript and AJAX
rss feed

All trademarks and copyrights held by respective owners. Member comments are owned by the poster.
Home ¦ Free Tools ¦ Terms of Service ¦ Privacy Policy ¦ Report Problem ¦ About ¦ Library ¦ Newsletter
WebmasterWorld is a Developer Shed Community owned by Jim Boykin.
© Webmaster World 1996-2014 all rights reserved