views:

307

answers:

3

EDIT: See this in action here: http://jsbin.com/emobi/5 -- and that's using mouseenter/mouseleave.

I have a basic menu using some nested UL's, which is pretty standard I think. When hovering over an LI from the "root" menu, I want the UL within that LI to display. Move the mouse off or to another LI, it shows that submenu. Move down to the submenu and it stays while you hover over each element. I had it working with a simple jQuery.hover() set, but then I ran into issues. When on a page, the "root" menu item is given a class of 'current-page' and if that class exists, I want it to display that submenu statically after a mouseout.

Hope I explained that well enough. I just tossed a variable into the hover functions so on the mouseout it ran a .show() on the current-page's submenu. Easy. Except that when I move the mouse between the individual LI's of the submenu, it changes back to the current-page submenu. So I attempted to add a timer element based on another question here. That made things worse -- now the submenus just don't disappear.

Here's my CSS, markup, and JS ... how the heck do I make this work properly?

Markup:

<div id="menu">
<div id="navbar">
    <ul id="firstmenu">
        <li>
            <a href="http://localhost/site/pageone"&gt;page one</a>
            <ul class="submenu">
                <li><a href="http://localhost/site/pageone/subone"&gt;subone&lt;/a&gt;&lt;/li&gt;
                <li><a href="http://localhost/site/pageone/subtwo"&gt;subtwo&lt;/a&gt;&lt;/li&gt;
                <li><a href="http://localhost/site/pageone/subthree"&gt;subthree&lt;/a&gt;&lt;/li&gt;
                <li><a href="http://localhost/site/pageone/subfour"&gt;subfour&lt;/a&gt;&lt;/li&gt;
                <li><a href="http://localhost/site/pageone/subfive"&gt;subfive&lt;/a&gt;&lt;/li&gt;
            </ul>
        </li>

        <li>
            <a href="http://localhost/site/pagetwo"&gt;barely there</a>
            <ul class="submenu">
                <li><a href="http://localhost/site/pageone/subone"&gt;subone&lt;/a&gt;&lt;/li&gt;
                <li><a href="http://localhost/site/pageone/subtwo"&gt;subtwo&lt;/a&gt;&lt;/li&gt;
                <li><a href="http://localhost/site/pageone/subthree"&gt;subthree&lt;/a&gt;&lt;/li&gt;
                <li><a href="http://localhost/site/pageone/subfour"&gt;subfour&lt;/a&gt;&lt;/li&gt;
                <li><a href="http://localhost/site/pageone/subfive"&gt;subfive&lt;/a&gt;&lt;/li&gt;
            </ul>
        </li>
        <li class="current-page">
            <a href="http://localhost/site/pagetwo"&gt;kith & kin</a>
            <ul class="submenu">
                <li><a href="http://localhost/site/pageone/subone"&gt;subone&lt;/a&gt;&lt;/li&gt;
                <li><a href="http://localhost/site/pageone/subtwo"&gt;subtwo&lt;/a&gt;&lt;/li&gt;
                <li><a href="http://localhost/site/pageone/subthree"&gt;subthree&lt;/a&gt;&lt;/li&gt;
                <li><a href="http://localhost/site/pageone/subfour"&gt;subfour&lt;/a&gt;&lt;/li&gt;
                <li><a href="http://localhost/site/pageone/subfive"&gt;subfive&lt;/a&gt;&lt;/li&gt;
            </ul>

        </li>
        <li>
            <a href="http://localhost/site/pagethree"&gt;focal point</a>
            <ul class="submenu">
                <li><a href="http://localhost/site/pageone/subone"&gt;subone&lt;/a&gt;&lt;/li&gt;
                <li><a href="http://localhost/site/pageone/subtwo"&gt;subtwo&lt;/a&gt;&lt;/li&gt;
                <li><a href="http://localhost/site/pageone/subthree"&gt;subthree&lt;/a&gt;&lt;/li&gt;
                <li><a href="http://localhost/site/pageone/subfour"&gt;subfour&lt;/a&gt;&lt;/li&gt;
                <li><a href="http://localhost/site/pageone/subfive"&gt;subfive&lt;/a&gt;&lt;/li&gt;
            </ul>
        </li>
        <li>
            <a href="http://localhost/site/pagefour"&gt;products&lt;/a&gt;
            <ul class="submenu">
                <li><a href="http://localhost/site/pageone/subone"&gt;subone&lt;/a&gt;&lt;/li&gt;
                <li><a href="http://localhost/site/pageone/subtwo"&gt;subtwo&lt;/a&gt;&lt;/li&gt;
                <li><a href="http://localhost/site/pageone/subthree"&gt;subthree&lt;/a&gt;&lt;/li&gt;
                <li><a href="http://localhost/site/pageone/subfour"&gt;subfour&lt;/a&gt;&lt;/li&gt;
                <li><a href="http://localhost/site/pageone/subfive"&gt;subfive&lt;/a&gt;&lt;/li&gt;
            </ul>
        </li>
        <li>
            <a href="http://localhost/site/pagefive"&gt;clients&lt;/a&gt;
        </li>

    </ul>
