jQuery - Dropdownify


/ Published in: jQuery
Save to your folder(s)

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


Copy this code and paste it in your HTML
  1. (function($) {
  2. /**
  3.   * toParent
  4.   *
  5.   * Returns the number of nodes (or steps) to the specified elements
  6.   *
  7.   * @param string dest The CSS selector of the target element
  8.   * @param string step The CSS selector of each 'step' (optional)
  9.   * @return integer The number of steps from the current element to dest, or null on error
  10.   */
  11. $.fn.toParent = function(dest, step) {
  12. var cur = $(this), i = 0, max = 200;
  13.  
  14. while (i < max) {
  15. if ($(cur).is(dest)) {
  16. return i;
  17. }
  18.  
  19. if ($(cur).length == 0) {
  20. return null;
  21. }
  22.  
  23. if (step) {
  24. while (true) {
  25. cur = $(cur).parent();
  26.  
  27. if ($(cur).is(step) || $(cur).is(dest) || !$(cur).length) {
  28. break;
  29. }
  30. }
  31.  
  32. } else {
  33. cur = $(cur).parent();
  34. }
  35.  
  36. i++;
  37. }
  38.  
  39. return null;
  40. }
  41.  
  42. /**
  43.   * dropdownify
  44.   *
  45.   * Creates a multi-level drop-down menu using the current element as the base
  46.   *
  47.   * @param object options (optional)
  48.   * @return object The jQuery object for chaining
  49.   */
  50. $.fn.dropdownify = function(options) {
  51. options = $.extend({
  52. 'delay': 100,
  53. 'items': 'li',
  54. 'menus': 'ul',
  55. 'onhide': null,
  56. 'onshow': null,
  57. 'position': {
  58. // level (number: 1 being top level, all others being the respective level deep): position (string: top, bottom, left, right)
  59. 1: 'bottom',
  60. 'default': 'right'
  61. },
  62. 'timeout': 750,
  63. 'zIndex': 200
  64. }, options || {});
  65.  
  66. // store the current selector, just in case
  67. options.selector = this.selector;
  68.  
  69. // add an identifier to the root element
  70. $(this).addClass('dropdownify-root');
  71. $(this).find(options.items).each(function(i, el) {
  72. if ($(el).find(options.menus).length) {
  73. $(el).addClass('submenu dropdownify-submenu');
  74. }
  75. });
  76.  
  77. // returns an object to be passed to $.css() for positioning
  78. var getPos = function(el) {
  79. // get the deptch of the current element
  80. var level = $(el).toParent('.dropdownify-root', options.menus);
  81.  
  82. // check the positioning options
  83. if (typeof options.position == 'object') {
  84. // if it's an object, check for the current depth
  85. if (level in options.position) {
  86. var or = options.position[level];
  87.  
  88. // or use the default
  89. } else {
  90. var or = options.position['default'];
  91. }
  92.  
  93. // just use it if it's not an object
  94. } else {
  95. var or = options.position;
  96. }
  97.  
  98. // if the orientation item is also an object
  99. if (typeof or == 'object') {
  100. // use the .v .h and .p values
  101. var offsetV = ('v' in or) ? or.v : 0;
  102. var offsetH = ('h' in or) ? or.h : 0;
  103. or = ('p' in or) ? or.p : 'bottom';
  104.  
  105. // use the defaults
  106. } else {
  107. var offsetV = 0;
  108. var offsetH = 0;
  109. }
  110.  
  111. // return the object
  112. return (function(or) {
  113. switch (or) {
  114. case 'bottom':
  115. return {
  116. 'top': ($(el).height() + offsetV) + 'px',
  117. 'left': (0 + offsetH) + 'px'
  118. };
  119. break;
  120.  
  121. case 'top':
  122. return {
  123. 'bottom': ($(el).height() + offsetV) + 'px',
  124. 'left': (0 + offsetH) + 'px'
  125. };
  126. break;
  127.  
  128. case 'left':
  129. return {
  130. 'top': (0 + offsetV) + 'px',
  131. 'right': ($(el).width() + offsetH) + 'px'
  132. };
  133. break;
  134.  
  135. case 'right':
  136. return {
  137. 'top': (0 + offsetV) + 'px',
  138. 'left': ($(el).width() + offsetH) + 'px'
  139. };
  140. break;
  141. }
  142. })(or);
  143. }
  144.  
  145. // find all 'items' in the current node
  146. $(this).find(options.items).each(function(i, el) {
  147. // hide all the submenus
  148. $(el).css({
  149. 'position': 'relative',
  150. 'overflow': 'visible'
  151. }).children(options.menus).css({
  152. 'display': 'none',
  153. 'position': 'absolute'
  154. });
  155.  
  156. // store the options locally
  157. $(el).data('dropdownify', options);
  158.  
  159. // hover in function
  160. $(el).mouseenter(function() {
  161. // hide any existing ones if we're top-level
  162. if ($(this).toParent('.dropdownify-root', options.menus) == 1) {
  163. $('.dropdownify-root').find(options.menus).css({
  164. 'display': 'none',
  165. 'visibility': 'hidden'
  166. });
  167. }
  168.  
  169. // clear the close timeout
  170. window.clearTimeout($(this).data('dropdownify').timeoutClose);
  171.  
  172. // store the timeout function for possible clearing
  173. $(this).data('dropdownify').timeoutOpen = window.setTimeout(function() {
  174. var object = this;
  175.  
  176. return function() {
  177. return function() {
  178. $(this).children($(this).data('dropdownify').menus).addClass('dropdownify-current').css({
  179. 'z-index': (options.zIndex + $(this).toParent('.dropdownify-root', options.menus)),
  180. 'display': 'block',
  181. 'visibility': 'visible',
  182. 'width': $(this).width()
  183. }).css(getPos(this));
  184.  
  185. if (options.onshow) {
  186. try {
  187. options.onshow.apply(this);
  188. } catch (err) {}
  189. }
  190. }.apply(object);
  191. }
  192. }.apply(this), options.delay);
  193. });
  194.  
  195. // hover out function
  196. $(el).mouseleave(function() {
  197. // clear the open timeout
  198. window.clearTimeout($(this).data('dropdownify').timeoutOpen);
  199.  
  200. // store the timeout function for possible clearing
  201. $(this).data('dropdownify').timeoutClose = window.setTimeout(function() {
  202. var object = this;
  203.  
  204. return function() {
  205. return function() {
  206. $(this).children($(this).data('dropdownify').menus).css({
  207. 'visibility': 'hidden',
  208. 'display': 'none'
  209. });
  210.  
  211. if (options.onhide) {
  212. try {
  213. options.onhide.apply(this);
  214. } catch (err) {}
  215. }
  216. }.apply(object);
  217. }
  218. }.apply(this), $(this).data('dropdownify').timeout);
  219. });
  220. });
  221.  
  222. // return the object for chaining
  223. return this;
  224. }
  225. })(jQuery);

URL: http://www.dom111.co.uk/blog/coding/dropdownify-minimal-effort-dropdown/218

Report this snippet


Comments

RSS Icon Subscribe to comments

You need to login to post a comment.