diff --git a/README.md b/README.md index b05ce8d..cb5292c 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,7 @@ menu-aim is a jQuery plugin for dropdown menus that can differentiate between a user trying hover over a dropdown item vs trying to navigate into a submenu's contents. -menu-aim assumes that you are using a menu with submenus that expand -to the menu's right. It will fire events when the user's mouse enters a new -dropdown item *and* when that item is being intentionally hovered over. +[Try a demo.](https://rawgithub.com/kamens/jQuery-menu-aim/master/example/example.html) ![Amazon screenshot](https://raw.github.com/kamens/jQuery-menu-aim/master/amazon.png) @@ -21,7 +19,7 @@ dropdown. $("#menu").menuAim({ activate: $.noop, // fired on row activation - deactivate: $.noop, // fired on row deactivation + deactivate: $.noop // fired on row deactivation }); ...to receive events when a menu's row has been purposefully (de)activated. @@ -45,6 +43,13 @@ the relevant row's HTML element as the execution context ('this'): // Function to call when mouse exits a menu row. exit: function() {}, + // Function to call when mouse exits the entire menu. If this returns + // true, the current row's deactivation event and callback function + // will be fired. Otherwise, if this isn't supplied or it returns + // false, the currently activated row will stay activated when the + // mouse leaves the menu entirely. + exitMenu: function() {}, + // Selector for identifying which elements in the menu are rows // that can trigger the above events. Defaults to "> li". rowSelector: "> li", @@ -52,12 +57,22 @@ the relevant row's HTML element as the execution context ('this'): // You may have some menu rows that aren't submenus and therefore // shouldn't ever need to "activate." If so, filter submenu rows w/ // this selector. Defaults to "*" (all elements). - submenuSelector: "*" + submenuSelector: "*", + + // Direction the submenu opens relative to the main menu. This + // controls which direction is "forgiving" as the user moves their + // cursor from the main menu into the submenu. Can be one of "right", + // "left", "above", or "below". Defaults to "right". + submenuDirection: "right" }); +menu-aim assumes that you are using a menu with submenus that expand +to the menu's right. It will fire events when the user's mouse enters a new +dropdown item *and* when that item is being intentionally hovered over. + ## Want an example to learn from? -Check out example/example.html -- it has a working dropdown for you to play with: +Check out example/example.html -- it has [a working dropdown for you to play with](https://rawgithub.com/kamens/jQuery-menu-aim/master/example/example.html): ![Example screenshot](https://raw.github.com/kamens/jQuery-menu-aim/master/example.png)
_Play with the above example full of fun monkey pictures by opening example/example.html after downloading the repo._ @@ -65,4 +80,6 @@ _Play with the above example full of fun monkey pictures by opening example/exam ## FAQ 1. What's the license? [MIT](http://en.wikipedia.org/wiki/MIT_License). -2. I'm not nearly bored enough. Got anything else? [Read about this plugin's creation](http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown). +2. Does it support horizontal menus or submenus that open to the left? Yup. Check out the submenuDirection option above. +3. I work at a big company that requires a version number on this third party code before I can use it. Do you have a version number? Sure, current version: 1.1 +4. I'm not nearly bored enough. Got anything else? [Read about this plugin's creation](http://bjk5.com/post/44698559168/breaking-down-amazons-mega-dropdown). diff --git a/example/example.html b/example/example.html index d2f934b..c81ede4 100644 --- a/example/example.html +++ b/example/example.html @@ -32,7 +32,7 @@ You can choose to do whatever you want w/ those events. --> - .popover { + .navbar .popover { width: 400px; -webkit-border-top-left-radius: 0px; -webkit-border-bottom-left-radius: 0px; @@ -41,23 +41,34 @@ overflow: hidden; } - .popover-content { + .navbar .popover-content { text-align: center; } - .popover-content img { + .navbar .popover-content img { height: 212px; max-width: 250px; } - .dropdown-menu > li > a:hover { + .navbar .dropdown-menu { + -webkit-border-top-right-radius: 0px; + -webkit-border-bottom-right-radius: 0px; + border-top-right-radius: 0px; + border-bottom-right-radius: 0px; + + -webkit-box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.2); + -moz-box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.2); + box-shadow: 5px 5px 10px rgba(0, 0, 0, 0.2); + } + + .navbar .dropdown-menu > li > a:hover { background-image: none; color: white; background-color: rgb(0, 129, 194); background-color: rgba(0, 129, 194, 0.5); } - .dropdown-menu > li > a.maintainHover { + .navbar .dropdown-menu > li > a.maintainHover { color: white; background-color: #0081C2; } @@ -83,7 +94,6 @@ @@ -117,56 +192,9 @@

jQuery-menu-aim example

