tags:

views:

120

answers:

4

I'm in the process of learning Javascript and I'm trying to create a simple dropdown menu. An example of my desired functionality can be seen on the google homepage in the top menu with the "more" and "settings" dropdown. Specifically when you click off the menu, the menu disappears.

What code do I need to place in the hideMenus function in Javascript to hide the visible uls when a click occurs anywhere on screen?

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"&gt;  
<html xmlns="http://www.w3.org/1999/xhtml"&gt;  

<head>  
<meta content="text/html; charset=utf-8" http-equiv="Content-Type" />  
<title>Untitled 1</title>  

<style type="text/css">  
a  
{  
    color:blue;  
}  

.info ul.submenu  
{  
    border: solid 1px #e0e0e0;  
    background-color: #fff;  
    position: absolute;  
    padding: 0;  
    z-index: 2;  
    display: none;  
}  

.info ul.submenu li  
{  
    display: block;  
    border-top: solid 1px #e0e0e0;  
    margin: 0px 10px 0 10px;  
}  

.info ul.submenu li a  
{  
    display: block;  
    padding: 7px 0px 6px 0;  
    color: #1177ee;  
    cursor:pointer;  
}  

</style>  

<script type="text/javascript">  

function hideMenus()
{
//TODO
}

function menu(id) {     
    var myLayer = document.getElementById(id);     

    myLayer.onblur = function() {       
        myLayer.style.display = 'none';   
    };   

    if (myLayer.style.display == "none" || myLayer.style.display == "") {     
        myLayer.style.display = "block";     
    } else {     
        myLayer.style.display = "none";     
    }     
}  

</script>  
</head>  

<body onclick="hideMenus();">  
<div class="info">     
     Some Text Boom A <a  onclick="menu('id1');">Link</a> | More text    
     <a onclick="menu('id2');">Another Link</a> | more text    
     <ul id="id1" class="submenu">     
       <li><a href="dfhdfh">A1</a></li>     
       <li><a href="aetjetjsd">A2 This is Long</a></li>     
       <li><a href="etetueb">A3</a></li>     
     </ul>     
    <ul id="id2" class="submenu">     
       <li><a href="dfhdfh">B1</a></li>     
       <li><a href="aetjetjsd">B2</a></li>     
       <li><a href="etetueb">B3</a></li>     
     </ul>     
  </div>    
</body>  
</html>   

I do not want to use jQuery.

A: 

I'd advise using jQuery rather than [edit] vanilla javascript, you will save yourself a whole lot of pain in the long run. Using jQuery there are simple selectors, with which you can easily target elements by id, and there are functions for show/hide which you can call directly.

fredley
Thanks, but I don't want to use jQuery.
Jim Roboju
I agree with this post regarding usage of jQuery for things like this, because you'll achieve things faster and better. What I do not agree with in this post is this "...using jQuery rather than javascript...". jQuery is a Javascript framework, not a whole new language to learn. I've seen a lot of people regarding to jQuery as a totally new language in comparison to Javascript, which is just plain wrong.
brozo
@brozo - in their defense, the first time with jQuery it does feel like a whole new language. Some of the selector methods take a bit of getting used to.
Joel Etherton
I, for one, applaud the OP for wanting to use vanilla javascript. Sure, there are some functions necessary to be able to use it cross-browser (a 'boilerplate', if you will), but it's a lovely language, and well worth learning.
Ryan Kinal
Learning JavaScript from scratch is a good thing to do, and it'll give you a better understanding of how stuff like jQuery works. However for anything you actually want to put on a site, I'd use jQuery every time, over vanilla.
fredley
@fredley: Agreed. The OP explicitly said: “I'm in the process of learning Javascript”. It's completely sound to avoid using any framework if you want to *learn* JavaScript.
Marcel Korpel
@Ryan Kinal - I agree javascript is a beautiful language, but it wasn't until I used jQuery that I appreciated just how flexible and how easy it was to throw some code together to do what you want. The way the jQuery API was designed also encourages you to play more with javascript features like anonymous functions, closures, objects and prototypes.
box9
@box9 - I'm not sure that's entirely true. Yes, jQuery encourages the use of anonymous functions, but unless you're writing plugins, I find it very unlikely that you'll be using objects and prototypes. Maybe closures. That could go either way. I think it's exactly the ease of using jQuery that prevents people from exploring the language features that make JavaScript great.
Ryan Kinal
A: 

You can capture a click anywhere if you put onclick on the body. Because of the javascript event propagation model, if you click anywhere on any element and you don't stop the event from propagating, it will reach body and hide the menus.

So basically this means that you want to capture body onclick and make it to hide the menus so when you click on any area of the page, it will close the menus.

But this hides a bit of unwanted behaviour - when you click on the button to show the menu, the menu will display and quickly after that hide (when the event reaches body). To prevent this, you will want to stop the event from propagating when you click on the button which shows the menu (you can see how this works in the code I posted below). The code shows where you need to touch to make it work nicely.

// this function stops event e or window.event if e is not present from 
// propagating to other elements.
function stop_event(e) {
   if(!e) {
      e = window.event;
   }
   if (e.stopPropagation) e.stopPropagation();
   e.cancelBubble = true;
   if (e.preventDefault) e.preventDefault();
   e.returnValue = false;
   return false;
}