</div></div>

And here's the CSS:

    #navbar {
     margin: 0;
     padding: 0;
     border: 0;
     text-align: center;
 }

 #firstmenu {
    margin: 6px auto 0 auto;
    font-size: 16px;
    list-style-type: none;
    letter-spacing: -1px;
 }

 #firstmenu li {
    display: inline;
    position:relative;
    overflow: hidden;
    text-align: center;
    margin-right: 10px;
    padding: 5px 15px;
 }

 #firstmenu a {
    text-decoration: none;
    outline: none;
    color: black;
    font-weight: 700;
    width: 75px;
    cursor: pointer;
 }

.current-page {
     color: white;
     background: url(../images/down_arrow.png) bottom center no-repeat;

}
.current-page a {
     color: white;
     border-bottom: 1px solid black;
}

#firstmenu .current-page a {
    color: white;
}

#firstmenu li.hover {
     color: white;
     background: url(../images/down_arrow.png) bottom center no-repeat;
}
#firstmenu li.hover a {
     color: white;
     border-bottom: 1px solid black;
}

#firstmenu li ul li.hover {
     color: white;
     background: none;
}
#firstmenu li ul li.hover a {
     color: white;
     border-bottom: none;
     text-decoration: underline;
}

#firstmenu li ul {
    width: 900px;
     color: white;
     font-size: .8em;
     margin-top: 3px;
     padding: 5px;
     position: absolute;
     display: none;
}

#firstmenu li ul li {
    list-style: none;
    display: inline;
    width: auto;
}

#firstmenu li ul li a {
    color: white;
    font-weight: normal;
    border: none;
}

.sub-current-page {
    font-weight: bold;
    text-decoration: underline;
}

#firstmenu li ul li.sub-current-page a {
    font-weight: bold;
}

And lastly, my not-at-all-working JS (this is in a $(document).ready(), of course):

// Initialize some variables
    var hideSubmenuTimer = null;
    var current_page;
$('.current-page ul:first').show();

    // Prep the menu
    $('#firstmenu li').hover(function() {
        // Clear the timeout if it exists
        if(hideSubmenuTimer) { clearTimeout(hideSubmenuTimer); }

        // Check if there's a current-page class set
        if($('li.current-page').length > 0) {
            current_page = $('li.current-page');
        } else {
            current_page = false;
        }

        // If there's a current-page class, hide it
        if(current_page) { current_page.children('ul:first').hide(); }

        // Show the new submenu
        $(this).addClass('hover').children('ul:first').show();

    }, function(){
        // Just in case
        var self = this;
        // Clear the timeout if it exists
        if(hideSubmenuTimer) { clearTimeout(hideSubmenuTimer); }

        // Check if there's a current-page class set
        if($('li.current-page').length > 0) {
            current_page = $('li.current-page');
        } else {
            current_page = false;
        }

        // Set a timeout on hiding the submenu
        hideSubmenuTimer = setTimeout(function() {
            // Hide the old submenu
            $(self).removeClass('hover').children('ul').hide();

            // If there's a current-page class, show it
            if(current_page) { current_page.children('ul:first').show(); current_page.css('color', 'white'); }
        }, 500);
    });

