Clicking anywhere except inside the menu closes it
I use two elements per menu: a button and a div. Clicking the button makes the div appear. The button’s name attribute marks the id of the div to open:
jQuery(function($) {
$('button.menu').one('click', openMenu);
function openMenu(e) {
var button = $(this).addClass('active');
var menu = $('#' + button.attr('name'));
var offset = button.offset();
var h = (button.outerHeight) ? button.outerHeight() : button.height();
menu.addClass('active').css({
'top': offset.top + h, 'left': offset.left
}).click(function(e) { e.stopPropagation(); }).show(200, function() {
$(document).one('click', {button: button, menu: menu}, closeMenu);
});
}
function closeMenu(e) {
e.data.menu.removeClass('active').hide(100, function() {
e.data.button.removeClass('active');
});
e.data.button.one('click', openMenu);
}
});
Here we go:
jQuery(function($) {
jQuery is an alias for $(document).ready. The callback is passed the jQuery object as an argument, so you can name it whatever you want. This is good practice for getting along with other JavaScript libraries. Name it $ here and it’ll work in the callback even if another library uses $.
$('button.menu').one('click', openMenu);
Attach the click event handler openMenu to all buttons with class “menu”. one is used to only respond to this event for one click. Why? Because the next click should close the menu, not open it. Why not use toggle? Because the menu should close when clicking on the document too, not just on the button, and this could put the toggle status out of sync.
function openMenu(e) {
var button = $(this).addClass('active');
Define the openMenu callback, get the clicked button, and add a class for styling purposes.
var menu = $('#' + button.attr('name'));
Retrieve the div with the same id as the button’s name.
var offset = button.offset();
var h = (button.outerHeight) ? button.outerHeight() : button.height();
Get the button’s position and size, because the menu will appear under it. outerHeight is used if available, provided by the Dimensions plugin, to account for borders and such.
Set a click handler on the menu that stops the click event from bubbling up to the document. Why? Because after the menu appears using the show method, we attach a click event to the whole document that closes the menu. If the event were allowed to propagate, clicking within the menu would close it, something you don’t necessarily always want. Notice that the call to one passes an object along with the event handler. This will be available as e.data in closeMenu.
}
function closeMenu(e) {
e.data.menu.removeClass('active').hide(100, function() {
e.data.button.removeClass('active');
});
Now define the closeMenu callback. It removes the “active” class from the menu, hides it, and also removes the “active” class from the button.
e.data.button.one('click', openMenu);
}
});
Finally, attach the openMenu callback to the button’s click event handler again. Remember, one is only good for one occurrence of the event.