// now you just hide all the menus in your hideMenus
function hideMenus()
{
    //pseudocode!
    for all visible menus - hide // or if you want you can hide all menus, 
                                 // the hidden will remain hidden
}

Now the important part.

function menu(id) {     
    // your stuff here
    stop_event(); // this will stop the event going down to the body 
                  // and hiding it after showing it
                  // this means it will not flicker like: show-hide
}  

And finally on your whole UL element:

//partly pesudocode
ul.onclick = function() { stop_event(); }

To explain again what this does:

1st. You hook your hideMenu function to body.onclick. This means that it will always hide the menus if we don't stop the event.

2nd. When you click the menu button, you show the menu and then we stop the event from going to the body. This way, the body.onclick will not fire and it will not hide the menu right after we opened it.

3rd. The ul.onclick means that the menu will not hide itself when we click on it (though if you want the menu to hide when you click the menu itself, you can remove this part).

bisko
You're defining `stop_event` with parameter `e`, but you don't call it with an event parameter?
Marcel Korpel
E is not necessary as you may see. Also if you look closely at the code, it does just what the op wanted - hide the menus if you click anywhere on the screen (if he means the webpage itself, not the OS or browser frame), because he hooked hideMenu on the body click. As for the last part ... will edit it now, it really seems stupid after reading it once more.
bisko
Whoops, overlooked the part with `onclick="hideMenus();"`. `e` is necessary, as `window.event` is only supported by IE. But when using DOM level 0 inline event handlers (in non-IE browsers), the `event` interface is passed as a parameter to a *wrapper function*, so `e` is [inaccessible](https://developer.mozilla.org/en/DOM/element#Event_Handlers). Also see [JSBin](http://jsbin.com/imuwo3/2/edit) to try. So stopping event bubbling this way isn't possible. About the explanation: indeed, this is much clearer now.
Marcel Korpel
+1  A: 

It looks like you have a pretty decent setup as-is. You'll likely run into some event bubbling problems (for more info, take a look at PPK's Event Order Article). That seems to be outside of the scope of your current question, so I'll just give you what you asked for:

hideMenus()
{
    var uls = document.getElementsByTagName('ul'), i;
    for (i = 0; i < uls.length; i++)
    {
        if (uls[i].className === 'submenu' && uls[i].style.display !== 'none')
        {
            uls[i].style.display = 'none';
        }
    }
}

First, we get all the <ul>'s on the page. Then, we loop through all of them, check to see if it's a submenu, and if it's currently displayed. If both are true, then we hide it.

There are a couple faults with this code:

  • If the uls have more than one class (class="animal submenu"), then it will not hide the menu
  • It will look through all the <ul>'s on the page. This isn't exactly efficient, but it's the only way to do it without cross-browser support for getElementsByClass.

These aren't huge faults, especially if you're only using this to learn about javascript, and if you closely control your code (i.e. no other developers are working on it). All in all, it's a good stepping stone.

In the future, I'd suggest using addEvent - a fairly common function that allows you to add event handlers to elements without using onclick="...". There are a couple different implementations of it, but they (almost) all work the same from your perspective. Here are links to Dean Edwards's Version and John Resig's Version

Good luck!

Ryan Kinal
`getElementsByTagName`
lincolnk
Oops! Good catch. Fixed, and +1
Ryan Kinal
A: 

Here is more or less the logic we use in our web app for drop down menus:

<html>
<head>
    <title></title>
</head>
<body>
    <div style="position:relative;width:250px">
      <a id="link" href="javascript:" onclick="showDiv(this)">Show menu</a>
      <ul id="entries" style="display:none;background:#DEF;padding:0;margin:0">
        <li>item 1</li>
        <li>item 2</li>
      </ul>
      <input id="inp" style="position:absolute;left:-30px;width:0" />
    </div>

    <script>
        function showDiv(lnk){
            var entries = document.getElementById('entries'),
                inp = document.getElementById('inp'),
                nh = 'data-nohide';
            //show the entries
            entries.style.display = 'block';
            entries.removeAttribute(nh);
            inp.focus();
            //if mouse over, can't close it
            entries.onmouseover = function(){ 
                this.setAttribute(nh, true);
                inp.focus();
            }; 
            //if mouse out, can close it
            entries.onmouseout  = function(){ 
                this.removeAttribute(nh);
            };
            entries.onclick = function(e){
                //code when the user clicks on the menu...
                alert((e.target||e.sourceElement).innerHTML);
                this.style.display = 'none';
            };
            //if the user press ESC
            inp.onkeyup = function(e){
                if(e.keyCode === 27){
                    this.style.display = 'none';
                    this.removeAttribute(nh);
                }else{
                    //do something else with other keys(ie:down, up, enter)...
                    inp.focus();
                }
            };
            //click somewhere else input onblur
            inp.onblur = function(){
                if(!entries.getAttribute(nh)){
                    entries.style.display = 'none';
                    entries = inp = null;
                }
            };
        }
    </script>
</body>
</html>

The trick is to use an input field that has the focus, and when it looses it an onblur is triggered and close the menu.

The mouseover, mouseout are there to prevent the onblur to fire when the user click an item in the menu.

To have a toggle effect like an open/close on the link, I guess 2 links that hide each other are needed.

Mic