/ Published in: jQuery
So recently I was asked to change a navigation style of an existing site to drop-down menus.
Simple, I thought, just use one of the many existing drop-down plugins. I tried many, but most seemed to use hardcoded styles and I had a few problems (some of which I encountered again, writing this).
So I’ve made this, I think it’s fairly robust, but I’m sure there’ll be problems with embedded objects (flash) and select boxes (in
Simple, I thought, just use one of the many existing drop-down plugins. I tried many, but most seemed to use hardcoded styles and I had a few problems (some of which I encountered again, writing this).
So I’ve made this, I think it’s fairly robust, but I’m sure there’ll be problems with embedded objects (flash) and select boxes (in
Expand |
Embed | Plain Text
Copy this code and paste it in your HTML
(function($) { /** * toParent * * Returns the number of nodes (or steps) to the specified elements * * @param string dest The CSS selector of the target element * @param string step The CSS selector of each 'step' (optional) * @return integer The number of steps from the current element to dest, or null on error */ $.fn.toParent = function(dest, step) { var cur = $(this), i = 0, max = 200; while (i < max) { if ($(cur).is(dest)) { return i; } if ($(cur).length == 0) { return null; } if (step) { while (true) { cur = $(cur).parent(); if ($(cur).is(step) || $(cur).is(dest) || !$(cur).length) { break; } } } else { cur = $(cur).parent(); } i++; } return null; } /** * dropdownify * * Creates a multi-level drop-down menu using the current element as the base * * @param object options (optional) * @return object The jQuery object for chaining */ $.fn.dropdownify = function(options) { options = $.extend({ 'delay': 100, 'items': 'li', 'menus': 'ul', 'onhide': null, 'onshow': null, 'position': { // level (number: 1 being top level, all others being the respective level deep): position (string: top, bottom, left, right) 1: 'bottom', 'default': 'right' }, 'timeout': 750, 'zIndex': 200 }, options || {}); // store the current selector, just in case options.selector = this.selector; // add an identifier to the root element $(this).addClass('dropdownify-root'); $(this).find(options.items).each(function(i, el) { if ($(el).find(options.menus).length) { $(el).addClass('submenu dropdownify-submenu'); } }); // returns an object to be passed to $.css() for positioning var getPos = function(el) { // get the deptch of the current element var level = $(el).toParent('.dropdownify-root', options.menus); // check the positioning options if (typeof options.position == 'object') { // if it's an object, check for the current depth if (level in options.position) { var or = options.position[level]; // or use the default } else { var or = options.position['default']; } // just use it if it's not an object } else { var or = options.position; } // if the orientation item is also an object if (typeof or == 'object') { // use the .v .h and .p values var offsetV = ('v' in or) ? or.v : 0; var offsetH = ('h' in or) ? or.h : 0; or = ('p' in or) ? or.p : 'bottom'; // use the defaults } else { var offsetV = 0; var offsetH = 0; } // return the object return (function(or) { switch (or) { case 'bottom': return { 'top': ($(el).height() + offsetV) + 'px', 'left': (0 + offsetH) + 'px' }; break; case 'top': return { 'bottom': ($(el).height() + offsetV) + 'px', 'left': (0 + offsetH) + 'px' }; break; case 'left': return { 'top': (0 + offsetV) + 'px', 'right': ($(el).width() + offsetH) + 'px' }; break; case 'right': return { 'top': (0 + offsetV) + 'px', 'left': ($(el).width() + offsetH) + 'px' }; break; } })(or); } // find all 'items' in the current node $(this).find(options.items).each(function(i, el) { // hide all the submenus $(el).css({ 'position': 'relative', 'overflow': 'visible' }).children(options.menus).css({ 'display': 'none', 'position': 'absolute' }); // store the options locally $(el).data('dropdownify', options); // hover in function $(el).mouseenter(function() { // hide any existing ones if we're top-level if ($(this).toParent('.dropdownify-root', options.menus) == 1) { $('.dropdownify-root').find(options.menus).css({ 'display': 'none', 'visibility': 'hidden' }); } // clear the close timeout window.clearTimeout($(this).data('dropdownify').timeoutClose); // store the timeout function for possible clearing $(this).data('dropdownify').timeoutOpen = window.setTimeout(function() { var object = this; return function() { return function() { $(this).children($(this).data('dropdownify').menus).addClass('dropdownify-current').css({ 'z-index': (options.zIndex + $(this).toParent('.dropdownify-root', options.menus)), 'display': 'block', 'visibility': 'visible', 'width': $(this).width() }).css(getPos(this)); if (options.onshow) { try { options.onshow.apply(this); } catch (err) {} } }.apply(object); } }.apply(this), options.delay); }); // hover out function $(el).mouseleave(function() { // clear the open timeout window.clearTimeout($(this).data('dropdownify').timeoutOpen); // store the timeout function for possible clearing $(this).data('dropdownify').timeoutClose = window.setTimeout(function() { var object = this; return function() { return function() { $(this).children($(this).data('dropdownify').menus).css({ 'visibility': 'hidden', 'display': 'none' }); if (options.onhide) { try { options.onhide.apply(this); } catch (err) {} } }.apply(object); } }.apply(this), $(this).data('dropdownify').timeout); }); }); // return the object for chaining return this; } })(jQuery);
URL: http://www.dom111.co.uk/blog/coding/dropdownify-minimal-effort-dropdown/218