Forum Moderators: open
I've got a menu with top links that shows or hides sub-items on mouseover/out and I need a delay 3 seconds delay before the show/hide is called so visitors can absorb the navigation concept. Simple... use setTimeout, right?
Unfortunately, I've tried inserting setTimeout in all kinds of places (onload, the script, links), but keep getting nada. Always checking syntax carefully, of course, I use:
setTimeout("closeItem()", 3000);
setTimeout("closeItem()", 3000);
This is really driving me insane. Any help would be appreciated.
Following are the script and menu. Thanks!
<html>
<head>
<script language="JavaScript" type="text/JavaScript">
function getItem(id)
{
var itm = false;
if(document.getElementById)
itm = document.getElementById(id);
else if(document.all)
itm = document.all[id];
else if(document.layers)
itm = document.layers[id];
return itm;
}
function closeItem(id)
{
itm = getItem(id);
if(!itm)
return false;
itm.style.display = 'none';
}
function openItem(id)
{
itm = getItem(id);
itm.style.display = '';
return false;
}
function init()
{
this.closeItem("sub1");
this.closeItem("sub2");
this.closeItem("sub3");
}
</script>
</head>
<body onload="init();">
<ul>
<li><a href="#" onMouseOver="openItem('sub1');closeItem('sub2');closeItem('sub3');" onMouseOut="return true;">Top Link 1</a></li></ul>
<ul id="sub1">
<li><a href="#">sub1a</a></li>
<li><a href="#">sub1b</a></li>
<li><a href="#">sub1c</a></li>
</ul>
<ul>
<li><a href="#" onMouseOver="openItem('sub2');closeItem('sub1');closeItem('sub3');" onMouseOut="return true;">Top Link 2</a></li></ul>
<ul id="sub2">
<li><a href="#">sub2a</a></li>
<li><a href="#">sub2b</a></li>
<li><a href="#">sub2c</a></li>
</ul>
<ul>
<li><a href="#" onMouseOver="openItem('sub3');closeItem('sub1');closeItem('sub2');" onMouseOut="return true;">Top Link 3</a></li></ul>
<ul id="sub3">
<li><a href="#">sub3a</a></li>
<li><a href="#">sub3b</a></li>
<li><a href="#">sub3c</a></li>
</ul>
</body>
</html>
1. <script language="JavaScript" type="text/JavaScript">
Don't use the language attribute. Also, the type should be "text/javascript" (I'm not sure if case matters, but it's common convention).
2. It's better to use setTimeout with a function reference vs. a string. See example below.
3. Your getItem method is really old school. All modern browsers support getElementById. There's no need to add all this extra logic.
4. You should avoid inline event handlers. Instead, use unobtrusive JavaScript to cleanly attach your event handlers, keeping your HTML nice and clean. It also makes it easier to get an idea what those with JavaScript disabled will see.
5. Your lists looked like the semantics were slightly wrong, so I made a little change.
6. Performance tip: Put scripts at the bottom of your page, so your content loads first.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<style type="text/css">
ul {
list-style: none;
margin: 0;
padding: 0;
}
</style>
<title>Example</title>
</head>
<body>
<ul>
<li><a href="#sub1" id="top1">Top Link 1</a>
<div id="sub1">
<ul>
<li><a href="#">sub1a</a></li>
<li><a href="#">sub1b</a></li>
<li><a href="#">sub1c</a></li>
</ul>
</div>
</li>
<li><a href="#sub2" id="top2">Top Link 2</a>
<div id="sub2">
<ul>
<li><a href="#">sub2a</a></li>
<li><a href="#">sub2b</a></li>
<li><a href="#">sub2c</a></li>
</ul>
</div>
</li>
<li><a href="#sub3" id="top3">Top Link 3</a>
<div id="sub3">
<ul>
<li><a href="#">sub3a</a></li>
<li><a href="#">sub3b</a></li>
<li><a href="#">sub3c</a></li>
</ul>
</div>
</li>
</ul>
<script type="text/javascript">
var menuTimers = {
items : ['sub1', 'sub2', 'sub3'],
open : null,
close : null,
init : function() {
for (var i = 0; i < menuTimers.items.length; i++) {
var itm = document.getElementById(menuTimers.items[i]);
if (itm) { itm.style.display = 'none'; }
}
// Attach event listeners
var top1 = document.getElementById('top1');
top1.onmouseover = function() {
menuTimers.openItem('sub1');
}
top1.onmouseout = function() {
menuTimers.closeItem('sub1');
}
var top2 = document.getElementById('top2');
top2.onmouseover = function() {
menuTimers.openItem('sub2');
}
top2.onmouseout = function() {
menuTimers.closeItem('sub2');
}
var top3 = document.getElementById('top3');
top3.onmouseover = function() {
menuTimers.openItem('sub3');
}
top3.onmouseout = function() {
menuTimers.closeItem('sub3');
}
},
openItem : function(id) {
clearTimeout(menuTimers.open);
menuTimers.open = setTimeout(function() {
var itm;
// Hide others
for (var i = 0; i < menuTimers.items.length; i++) {
if (id!= menuTimers.items[i]) {
itm = document.getElementById(menuTimers.items[i]);
if(itm) { itm.style.display = 'none'; }
}
}
// Show this one
itm = document.getElementById(id);
if(itm) { itm.style.display = ''; }
}, 1000); // Set the timeout value here. I used 1 second for testing.
},
closeItem : function(id) {
clearTimeout(menuTimers.close);
menuTimers.close = setTimeout(function() {
var itm = document.getElementById(id);
if(itm) { itm.style.display = 'none'; }
}, 1000); // Set the timeout value here. I used 1 second for testing.
}
}
window.onload = menuTimers.init;
</script>
</body>
</html>
If you have any questions on the changes I've made above, please don't hesitate to ask. :)
For example, if a user clicked "sub2b" and was directed to the "sub2b" page in the "Top Link 2" section, what would be the best way to have the entire "Top Link 2" menu open on that page, as well as all pages in that section?
For example, if a user clicked "sub2b" and was directed to the "sub2b" page in the "Top Link 2" section, what would be the best way to have the entire "Top Link 2" menu open on that page, as well as all pages in that section?
1. Write a function that sets display to visible only
keepopen : function (id) {
itm = getItem(id);
itm.style.display = '';
return false;
}
2. If the body has an id where a menu is to be always open, run that
function on that menu (my syntax is horrible... guidance would be greatly appreciated here), something like:
if (body.id =='top2page') = keepopen.top2;
Would this method sitll work within the rest of the script, so that when a user mouseovers the always-open menu, the open/close function isn't called?
2. I've replaced the items array with a menus array. This is an array of objects. Each object has 2 properties:
top - The id of the top link for the menu
sub - The id of the sub link container
3. I've cleaned up the code that attaches the listeners to use the menus array (so you don't need to hand code each one).
4. Added some code that removes (ie - nulls out) the current page from the menus array.
It's worth noting that I've not really included a bunch of validation (for example, getting the id of the page I'm stripping off the first 5 characters without checking the length or verifying that it's in the correct format of "page-subN". It might be a good idea to add a few safety checks.
Here's the updated code:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<style type="text/css">
ul {
list-style: none;
margin: 0;
padding: 0;
}
</style>
<title>Example</title>
</head>
<body id="page-sub2">
<ul>
<li><a href="#sub1" id="top1">Top Link 1</a>
<div id="sub1">
<ul>
<li><a href="#">sub1a</a></li>
<li><a href="#">sub1b</a></li>
<li><a href="#">sub1c</a></li>
</ul>
</div>
</li>
<li><a href="#sub2" id="top2">Top Link 2</a>
<div id="sub2">
<ul>
<li><a href="#">sub2a</a></li>
<li><a href="#">sub2b</a></li>
<li><a href="#">sub2c</a></li>
</ul>
</div>
</li>
<li><a href="#sub3" id="top3">Top Link 3</a>
<div id="sub3">
<ul>
<li><a href="#">sub3a</a></li>
<li><a href="#">sub3b</a></li>
<li><a href="#">sub3c</a></li>
</ul>
</div>
</li>
</ul>
<script type="text/javascript">
var menuTimers = {
menus : [{top:"top1", sub:"sub1"},{top:"top2", sub:"sub2"},{top:"top3", sub:"sub3"}],
open : null,
close : null,
init : function() {
// Remove the current page from menus array
var page = document.body.id;
if (page) {
page = page.substring(5); // Strip off the 'page-' part of the id
}
for (i = 0; i < menuTimers.menus.length; i++) {
if (menuTimers.menus[i].sub == page) {
menuTimers.menus[i] = null;
continue;
}
var top = document.getElementById(menuTimers.menus[i].top);
var itm = document.getElementById(menuTimers.menus[i].sub);
if (itm) { itm.style.display = 'none'; }
// Attach event listeners
top.onmouseover = function(id) {
return function() {
menuTimers.openItem(id);
};
}(menuTimers.menus[i].sub);
top.onmouseout = function(id) {
return function() {
menuTimers.closeItem(id);
};
}(menuTimers.menus[i].sub);
}
},
openItem : function(id) {
clearTimeout(menuTimers.open);
menuTimers.open = setTimeout(function() {
var itm;
// Hide others
for (var i = 0; i < menuTimers.menus.length; i++) {
if (menuTimers.menus[i] == null) { continue; }
if (id!= menuTimers.menus[i].sub) {
itm = document.getElementById(menuTimers.menus[i].sub);
if(itm) { itm.style.display = 'none'; }
}
}
// Show this one
itm = document.getElementById(id);
if(itm) { itm.style.display = ''; }
}, 1000); // Set the timeout value here. I used 1 second for testing.
},
closeItem : function(id) {
clearTimeout(menuTimers.close);
menuTimers.close = setTimeout(function() {
var itm = document.getElementById(id);
if(itm) { itm.style.display = 'none'; }
}, 1000); // Set the timeout value here. I used 1 second for testing.
}
}
window.onload = menuTimers.init;
</script>
</body>
</html>
[edited by: Fotiman at 4:21 pm (utc) on Jan. 31, 2008]
NOW I see! This is awesome. I couldn't get it clear in my head before how to selectively keep one menu always open, but the nulling out makes total sense (now that I actually see it). And good call attaching the listeners to use the menus array: even more elegant (and maintainable). I see your point about the safety check, too - but as long as the page ids are consistent, this should work just fine.
Again, Fotiman, thanks for all your help. Above and beyond, sir.