So what am I doing so wrong?

As a side note, I'm using the $('.current-page ul:first').show() because if I gave .current-page any "display" setting in the CSS, it positioned it really weirdly on the page.

A: 

Why are you messing with timeouts? Should you just .toggle()?

Josh K
.toggle() is for a click event; I want the menu work when the mouse hovers over.
Nathan Loding
Yes, you can .toggle() a class on an element in the `function(){...}` of the `.hover(`.
Josh K
A: 

I rewrote it to not use hover, but mouseover and mouseout. Not sure if you like it but here is the code:

$(document).ready(function(){
          $(".submenu").hide();
          $("li").mouseover(function(){
            $(this).find('.submenu').show();
            });
          $("li").mouseout(function(){
            $(this).find('.submenu').hide();
            })
});

edit: I took the time to review your code and found the offending line:

#firstmenu li {
    display: inline;

Since the nested lists are inside of the first menu div this applies to them too. A nested list inlined cannot be placed and thus no longer placed as a child of its dom parent in the layout engine. The result is that mousing over the submenu is still considered mousing over its dom parent (due to bubbling), but between list items you will no longer be mousing over the dom parent (as it is in the original design). You need to rethink your layout design, because right now its breaking the layout engine.

You should probably also rethink using lists as tables...

tzenes
`mouseover` and `mouseout` will fire going into the children, hiding them...you would want `mouseenter` and `mouseleave` here.
Nick Craver
Actually when it enters the child it'll trigger the `mouseover` on the child li, which will trickle up to the original li, and it will redisplay them. Although you are right, `mouseenter` and `mouseleave` would be cleaner
tzenes
@tzenes - The `mouseout` event of the previous element fires before the `mouseover` of the next element, think of how `focus` and `blur` work, same situation. So the child element gets hidden before it's `mouseover` *would* fire, but it's not visible, it just got hidden, so it'll never fire.
Nick Craver
This still causes one of the first issues I ran into. When sliding the cursor horizontally over the submenu li's, the menu disappears and reverts back to the current pages menu when leave one li. Here's a pastebin of the issue in action: http://pastebin.com/ZTZipdGG
Nathan Loding
Even better -- I forgot out JS Bin -- here it is, live: http://jsbin.com/emobi/5
Nathan Loding
@Nick - try it here: http://jsbin.com/ejuwe/ You're having an issue with concurrency. When the mouse move is updated it looks at which dom element its over, it then sees this is not the same element and `mouseout` gets added to the event queue, then it sees this is a new element and adds `mouseover` to the event queue. Since it samples the dom element at the beginning of the function it still thinks its over the child `li` even though its being removed
tzenes
@tzenes -- what to do you mean "rethink using lists as tables"? I was able to resolve the issue (see my posted answer), without changing the `display: inline` style. I don't think it's removing it from being a child of the list-item. Could be wrong, I'm no DOM expert.
Nathan Loding
+1  A: 

The answer is that the script was attempting to run the hover/mouseenter/whatever functions on the LI's of the submenu. By giving each of the root menu LI's their own class, it now works. That way it's not calling the functions on the submenu LI's. Here's the finished function:

$('#firstmenu .root-item').mouseenter(function() {
    $(this).addClass('hover').children('ul:first').show();
    if($('.current-page').length > 0) {
        $('.current-page').children('ul:first').hide();
    }
}).mouseleave(function() {
    $(this).removeClass('hover').children('ul').hide();
    if($('.current-page').length > 0) {
        $('.current-page').children('ul:first').show();
    }
});
Nathan Loding