1// PURE 2/* 3 4Dropdown.js 5 6Creates touch-friendly drop-down menus 7 8Created by Stephen Morley - http://code.stephenmorley.org/ - and released under 9the terms of the CC0 1.0 Universal legal code: 10 11http://creativecommons.org/publicdomain/zero/1.0/legalcode 12 13@version 2.0.0 SPK Changes to fix touch / scroll on mobile 14 15*/ 16// create the Dropdown object 17var Dropdown = (function(){ 18 19 // the delay, in milliseconds 20 var DELAY = 250; 21 22 // the list of menus 23 var menus = []; 24 25 // Initialises the drop-down menus. 26 function initialise(){ 27 28 // listen for touch events on the document if appropriate 29 if ('createTouch' in document){ 30 document.body.addEventListener('touchstart', handleTouchStart, false); 31 } 32 33 // loop over the menus, converting them 34 var menus = document.querySelectorAll('ul.dropdown'); 35 for (var i = 0; i < menus.length; i ++) applyTo(menus[i]); 36 } 37 38 /* Handles a touch start event. The parameter is: 39 * 40 * e - the event 41 */ 42 function handleTouchStart(e){ 43 44 // determine whether any menu is open 45 var isOpen = false; 46 for (var i = 0; i < menus.length; i ++){ 47 if (menus[i].isOpen) isOpen = true; 48 } 49 50 // return immediately if all menus are closed 51 if (!isOpen) return; 52 53 // move up the document tree until we reach the root node 54 var node = e.target; 55 while (node != null){ 56 57 // return immediately if we are inside a drop-down menu 58 if (/\bdropdown\b/.test(node.className)) return; 59 60 // move onto the parent node 61 node = node.parentNode; 62 63 } 64 65 // close all menus 66 close(); 67 68 } 69 70 /* Closes all menus except the specified menu. The parameter is: 71 * 72 * menu - a menu not to close; this parameter is optional 73 */ 74 function close(menu){ 75 76 // loop over the menus, closing them 77 for (var i = 0; i < menus.length; i ++){ 78 if (menus[i] != menu) menus[i].close(); 79 } 80 81 } 82 83 /* Creates a drop-down menu. The parameter is: 84 * 85 * node - either the DOM node of the menu or the ID of the node 86 */ 87 function applyTo(node){ 88 // fetch the DOM node if a string was supplied 89 if (typeof node == 'string') node = document.getElementById(node); 90 // create and store the new menu 91 menus.push(new Menu(node)); 92 93 } 94 95 /* Creates a drop-down menu. The parameter is: 96 * 97 * node - the DOM node of the menu 98 */ 99 function Menu(node){ 100 101 // store the node 102 this.node = node; 103 104 // update the class name 105 node.className += ' dropdownJavaScript'; 106 107 // listen for mouse events 108 if ('addEventListener' in node){ 109 node.addEventListener('mouseover', this.bind(this.handleMouseOver), false); 110 node.addEventListener('mouseout', this.bind(this.handleMouseOut), false); 111 node.addEventListener('click', this.bind(this.handleClick), false); 112 }else{ 113 node.attachEvent('onmouseover', this.bind(this.handleMouseOver)); 114 node.attachEvent('onmouseout', this.bind(this.handleMouseOut)); 115 node.attachEvent('onclick', this.bind(this.handleClick)); 116 } 117 118 // listen for touch events if appropriate
119 if ('createTouch' in document){ 120 // node.addEventListener('touchstart', this.bind(this.handleClick), false); 121 node.addEventListener('touchend', this.bind(this.handleTouchEnd), false); 122 } 123 124 } 125 126 // whether the menu is open 127 Menu.prototype.isOpen = false; 128 129 // the timeout 130 Menu.prototype.timeout = null; 131 132 /* Binds the specified function to the current object. The parameter is: 133 * 134 * f - the function 135 */ 136 Menu.prototype.bind = function(f){ 137 138 // return the bound function 139 var thisObject = this; 140 return function(){ f.apply(thisObject, arguments); } 141 142 } 143 144 /* Handles a mouse over event. The parameters are: 145 * 146 * e - the event 147 * immediate - true to open the menu without a delay 148 */ 149 Menu.prototype.handleMouseOver = function(e, immediate){ 150 // clear the timeout 151 this.clearTimeout(); 152 // find the parent list item 153 var item = ('target' in e ? e.target : e.srcElement); 154 while (item.nodeName != 'LI' && item != this.node) item = item.parentNode; 155 156 // if the target is within a list item, set the timeout 157 if (item.nodeName == 'LI'){ 158 this.toOpen = item; 159 this.timeout = 160 window.setTimeout(this.bind(this.open), (immediate ? 0 : DELAY)); 161 } 162 163 } 164 165 // Handles a mouse out event. 166 Menu.prototype.handleMouseOut = function(){ 167 168 // clear the timeout 169 this.clearTimeout(); 170 171 // set the timeout 172 this.timeout = window.setTimeout(this.bind(this.close), DELAY); 173 174 } 175 176 Menu.prototype.handleTouchEnd = function(e){ 177 } 178 179 /* Handles a click event. The parameter is: 180 * 181 * e - the event 182 */ 183 Menu.prototype.handleClick = function(e){ 184 185 // close any other menus 186 close(this); 187 188 // find the parent list item 189 var item = ('target' in e ? e.target : e.srcElement); 190 while (item.nodeName != 'LI' && item != this.node) item = item.parentNode; 191 192 // check that the target is within a list item 193 if (item.nodeName == 'LI'){ 194 195 // check whether the item has a closed submenu 196 var submenu = this.getChildrenByTagName(item, 'UL'); 197 if (submenu.length > 0 && !/\bdropdownOpen\b/.test(item.className)){ 198 199 // This is where touch / click to open menu comes 200 // Need to put some sort of check on scrolling 201 // 202 // 203 // open the submenu 204 this.handleMouseOver(e, true); 205 206 // prevent the default action 207 if ('preventDefault' in e){ 208 e.preventDefault(); 209 }else{ 210 e.returnValue = false; 211 } 212 213 } 214 215 } 216 217 } 218 219 // Clears the timeout. 220 Menu.prototype.clearTimeout = function(){ 221 222 // clear the timeout 223 if (this.timeout){ 224 window.clearTimeout(this.timeout); 225 this.timeout = null; 226 } 227 } 228 229 // Opens the last item hovered over. 230 Menu.prototype.open = function(){ 231 // store that the menu is open 232 this.isOpen = true; 233 234 // loop over the list items with the same parent 235 var items = this.getChildrenByTagName(this.toOpen.parentNode, 'LI'); 236 for (var i = 0; i < items.length; i ++){ 237 238 // check whether there is a submenu 239 var submenu = this.getChildrenByTagName(items[i], 'UL'); 240 if (submenu.length > 0){ 241 242 // check whether the submenu should be opened or closed 243 if (items[i] != this.toOpen){ 244 245 // close the submenu 246 items[i].className = 247 items[i].className.replace(/\bdropdownOpen\b/g, ''); 248 this.close(items[i]); 249 250 }else if (!/\bdropdownOpen\b/.test(items[i].className)){ 251 252 // open the submenu 253 items[i].className += ' dropdownOpen'; 254 255 // determine the location of the edges of the submenu 256 var left = 0; 257 var node = submenu[0]; 258 while (node){ 259 left += node.offsetLeft; 260 node = node.offsetParent; 261 } 262 right = left + submenu[0].offsetWidth; 263 264 // move the submenu to the right of the item if appropriate 265 if (left < 0) items[i].className += ' dropdownLeftToRight'; 266 267 // move the submenu to the left of the item if appropriate 268 if (right > document.body.clientWidth){ 269 items[i].className += ' dropdownRightToLeft'; 270 } 271 272 } 273 274 } 275 276 } 277 278 } 279 280 /* Closes the menus within the specified node. The parameter is: 281 * 282 * node - the node; if omitted, all menus are closed 283 */ 284 Menu.prototype.close = function(node){ 285 286 // if no node was specified, close all menus 287 if (!node){ 288 this.isOpen = false;
289 node = this.node; 290 } 291 292 // loop over the items, closing their submenus 293 var items = node.getElementsByTagName('li'); 294 for (var i = 0; i < items.length; i ++){ 295 items[i].className = items[i].className.replace(/\bdropdownOpen\b/g, ''); 296 } 297 298 } 299 300 /* Returns an array containing the children of the specified node with the 301 * specified tag name. The parameters are: 302 * 303 * node - the node 304 * tagName - the tag name 305 */ 306 Menu.prototype.getChildrenByTagName = function(node, tagName){ 307 308 // initialise the list of children 309 var result = []; 310 311 // loop over the children, adding those with the right tag name to the list 312 for (var i = 0; i < node.childNodes.length; i ++){ 313 if (node.childNodes[i].nodeName == tagName){ 314 result.push(node.childNodes[i]); 315 } 316 } 317 318 // return the children 319 return result; 320 321 } 322 323 // return the public API 324 return { 325 initialise : initialise, 326 applyTo : applyTo 327 }; 328 329})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.