- - - - - - - - - - - - - - - + + + diff --git a/jquery.menu-aim.js b/jquery.menu-aim.js index 4b3ffb5..0c32941 100644 --- a/jquery.menu-aim.js +++ b/jquery.menu-aim.js @@ -29,7 +29,7 @@ * * $("#menu").menuAim({ * activate: $.noop, // fired on row activation - * deactivate: $.noop, // fired on row deactivation + * deactivate: $.noop // fired on row deactivation * }); * * ...to receive events when a menu's row has been purposefully (de)activated. @@ -60,14 +60,27 @@ * // You may have some menu rows that aren't submenus and therefore * // shouldn't ever need to "activate." If so, filter submenu rows w/ * // this selector. Defaults to "*" (all elements). - * submenuSelector: "*" + * submenuSelector: "*", + * + * // Direction the submenu opens relative to the main menu. Can be + * // left, right, above, or below. Defaults to "right". + * submenuDirection: "right" * }); * * https://github.com/kamens/jQuery-menu-aim */ (function($) { + $.fn.menuAim = function(opts) { + // Initialize menu-aim for all elements in jQuery collection + this.each(function() { + init.call(this, opts); + }); + + return this; + }; + function init(opts) { var $menu = $(this), activeRow = null, mouseLocs = [], @@ -76,11 +89,13 @@ options = $.extend({ rowSelector: "> li", submenuSelector: "*", + submenuDirection: "right", tolerance: 75, // bigger = more forgivey when entering submenu enter: $.noop, exit: $.noop, activate: $.noop, - deactivate: $.noop + deactivate: $.noop, + exitMenu: $.noop }, opts); var MOUSE_LOCS_TRACKED = 3, // number of past mouse locations to track @@ -104,6 +119,16 @@ if (timeoutId) { clearTimeout(timeoutId); } + + // If exitMenu is supplied and returns true, deactivate the + // currently active row on menu exit. + if (options.exitMenu(this)) { + if (activeRow) { + options.deactivate(activeRow); + } + + activeRow = null; + } }; /** @@ -122,6 +147,13 @@ options.exit(this); }; + /* + * Immediately activate a row if the user clicks on it. + */ + var clickRow = function() { + activate(this); + }; + /** * Activate a menu row. */ @@ -171,13 +203,21 @@ } var offset = $menu.offset(), + upperLeft = { + x: offset.left, + y: offset.top - options.tolerance + }, upperRight = { x: offset.left + $menu.outerWidth(), - y: offset.top - options.tolerance + y: upperLeft.y + }, + lowerLeft = { + x: offset.left, + y: offset.top + $menu.outerHeight() + options.tolerance }, lowerRight = { x: offset.left + $menu.outerWidth(), - y: offset.top + $menu.outerHeight() + options.tolerance + y: lowerLeft.y }, loc = mouseLocs[mouseLocs.length - 1], prevLoc = mouseLocs[0]; @@ -227,13 +267,34 @@ return (b.y - a.y) / (b.x - a.x); }; - var upperSlope = slope(loc, upperRight), - lowerSlope = slope(loc, lowerRight), - prevUpperSlope = slope(prevLoc, upperRight), - prevLowerSlope = slope(prevLoc, lowerRight); + var decreasingCorner = upperRight, + increasingCorner = lowerRight; + + // Our expectations for decreasing or increasing slope values + // depends on which direction the submenu opens relative to the + // main menu. By default, if the menu opens on the right, we + // expect the slope between the cursor and the upper right + // corner to decrease over time, as explained above. If the + // submenu opens in a different direction, we change our slope + // expectations. + if (options.submenuDirection == "left") { + decreasingCorner = lowerLeft; + increasingCorner = upperLeft; + } else if (options.submenuDirection == "below") { + decreasingCorner = lowerRight; + increasingCorner = lowerLeft; + } else if (options.submenuDirection == "above") { + decreasingCorner = upperLeft; + increasingCorner = upperRight; + } - if (upperSlope < prevUpperSlope && - lowerSlope > prevLowerSlope) { + var decreasingSlope = slope(loc, decreasingCorner), + increasingSlope = slope(loc, increasingCorner), + prevDecreasingSlope = slope(prevLoc, decreasingCorner), + prevIncreasingSlope = slope(prevLoc, increasingCorner); + + if (decreasingSlope < prevDecreasingSlope && + increasingSlope > prevIncreasingSlope) { // Mouse is moving from previous location towards the // currently activated submenu. Delay before activating a // new menu row, because user may be moving into submenu. @@ -248,18 +309,15 @@ /** * Hook up initial menu events */ - var init = function() { - $menu - .mouseleave(mouseleaveMenu) - .find(options.rowSelector) - .mouseenter(mouseenterRow) - .mouseleave(mouseleaveRow); + $menu + .mouseleave(mouseleaveMenu) + .find(options.rowSelector) + .mouseenter(mouseenterRow) + .mouseleave(mouseleaveRow) + .click(clickRow); - $(document).mousemove(mousemoveDocument); - }; + $(document).mousemove(mousemoveDocument); - init(); - return this; }; })(jQuery);