Thursday, April 21, 2011

simple jquery dropdown - clearTimeout, setTimeout issues

HTML:

<ul class="topnav">
    <li><a href="#"><span>One</span></a></li>
    <li><a href="#"><span>Two</span></a></li>
    <li>
        <li><a href="#"><span>Three</span></a></li>
        <ul class="subnav">
            <li><a href="#">A</a></li>
            <li><a href="#">B</a></li>
            <li><a href="#">C</a></li>
        </ul>
    </li>
</ul>

jquery:

var timeout = null;

$(document).ready(function() {

    $("ul.topnav li").mouseover(function() {

        if (timeout) clearTimeout(timeout);

        $(this).find("ul.subnav").slideDown('fast').show();

    }).mouseout(function() {
        timeout = setTimeout(closemenu, 500);
    });

    // sub menu mouseovers keep dropdown open
    $("ul.subnav li").mouseover(function() {
        if (timeout) clearTimeout(timeout);
    }
    ).mouseout(function() {
        timeout = setTimeout(closemenu, 500);
        // alert(timeout);

    });

    // any click closes
    $(document).click(closemenu);
});

// Closes all open menus 
function closemenu() {
    $('ul.subnav:visible').hide();
    if (timeout) clearTimeout(timeout);
} 

I'm having issues with timeout. In use, if i mouseover "Three", the dropdown stays out forever. if i mouseover "A", dropdown will stay out forever, but if I mouseover "B" or anything lower, the menu will close on me. if you uncomment "// alert(timeout);" it gets there for B, (and A) but timeout will have a value. why is this? i thought clearTimeout would null the timeout variable?

From stackoverflow
  • youre trying to build a drop down menu? why not use existing jquery plugins for that or better yet, css-only drop down menu like http://purecssmenu.com/?

    : i'm trying to work it into an existing website menu, so i was trying to go the simplest route possible
  • You can simplify your code overall by using .hover() and .data() like this:

    $(function() {
      $("ul.topnav li").hover(function() {
        var timeout = $(this).data("timeout");
        if(timeout) clearTimeout(timeout);
        $(this).find("ul.subnav").slideDown('fast');
      }, function() {
          $(this).data("timeout", setTimeout($.proxy(function() {
              $(this).find("ul.subnav").slideUp();
          }, this), 500));
      });
      $(document).click(function() {
          $('ul.subnav:visible').hide();
      });
    });​
    

    You can see a working demo here

    Instead of sharing a global timeout variable, this sets a timeout per top level <li>, each one has an independent timer, and when you hover back over that element, only its timer is cleared. Also .hover() uses mouseenter and mouseleave, rather than mouseover and mouseout, the difference is when you go into a child or between children, mouseenter doesn't fire again, and mouseleave doesn't fire on the parent <li> we care about.

    You can test this with the demo link above, I added sub-items to the first menu as well, to demonstrate they're independent. If you happen to have question about the $.proxy in there, it's just making this inside that timeout anonymous function refer to what I want it to (the current this)...the element that needs closing after the timeout.

0 comments:

Post a Comment