Skip to content

Instantly share code, notes, and snippets.

@unruthless
Created August 2, 2010 15:35
Show Gist options
  • Save unruthless/504814 to your computer and use it in GitHub Desktop.
Save unruthless/504814 to your computer and use it in GitHub Desktop.
jQuery snippet to handle global navigation behavior
/*
* Global Navigation Behaviors
*
* @author Ruthie BenDor
* @updated 2-Aug-2010
*
* See in use on http://www.glad.org/styles/test
* How can I make this better?
*
*/
(function() {
var activeDropdown,
activeMenu,
activeMenuClass = 'droppeddown',
activeParent,
// Deactivates the current menu
deactivateMenu = function() {
if (activeDropdown) {
activeDropdown.removeClass(activeMenuClass);
activeMenu.hide();
}
},
activeExpander,
activeSubmenu,
activeSubmenuClass = 'expanded',
// Deactivates the current submenu
deactivateSubmenu = function() {
if (activeMenu) {
activeMenu.find('.expander').removeClass(activeSubmenuClass);
activeMenu.find('.submenu').hide();
}
},
// Deactivates menu and enclosed submenus when outside element is moused over
deactivateAll = function(e) {
if (typeof e !== 'object' || !activeParent) {
return;
}
var parentElement = activeParent[0];
if ( !$.contains(parentElement,e.target) || !parentElement == e.target ) {
deactivateSubmenu();
deactivateMenu();
// We don't need this function anymore, so unbind it.
$(document.body).unbind('mouseover', deactivateAll);
}
};
// Iterate through each dropdown
$('.dropdown').each(function() {
var dropdown = $(this),
menu = dropdown.next('.menu'),
parent = dropdown.parent(),
// Activates this dropdown's menu
activate = function() {
deactivateMenu();
activeDropdown = dropdown.addClass(activeMenuClass);
activeMenu = menu.show();
activeParent = parent;
// Deactivates menu when outside element is moused over
$(document.body).bind('mouseover', deactivateAll);
};
// Activate menu when mouseovered
dropdown.bind('mouseover',function(e) {
if (e) {
e.stopPropagation();
}
activate();
});
// Activate menu when focused
dropdown.bind('focus',function() {
activate();
});
});
// Iterate through each expander
$('.expander').each(function() {
var expander = $(this),
submenu = expander.next('.submenu'),
// Toggles this expander's submenu
toggle = function() {
activeExpander = expander.toggleClass(activeSubmenuClass);
activeSubmenu = submenu.slideToggle();
};
// Toggle submenu when clicked
expander.bind('click',function(e) {
if (e) {
e.stopPropagation();
e.preventDefault();
}
toggle();
});
});
})();
@kimili
Copy link

kimili commented Aug 2, 2010

Ruth -

Looks good overall. A few suggestions, though:

  1. I see you're combining var definitions with one call to var. To really optimize it, you can extend that single var declaration to the two function definitions, too. This can help save a few bytes off the file size. So instead of:
var activeDropdown,
    activeMenu,
    activeParent,
    activeExpander,
    activeSubmenu;

// Deactivates the current submenu
var deactivateSubmenu = function() {
    if (activeMenu) {
        activeMenu.find('.expander').removeClass('expanded');
        activeMenu.find('.submenu').hide();
    }
}

// Deactivates the current menu
var deactivateMenu = function() {
    if (activeDropdown) {
        activeDropdown.removeClass('droppeddown');
        activeMenu.hide();
    }
};

you can try this:

var activeDropdown,
    activeMenu,
    activeParent,
    activeExpander,
    activeSubmenu,

    // Deactivates the current submenu
    deactivateSubmenu = function() {
        if (activeMenu) {
            activeMenu.find('.expander').removeClass('expanded');
            activeMenu.find('.submenu').hide();
        }
    },

    // Deactivates the current menu
    deactivateMenu = function() {
        if (activeDropdown) {
            activeDropdown.removeClass('droppeddown');
            activeMenu.hide();
        }
    };
  1. It's good that you're using one function to bind a single event which triggers the menu behavior, but using a mouseover event on the document.body is going to fire that function every time the cursor enters the bounds of any element anywhere on the page - in other words, constantly. Not very efficient. In most cases, it'd be best to bind the event to the parent element of the menus and that's it.

  2. You probably don't need e.preventDefault() on mouseover events, as there is no default action for that event.

  3. Any string that you've used more than once ('droppeddown', 'expander', 'expanded') should probably be set to a variable that's global to the closure, so the string is only set once. This helps with keeping file size down when minimizing. If you want to be really consistent, set all your strings to closure-global variables, and just refer to the variable names throughout the code.

Cheers,
M

@unruthless
Copy link
Author

Thanks for the suggestions, Michael! I've implemented (1) and (3), started implementing (4), and am studying how best to implement (2). That function's a tricky one -- how to close the menu when something is moused over, where that something is neither the active menu nor the active menu's dropdown anchor.

@kimili
Copy link

kimili commented Aug 2, 2010

Not so tricky. In that case, just bind it when you need it, and unbind it when you don't. Something like this:

(function() {

    var activeDropdown,
        activeMenu,
        activeMenuClass = 'droppeddown',
        activeParent,

        // Deactivates the current menu
        deactivateMenu = function() {
            if (activeDropdown) {
                activeDropdown.removeClass(activeMenuClass);
                activeMenu.hide();
            }
        },

        activeExpander,
        activeSubmenu,
        activeSubmenuClass = 'expanded',

        // Deactivates the current submenu
        deactivateSubmenu = function() {
            if (activeMenu) {
                activeMenu.find('.expander').removeClass(activeSubmenuClass);
                activeMenu.find('.submenu').hide();
            }
        },

        // Deactivates menu when outside element is moused over
        deactivateAll = function(e) {
            if (typeof e !== 'object' || !activeParent) {
                return;
            }
            var parentElement = activeParent[0];
            if ( !$.contains(parentElement,e.target) || !parentElement == e.target ) {
                deactivateSubmenu();
                deactivateMenu();
                // we don't need this function anymore. Unbind it.
                $(document.body).unbind('mouseover', deactivateAll);
            }
        };

    // Iterate through each dropdown
    $('.dropdown').each(function() {

        var dropdown  = $(this),
            menu      = dropdown.next('.menu'),
            parent    = dropdown.parent();

        // Activates this dropdown's menu
        var activate = function() {
            deactivateMenu();
            activeDropdown = dropdown.addClass(activeMenuClass);
            activeMenu = menu.show();
            activeParent = parent;
            // Deactivates menu when outside element is moused over
            $(document.body).bind('mouseover', deactivateAll);
        };

        // Activates menu when mouseovered
        dropdown.bind('mouseover',function(e) {
            if (e) {
                e.stopPropagation();
            }
            activate();
        });

        // Activates menu when focused
        dropdown.bind('focus',function() {
            activate();
        });

    });

    // Iterate through each expander
    $('.expander').each(function() {

        var expander  = $(this),
            submenu   = expander.next('.submenu');

        // Toggles this expander's submenu
        var toggle = function() {
          activeExpander = expander.toggleClass(activeSubmenuClass);
          activeSubmenu = submenu.slideToggle();
        }

        // Toggles submenu when clicked
        expander.bind('click',function(e) {
          if (e) {
              e.stopPropagation();
              e.preventDefault();
          }
          toggle();
        });
    });

})();

@unruthless
Copy link
Author

Michael, that's brilliant. Beers on me at the next Build Guild. Thank you so much.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment