Posted By

dom111 on 09/15/10


Tagged

javascript dropdown drop-down mootools


Versions (?)

mootools dropdownify


 / Published in: JavaScript
 

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

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 <= IE6), but for my needs, it sufficed, so I thought I'd share, in case anyone else needs a simple script to manage drop-downs.

  1. Element.implement({
  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. toParent: function(dest, step) {
  12. var cur = $(this), i = 0, max = 200;
  13.  
  14. while (i < max) {
  15. if (!cur) {
  16. return null;
  17. }
  18.  
  19. if ($(cur).match(dest)) {
  20. return i;
  21. }
  22.  
  23. if (step) {
  24. while (true) {
  25. cur = $(cur).getParent();
  26.  
  27. if (!cur || $(cur).match(step) || $(cur).match(dest)) {
  28. break;
  29. }
  30. }
  31.  
  32. } else {
  33. cur = $(cur).getParent();
  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 object for chaining
  49.   */
  50. 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. // add an identifier to the root element
  67. $(this).addClass('dropdownify-root');
  68. $(this).getElements(options.items).each(function(el, i) {
  69. if ($(el).getElements(options.menus).length) {
  70. $(el).addClass('submenu dropdownify-submenu');
  71. }
  72. });
  73.  
  74. // returns an object to be passed to $.css() for positioning
  75. var getPos = function(el) {
  76. // get the deptch of the current element
  77. var level = $(el).toParent('.dropdownify-root', options.menus);
  78.  
  79. // check the positioning options
  80. if (typeof options.position == 'object') {
  81. // if it's an object, check for the current depth
  82. if (level in options.position) {
  83. var or = options.position[level];
  84.  
  85. // or use the default
  86. } else {
  87. var or = options.position['default'];
  88. }
  89.  
  90. // just use it if it's not an object
  91. } else {
  92. var or = options.position;
  93. }
  94.  
  95. // if the orientation item is also an object
  96. if (typeof or == 'object') {
  97. // use the .v .h and .p values
  98. var offsetV = ('v' in or) ? or.v : 0;
  99. var offsetH = ('h' in or) ? or.h : 0;
  100. or = ('p' in or) ? or.p : 'bottom';
  101.  
  102. // use the defaults
  103. } else {
  104. var offsetV = 0;
  105. var offsetH = 0;
  106. }
  107.  
  108. // return the object
  109. return (function(or) {
  110. switch (or) {
  111. case 'bottom':
  112. return {
  113. 'top': ($(el).getStyle('height').toInt() + offsetV) + 'px',
  114. 'left': (0 + offsetH) + 'px'
  115. };
  116. break;
  117.  
  118. case 'top':
  119. return {
  120. 'bottom': ($(el).getStyle('height').toInt() + offsetV) + 'px',
  121. 'left': (0 + offsetH) + 'px'
  122. };
  123. break;
  124.  
  125. case 'left':
  126. return {
  127. 'top': (0 + offsetV) + 'px',
  128. 'right': ($(el).getStyle('width').toInt() + offsetH) + 'px'
  129. };
  130. break;
  131.  
  132. case 'right':
  133. return {
  134. 'top': (0 + offsetV) + 'px',
  135. 'left': ($(el).getStyle('width').toInt() + offsetH) + 'px'
  136. };
  137. break;
  138. }
  139. })(or);
  140. }
  141.  
  142. // find all 'items' in the current node
  143. $(this).getElements(options.items).each(function(el, i) {
  144. // hide all the submenus
  145. $(el).setStyles({
  146. 'position': 'relative',
  147. 'overflow': 'visible'
  148. }).getChildren(options.menus).setStyles({
  149. 'display': 'none',
  150. 'position': 'absolute'
  151. });
  152.  
  153. // store the options locally
  154. $(el).store('dropdownify', options);
  155.  
  156. // hover in function
  157. $(el).addEvent('mouseenter', function() {
  158. // hide any existing ones if we're top-level
  159. if ($(this).toParent('.dropdownify-root', options.menus) == 1) {
  160. $$('.dropdownify-root')[0].getElements(options.menus).setStyles({
  161. 'display': 'none',
  162. 'visibility': 'hidden'
  163. });
  164. }
  165.  
  166. // clear the close timeout
  167. window.clearTimeout($(this).retrieve('dropdownify').timeoutClose);
  168.  
  169. // store the timeout function for possible clearing
  170. $(this).retrieve('dropdownify').timeoutOpen = window.setTimeout(function() {
  171. var object = this;
  172.  
  173. return function() {
  174. return function() {
  175. $(this).getChildren($(this).retrieve('dropdownify').menus).addClass('dropdownify-current').setStyles({
  176. 'z-index': (options.zIndex + $(this).toParent('.dropdownify-root', options.menus)),
  177. 'display': 'block',
  178. 'visibility': 'visible',
  179. 'width': $(this).getStyle('width')
  180. }).setStyles(getPos(this));
  181.  
  182. if (options.onshow) {
  183. try {
  184. options.onshow.apply(this);
  185. } catch (err) {}
  186. }
  187. }.apply(object);
  188. }
  189. }.apply(this), options.delay);
  190. });
  191.  
  192. // hover out function
  193. $(el).addEvent('mouseleave', function() {
  194. // clear the open timeout
  195. window.clearTimeout($(this).retrieve('dropdownify').timeoutOpen);
  196.  
  197. // store the timeout function for possible clearing
  198. $(this).retrieve('dropdownify').timeoutClose = window.setTimeout(function() {
  199. var object = this;
  200.  
  201. return function() {
  202. return function() {
  203. $(this).getChildren($(this).retrieve('dropdownify').menus).setStyles({
  204. 'visibility': 'hidden',
  205. 'display': 'none'
  206. });
  207.  
  208. if (options.onhide) {
  209. try {
  210. options.onhide.apply(this);
  211. } catch (err) {}
  212. }
  213. }.apply(object);
  214. }
  215. }.apply(this), $(this).retrieve('dropdownify').timeout);
  216. });
  217. });
  218.  
  219. // return the object for chaining
  220. return this;
  221. }
  222. });

Report this snippet  

You need to login to post a comment.