1/* 2 * SmartMenus jQuery v1.0.0 3 * http://www.smartmenus.org/ 4 * 5 * Copyright Vasil Dinkov, Vadikom Web Ltd. 6 * http://vadikom.com/ 7 * 8 * Released under the MIT license: 9 * http://www.opensource.org/licenses/MIT 10 */ 11 12(function(factory) { 13 if (typeof define === 'function' && define.amd) { 14 // AMD 15 define(['jquery'], factory); 16 } else if (typeof module === 'object' && typeof module.exports === 'object') { 17 // CommonJS 18 module.exports = factory(require('jquery')); 19 } else { 20 // Global jQuery 21 factory(jQuery); 22 } 23} (function($) { 24 25 var menuTrees = [], 26 IE = !!window.createPopup, // detect it for the iframe shim 27 mouse = false, // optimize for touch by default - we will detect for mouse input 28 touchEvents = 'ontouchstart' in window, // we use this just to choose between toucn and pointer events, not for touch screen detection 29 mouseDetectionEnabled = false, 30 requestAnimationFrame = window.requestAnimationFrame || function(callback) { return setTimeout(callback, 1000 / 60); }, 31 cancelAnimationFrame = window.cancelAnimationFrame || function(id) { clearTimeout(id); }; 32 33 // Handle detection for mouse input (i.e. desktop browsers, tablets with a mouse, etc.) 34 function initMouseDetection(disable) { 35 var eNS = '.smartmenus_mouse'; 36 if (!mouseDetectionEnabled && !disable) { 37 // if we get two consecutive mousemoves within 2 pixels from each other and within 300ms, we assume a real mouse/cursor is present 38 // in practice, this seems like impossible to trick unintentianally with a real mouse and a pretty safe detection on touch devices (even with older browsers that do not support touch events) 39 var firstTime = true, 40 lastMove = null; 41 $(document).bind(getEventsNS([ 42 ['mousemove', function(e) { 43 var thisMove = { x: e.pageX, y: e.pageY, timeStamp: new Date().getTime() }; 44 if (lastMove) { 45 var deltaX = Math.abs(lastMove.x - thisMove.x), 46 deltaY = Math.abs(lastMove.y - thisMove.y); 47 if ((deltaX > 0 || deltaY > 0) && deltaX <= 2 && deltaY <= 2 && thisMove.timeStamp - lastMove.timeStamp <= 300) { 48 mouse = true; 49 // if this is the first check after page load, check if we are not over some item by chance and call the mouseenter handler if yes 50 if (firstTime) { 51 var $a = $(e.target).closest('a'); 52 if ($a.is('a')) { 53 $.each(menuTrees, function() { 54 if ($.contains(this.$root[0], $a[0])) { 55 this.itemEnter({ currentTarget: $a[0] }); 56 return false; 57 } 58 }); 59 } 60 firstTime = false; 61 } 62 } 63 } 64 lastMove = thisMove; 65 }], 66 [touchEvents ? 'touchstart' : 'pointerover pointermove pointerout MSPointerOver MSPointerMove MSPointerOut', function(e) { 67 if (isTouchEvent(e.originalEvent)) { 68 mouse = false; 69 } 70 }] 71 ], eNS)); 72 mouseDetectionEnabled = true; 73 } else if (mouseDetectionEnabled && disable) { 74 $(document).unbind(eNS); 75 mouseDetectionEnabled = false; 76 } 77 } 78 79 function isTouchEvent(e) { 80 return !/^(4|mouse)$/.test(e.pointerType); 81 } 82 83 // returns a jQuery bind() ready object 84 function getEventsNS(defArr, eNS) { 85 if (!eNS) { 86 eNS = ''; 87 } 88 var obj = {}; 89 $.each(defArr, function(index, value) { 90 obj[value[0].split(' ').join(eNS + ' ') + eNS] = value[1]; 91 }); 92 return obj; 93 } 94 95 $.SmartMenus = function(elm, options) { 96 this.$root = $(elm); 97 this.opts = options; 98 this.rootId = ''; // internal 99 this.accessIdPrefix = ''; 100 this.$subArrow = null; 101 this.activatedItems = []; // stores last activated A's for each level 102 this.visibleSubMenus = []; // stores visible sub menus UL's (might be in no particular order) 103 this.showTimeout = 0; 104 this.hideTimeout = 0; 105 this.scrollTimeout = 0; 106 this.clickActivated = false; 107 this.focusActivated = false; 108 this.zIndexInc = 0; 109 this.idInc = 0; 110 this.$firstLink = null; // we'll use these for some tests 111 this.$firstSub = null; // at runtime so we'll cache them 112 this.disabled = false; 113 this.$disableOverlay = null; 114 this.$touchScrollingSub = null; 115 this.cssTransforms3d = 'perspective' in elm.style || 'webkitPerspective' in elm.style; 116 this.wasCollapsible = false;
117 this.init(); 118 }; 119 120 $.extend($.SmartMenus, { 121 hideAll: function() { 122 $.each(menuTrees, function() { 123 this.menuHideAll(); 124 }); 125 }, 126 destroy: function() { 127 while (menuTrees.length) { 128 menuTrees[0].destroy(); 129 } 130 initMouseDetection(true); 131 }, 132 prototype: { 133 init: function(refresh) { 134 var self = this; 135 136 if (!refresh) { 137 menuTrees.push(this); 138 139 this.rootId = (new Date().getTime() + Math.random() + '').replace(/\D/g, ''); 140 this.accessIdPrefix = 'sm-' + this.rootId + '-'; 141 142 if (this.$root.hasClass('sm-rtl')) { 143 this.opts.rightToLeftSubMenus = true; 144 } 145 146 // init root (main menu) 147 var eNS = '.smartmenus'; 148 this.$root 149 .data('smartmenus', this) 150 .attr('data-smartmenus-id', this.rootId) 151 .dataSM('level', 1) 152 .bind(getEventsNS([ 153 ['mouseover focusin', $.proxy(this.rootOver, this)], 154 ['mouseout focusout', $.proxy(this.rootOut, this)], 155 ['keydown', $.proxy(this.rootKeyDown, this)] 156 ], eNS)) 157 .delegate('a', getEventsNS([ 158 ['mouseenter', $.proxy(this.itemEnter, this)], 159 ['mouseleave', $.proxy(this.itemLeave, this)], 160 ['mousedown', $.proxy(this.itemDown, this)], 161 ['focus', $.proxy(this.itemFocus, this)], 162 ['blur', $.proxy(this.itemBlur, this)], 163 ['click', $.proxy(this.itemClick, this)] 164 ], eNS)); 165 166 // hide menus on tap or click outside the root UL 167 eNS += this.rootId; 168 if (this.opts.hideOnClick) { 169 $(document).bind(getEventsNS([ 170 ['touchstart', $.proxy(this.docTouchStart, this)], 171 ['touchmove', $.proxy(this.docTouchMove, this)], 172 ['touchend', $.proxy(this.docTouchEnd, this)], 173 // for Opera Mobile < 11.5, webOS browser, etc. we'll check click too 174 ['click', $.proxy(this.docClick, this)] 175 ], eNS)); 176 } 177 // hide sub menus on resize 178 $(window).bind(getEventsNS([['resize orientationchange', $.proxy(this.winResize, this)]], eNS)); 179 180 if (this.opts.subIndicators) { 181 this.$subArrow = $('<span/>').addClass('sub-arrow'); 182 if (this.opts.subIndicatorsText) { 183 this.$subArrow.html(this.opts.subIndicatorsText); 184 } 185 } 186 187 // make sure mouse detection is enabled 188 initMouseDetection(); 189 } 190 191 // init sub menus 192 this.$firstSub = this.$root.find('ul').each(function() { self.menuInit($(this)); }).eq(0); 193 194 this.$firstLink = this.$root.find('a').eq(0); 195 196 // find current item 197 if (this.opts.markCurrentItem) { 198 var reDefaultDoc = /(index|default)\.[^#\?\/]*/i, 199 reHash = /#.*/, 200 locHref = window.location.href.replace(reDefaultDoc, ''), 201 locHrefNoHash = locHref.replace(reHash, ''); 202 this.$root.find('a').each(function() { 203 var href = this.href.replace(reDefaultDoc, ''), 204 $this = $(this); 205 if (href == locHref || href == locHrefNoHash) { 206 $this.addClass('current'); 207 if (self.opts.markCurrentTree) { 208 $this.parentsUntil('[data-smartmenus-id]', 'ul').each(function() { 209 $(this).dataSM('parent-a').addClass('current'); 210 }); 211 } 212 } 213 }); 214 } 215 216 // save initial state 217 this.wasCollapsible = this.isCollapsible(); 218 }, 219 destroy: function(refresh) { 220 if (!refresh) { 221 var eNS = '.smartmenus'; 222 this.$root 223 .removeData('smartmenus') 224 .removeAttr('data-smartmenus-id') 225 .removeDataSM('level') 226 .unbind(eNS) 227 .undelegate(eNS); 228 eNS += this.rootId; 229 $(document).unbind(eNS); 230 $(window).unbind(eNS); 231 if (this.opts.subIndicators) { 232 this.$subArrow = null; 233 } 234 } 235 this.menuHideAll(); 236 var self = this; 237 this.$root.find('ul').each(function() { 238 var $this = $(this); 239 if ($this.dataSM('scroll-arrows')) { 240 $this.dataSM('scroll-arrows').remove(); 241 } 242 if ($this.dataSM('shown-before')) { 243 if (self.opts.subMenusMinWidth || self.opts.subMenusMaxWidth) { 244 $this.css({ width: '', minWidth: '', maxWidth: '' }).removeClass('sm-nowrap'); 245 } 246 if ($this.dataSM('scroll-arrows')) { 247 $this.dataSM('scroll-arrows').remove(); 248 } 249 $this.css({ zIndex: '', top: '', left: '', marginLeft: '', marginTop: '', display: '' }); 250 } 251 if (($this.attr('id') || '').indexOf(self.accessIdPrefix) == 0) { 252 $this.removeAttr('id'); 253 } 254 }) 255 .removeDataSM('in-mega') 256 .removeDataSM('shown-before') 257 .removeDataSM('ie-shim') 258 .removeDataSM('scroll-arrows') 259 .removeDataSM('parent-a') 260 .removeDataSM('level') 261 .removeDataSM('beforefirstshowfired') 262 .removeAttr('role') 263 .removeAttr('aria-hidden') 264 .removeAttr('aria-labelledby') 265 .removeAttr('aria-expanded'); 266 this.$root.find('a.has-submenu').each(function() { 267 var $this = $(this); 268 if ($this.attr('id').indexOf(self.accessIdPrefix) == 0) { 269 $this.removeAttr('id'); 270 } 271 }) 272 .removeClass('has-submenu') 273 .removeDataSM('sub') 274 .removeAttr('aria-haspopup') 275 .removeAttr('aria-controls') 276 .removeAttr('aria-expanded') 277 .closest('li').removeDataSM('sub'); 278 if (this.opts.subIndicators) { 279 this.$root.find('span.sub-arrow').remove(); 280 } 281 if (this.opts.markCurrentItem) { 282 this.$root.find('a.current').removeClass('current'); 283 } 284 if (!refresh) { 285 this.$root = null; 286 this.$firstLink = null; 287 this.$firstSub = null; 288 if (this.$disableOverlay) { 289 this.$disableOverlay.remove(); 290 this.$disableOverlay = null; 291 } 292 menuTrees.splice($.inArray(this, menuTrees), 1); 293 } 294 }, 295 disable: function(noOverlay) { 296 if (!this.disabled) { 297 this.menuHideAll(); 298 // display overlay over the menu to prevent interaction 299 if (!noOverlay && !this.opts.isPopup && this.$root.is(':visible')) { 300 var pos = this.$root.offset(); 301 this.$disableOverlay = $('<div class="sm-jquery-disable-overlay"/>').css({ 302 position: 'absolute', 303 top: pos.top, 304 left: pos.left, 305 width: this.$root.outerWidth(), 306 height: this.$root.outerHeight(),
307 zIndex: this.getStartZIndex(true), 308 opacity: 0 309 }).appendTo(document.body); 310 } 311 this.disabled = true; 312 } 313 }, 314 docClick: function(e) { 315 if (this.$touchScrollingSub) { 316 this.$touchScrollingSub = null; 317 return; 318 } 319 // hide on any click outside the menu or on a menu link 320 if (this.visibleSubMenus.length && !$.contains(this.$root[0], e.target) || $(e.target).is('a')) { 321 this.menuHideAll(); 322 } 323 }, 324 docTouchEnd: function(e) { 325 if (!this.lastTouch) { 326 return; 327 } 328 if (this.visibleSubMenus.length && (this.lastTouch.x2 === undefined || this.lastTouch.x1 == this.lastTouch.x2) && (this.lastTouch.y2 === undefined || this.lastTouch.y1 == this.lastTouch.y2) && (!this.lastTouch.target || !$.contains(this.$root[0], this.lastTouch.target))) { 329 if (this.hideTimeout) { 330 clearTimeout(this.hideTimeout); 331 this.hideTimeout = 0; 332 } 333 // hide with a delay to prevent triggering accidental unwanted click on some page element 334 var self = this; 335 this.hideTimeout = setTimeout(function() { self.menuHideAll(); }, 350); 336 } 337 this.lastTouch = null; 338 }, 339 docTouchMove: function(e) { 340 if (!this.lastTouch) { 341 return; 342 } 343 var touchPoint = e.originalEvent.touches[0]; 344 this.lastTouch.x2 = touchPoint.pageX; 345 this.lastTouch.y2 = touchPoint.pageY; 346 }, 347 docTouchStart: function(e) { 348 var touchPoint = e.originalEvent.touches[0]; 349 this.lastTouch = { x1: touchPoint.pageX, y1: touchPoint.pageY, target: touchPoint.target }; 350 }, 351 enable: function() { 352 if (this.disabled) { 353 if (this.$disableOverlay) { 354 this.$disableOverlay.remove(); 355 this.$disableOverlay = null; 356 } 357 this.disabled = false; 358 } 359 }, 360 getClosestMenu: function(elm) { 361 var $closestMenu = $(elm).closest('ul'); 362 while ($closestMenu.dataSM('in-mega')) { 363 $closestMenu = $closestMenu.parent().closest('ul'); 364 } 365 return $closestMenu[0] || null; 366 }, 367 getHeight: function($elm) { 368 return this.getOffset($elm, true); 369 }, 370 // returns precise width/height float values 371 getOffset: function($elm, height) { 372 var old; 373 if ($elm.css('display') == 'none') { 374 old = { position: $elm[0].style.position, visibility: $elm[0].style.visibility }; 375 $elm.css({ position: 'absolute', visibility: 'hidden' }).show(); 376 } 377 var box = $elm[0].getBoundingClientRect && $elm[0].getBoundingClientRect(), 378 val = box && (height ? box.height || box.bottom - box.top : box.width || box.right - box.left); 379 if (!val && val !== 0) { 380 val = height ? $elm[0].offsetHeight : $elm[0].offsetWidth; 381 } 382 if (old) { 383 $elm.hide().css(old); 384 } 385 return val; 386 }, 387 getStartZIndex: function(root) { 388 var zIndex = parseInt(this[root ? '$root' : '$firstSub'].css('z-index')); 389 if (!root && isNaN(zIndex)) { 390 zIndex = parseInt(this.$root.css('z-index')); 391 } 392 return !isNaN(zIndex) ? zIndex : 1; 393 }, 394 getTouchPoint: function(e) { 395 return e.touches && e.touches[0] || e.changedTouches && e.changedTouches[0] || e; 396 }, 397 getViewport: function(height) { 398 var name = height ? 'Height' : 'Width', 399 val = document.documentElement['client' + name], 400 val2 = window['inner' + name]; 401 if (val2) { 402 val = Math.min(val, val2); 403 } 404 return val; 405 }, 406 getViewportHeight: function() { 407 return this.getViewport(true); 408 }, 409 getViewportWidth: function() { 410 return this.getViewport(); 411 }, 412 getWidth: function($elm) { 413 return this.getOffset($elm); 414 }, 415 handleEvents: function() { 416 return !this.disabled && this.isCSSOn(); 417 }, 418 handleItemEvents: function($a) { 419 return this.handleEvents() && !this.isLinkInMegaMenu($a); 420 }, 421 isCollapsible: function() { 422 return this.$firstSub.css('position') == 'static'; 423 }, 424 isCSSOn: function() { 425 return this.$firstLink.css('display') == 'block'; 426 }, 427 isFixed: function() { 428 var isFixed = this.$root.css('position') == 'fixed'; 429 if (!isFixed) { 430 this.$root.parentsUntil('body').each(function() { 431 if ($(this).css('position') == 'fixed') { 432 isFixed = true; 433 return false;
434 } 435 }); 436 } 437 return isFixed; 438 }, 439 isLinkInMegaMenu: function($a) { 440 return $(this.getClosestMenu($a[0])).hasClass('mega-menu'); 441 }, 442 isTouchMode: function() { 443 return !mouse || this.opts.noMouseOver || this.isCollapsible(); 444 }, 445 itemActivate: function($a, focus) { 446 var $ul = $a.closest('ul'), 447 level = $ul.dataSM('level'); 448 // if for some reason the parent item is not activated (e.g. this is an API call to activate the item), activate all parent items first 449 if (level > 1 && (!this.activatedItems[level - 2] || this.activatedItems[level - 2][0] != $ul.dataSM('parent-a')[0])) { 450 var self = this; 451 $($ul.parentsUntil('[data-smartmenus-id]', 'ul').get().reverse()).add($ul).each(function() { 452 self.itemActivate($(this).dataSM('parent-a')); 453 }); 454 } 455 // hide any visible deeper level sub menus 456 if (!this.isCollapsible() || focus) { 457 this.menuHideSubMenus(!this.activatedItems[level - 1] || this.activatedItems[level - 1][0] != $a[0] ? level - 1 : level); 458 } 459 // save new active item for this level 460 this.activatedItems[level - 1] = $a; 461 if (this.$root.triggerHandler('activate.smapi', $a[0]) === false) { 462 return; 463 } 464 // show the sub menu if this item has one 465 var $sub = $a.dataSM('sub'); 466 if ($sub && (this.isTouchMode() || (!this.opts.showOnClick || this.clickActivated))) { 467 this.menuShow($sub); 468 } 469 }, 470 itemBlur: function(e) { 471 var $a = $(e.currentTarget); 472 if (!this.handleItemEvents($a)) { 473 return; 474 } 475 this.$root.triggerHandler('blur.smapi', $a[0]); 476 }, 477 itemClick: function(e) { 478 var $a = $(e.currentTarget); 479 if (!this.handleItemEvents($a)) { 480 return; 481 } 482 if (this.$touchScrollingSub && this.$touchScrollingSub[0] == $a.closest('ul')[0]) { 483 this.$touchScrollingSub = null; 484 e.stopPropagation(); 485 return false; 486 } 487 if (this.$root.triggerHandler('click.smapi', $a[0]) === false) { 488 return false; 489 } 490 var subArrowClicked = $(e.target).is('span.sub-arrow'), 491 $sub = $a.dataSM('sub'), 492 firstLevelSub = $sub ? $sub.dataSM('level') == 2 : false; 493 // if the sub is not visible 494 if ($sub && !$sub.is(':visible')) { 495 if (this.opts.showOnClick && firstLevelSub) { 496 this.clickActivated = true; 497 } 498 // try to activate the item and show the sub 499 this.itemActivate($a); 500 // if "itemActivate" showed the sub, prevent the click so that the link is not loaded 501 // if it couldn't show it, then the sub menus are disabled with an !important declaration (e.g. via mobile styles) so let the link get loaded 502 if ($sub.is(':visible')) { 503 this.focusActivated = true; 504 return false; 505 } 506 } else if (this.isCollapsible() && subArrowClicked) { 507 this.itemActivate($a); 508 this.menuHide($sub); 509 return false; 510 } 511 if (this.opts.showOnClick && firstLevelSub || $a.hasClass('disabled') || this.$root.triggerHandler('select.smapi', $a[0]) === false) { 512 return false; 513 } 514 }, 515 itemDown: function(e) { 516 var $a = $(e.currentTarget); 517 if (!this.handleItemEvents($a)) { 518 return; 519 } 520 $a.dataSM('mousedown', true); 521 }, 522 itemEnter: function(e) { 523 var $a = $(e.currentTarget); 524 if (!this.handleItemEvents($a)) { 525 return; 526 } 527 if (!this.isTouchMode()) { 528 if (this.showTimeout) { 529 clearTimeout(this.showTimeout); 530 this.showTimeout = 0; 531 } 532 var self = this; 533 this.showTimeout = setTimeout(function() { self.itemActivate($a); }, this.opts.showOnClick && $a.closest('ul').dataSM('level') == 1 ? 1 : this.opts.showTimeout); 534 } 535 this.$root.triggerHandler('mouseenter.smapi', $a[0]); 536 }, 537 itemFocus: function(e) { 538 var $a = $(e.currentTarget); 539 if (!this.handleItemEvents($a)) { 540 return; 541 } 542 // fix (the mousedown check): in some browsers a tap/click produces consecutive focus + click events so we don't need to activate the item on focus 543 if (this.focusActivated && (!this.isTouchMode() || !$a.dataSM('mousedown')) && (!this.activatedItems.length || this.activatedItems[this.activatedItems.length - 1][0] != $a[0])) { 544 this.itemActivate($a, true); 545 } 546 this.$root.triggerHandler('focus.smapi', $a[0]); 547 }, 548 itemLeave: function(e) { 549 var $a = $(e.currentTarget); 550 if (!this.handleItemEvents($a)) { 551 return; 552 } 553 if (!this.isTouchMode()) { 554 $a[0].blur(); 555 if (this.showTimeout) { 556 clearTimeout(this.showTimeout); 557 this.showTimeout = 0; 558 } 559 } 560 $a.removeDataSM('mousedown');
561 this.$root.triggerHandler('mouseleave.smapi', $a[0]); 562 }, 563 menuHide: function($sub) { 564 if (this.$root.triggerHandler('beforehide.smapi', $sub[0]) === false) { 565 return; 566 } 567 $sub.stop(true, true); 568 if ($sub.css('display') != 'none') { 569 var complete = function() { 570 // unset z-index 571 $sub.css('z-index', ''); 572 }; 573 // if sub is collapsible (mobile view) 574 if (this.isCollapsible()) { 575 if (this.opts.collapsibleHideFunction) { 576 this.opts.collapsibleHideFunction.call(this, $sub, complete); 577 } else { 578 $sub.hide(this.opts.collapsibleHideDuration, complete); 579 } 580 } else { 581 if (this.opts.hideFunction) { 582 this.opts.hideFunction.call(this, $sub, complete); 583 } else { 584 $sub.hide(this.opts.hideDuration, complete); 585 } 586 } 587 // remove IE iframe shim 588 if ($sub.dataSM('ie-shim')) { 589 $sub.dataSM('ie-shim').remove().css({ '-webkit-transform': '', transform: '' }); 590 } 591 // deactivate scrolling if it is activated for this sub 592 if ($sub.dataSM('scroll')) { 593 this.menuScrollStop($sub); 594 $sub.css({ 'touch-action': '', '-ms-touch-action': '', '-webkit-transform': '', transform: '' }) 595 .unbind('.smartmenus_scroll').removeDataSM('scroll').dataSM('scroll-arrows').hide(); 596 } 597 // unhighlight parent item + accessibility 598 $sub.dataSM('parent-a').removeClass('highlighted').attr('aria-expanded', 'false'); 599 $sub.attr({ 600 'aria-expanded': 'false', 601 'aria-hidden': 'true' 602 }); 603 var level = $sub.dataSM('level'); 604 this.activatedItems.splice(level - 1, 1); 605 this.visibleSubMenus.splice($.inArray($sub, this.visibleSubMenus), 1); 606 this.$root.triggerHandler('hide.smapi', $sub[0]); 607 } 608 }, 609 menuHideAll: function() { 610 if (this.showTimeout) { 611 clearTimeout(this.showTimeout); 612 this.showTimeout = 0; 613 } 614 // hide all subs 615 // if it's a popup, this.visibleSubMenus[0] is the root UL 616 var level = this.opts.isPopup ? 1 : 0; 617 for (var i = this.visibleSubMenus.length - 1; i >= level; i--) { 618 this.menuHide(this.visibleSubMenus[i]); 619 } 620 // hide root if it's popup 621 if (this.opts.isPopup) { 622 this.$root.stop(true, true); 623 if (this.$root.is(':visible')) { 624 if (this.opts.hideFunction) { 625 this.opts.hideFunction.call(this, this.$root); 626 } else { 627 this.$root.hide(this.opts.hideDuration); 628 } 629 // remove IE iframe shim 630 if (this.$root.dataSM('ie-shim')) { 631 this.$root.dataSM('ie-shim').remove(); 632 } 633 } 634 } 635 this.activatedItems = []; 636 this.visibleSubMenus = []; 637 this.clickActivated = false; 638 this.focusActivated = false; 639 // reset z-index increment 640 this.zIndexInc = 0; 641 this.$root.triggerHandler('hideAll.smapi'); 642 }, 643 menuHideSubMenus: function(level) { 644 for (var i = this.activatedItems.length - 1; i >= level; i--) { 645 var $sub = this.activatedItems[i].dataSM('sub'); 646 if ($sub) { 647 this.menuHide($sub); 648 } 649 } 650 }, 651 menuIframeShim: function($ul) { 652 // create iframe shim for the menu 653 if (IE && this.opts.overlapControlsInIE && !$ul.dataSM('ie-shim')) { 654 $ul.dataSM('ie-shim', $('<iframe/>').attr({ src: 'javascript:0', tabindex: -9 }) 655 .css({ position: 'absolute', top: 'auto', left: '0', opacity: 0, border: '0' }) 656 ); 657 } 658 }, 659 menuInit: function($ul) { 660 if (!$ul.dataSM('in-mega')) { 661 // mark UL's in mega drop downs (if any) so we can neglect them 662 if ($ul.hasClass('mega-menu')) { 663 $ul.find('ul').dataSM('in-mega', true); 664 } 665 // get level (much faster than, for example, using parentsUntil) 666 var level = 2, 667 par = $ul[0]; 668 while ((par = par.parentNode.parentNode) != this.$root[0]) { 669 level++; 670 } 671 // cache stuff for quick access 672 var $a = $ul.prevAll('a').eq(-1); 673 // if the link is nested (e.g. in a heading) 674 if (!$a.length) { 675 $a = $ul.prevAll().find('a').eq(-1); 676 } 677 $a.addClass('has-submenu').dataSM('sub', $ul); 678 $ul.dataSM('parent-a', $a) 679 .dataSM('level', level) 680 .parent().dataSM('sub', $ul); 681 // accessibility 682 var aId = $a.attr('id') || this.accessIdPrefix + (++this.idInc), 683 ulId = $ul.attr('id') || this.accessIdPrefix + (++this.idInc); 684 $a.attr({ 685 id: aId, 686 'aria-haspopup': 'true', 687 'aria-controls': ulId, 688 'aria-expanded': 'false' 689 }); 690 $ul.attr({ 691 id: ulId, 692 'role': 'group', 693 'aria-hidden': 'true', 694 'aria-labelledby': aId, 695 'aria-expanded': 'false' 696 }); 697 // add sub indicator to parent item 698 if (this.opts.subIndicators) { 699 $a[this.opts.subIndicatorsPos](this.$subArrow.clone()); 700 } 701 } 702 }, 703 menuPosition: function($sub) { 704 var $a = $sub.dataSM('parent-a'), 705 $li = $a.closest('li'), 706 $ul = $li.parent(), 707 level = $sub.dataSM('level'), 708 subW = this.getWidth($sub), 709 subH = this.getHeight($sub), 710 itemOffset = $a.offset(), 711 itemX = itemOffset.left, 712 itemY = itemOffset.top, 713 itemW = this.getWidth($a), 714 itemH = this.getHeight($a), 715 $win = $(window), 716 winX = $win.scrollLeft(), 717 winY = $win.scrollTop(), 718 winW = this.getViewportWidth(),
719 winH = this.getViewportHeight(), 720 horizontalParent = $ul.parent().is('[data-sm-horizontal-sub]') || level == 2 && !$ul.hasClass('sm-vertical'), 721 rightToLeft = this.opts.rightToLeftSubMenus && !$li.is('[data-sm-reverse]') || !this.opts.rightToLeftSubMenus && $li.is('[data-sm-reverse]'), 722 subOffsetX = level == 2 ? this.opts.mainMenuSubOffsetX : this.opts.subMenusSubOffsetX, 723 subOffsetY = level == 2 ? this.opts.mainMenuSubOffsetY : this.opts.subMenusSubOffsetY, 724 x, y; 725 if (horizontalParent) { 726 x = rightToLeft ? itemW - subW - subOffsetX : subOffsetX; 727 y = this.opts.bottomToTopSubMenus ? -subH - subOffsetY : itemH + subOffsetY; 728 } else { 729 x = rightToLeft ? subOffsetX - subW : itemW - subOffsetX; 730 y = this.opts.bottomToTopSubMenus ? itemH - subOffsetY - subH : subOffsetY; 731 } 732 if (this.opts.keepInViewport) { 733 var absX = itemX + x, 734 absY = itemY + y; 735 if (rightToLeft && absX < winX) { 736 x = horizontalParent ? winX - absX + x : itemW - subOffsetX; 737 } else if (!rightToLeft && absX + subW > winX + winW) { 738 x = horizontalParent ? winX + winW - subW - absX + x : subOffsetX - subW; 739 } 740 if (!horizontalParent) { 741 if (subH < winH && absY + subH > winY + winH) { 742 y += winY + winH - subH - absY; 743 } else if (subH >= winH || absY < winY) { 744 y += winY - absY; 745 } 746 } 747 // do we need scrolling? 748 // 0.49 used for better precision when dealing with float values 749 if (horizontalParent && (absY + subH > winY + winH + 0.49 || absY < winY) || !horizontalParent && subH > winH + 0.49) { 750 var self = this; 751 if (!$sub.dataSM('scroll-arrows')) { 752 $sub.dataSM('scroll-arrows', $([$('<span class="scroll-up"><span class="scroll-up-arrow"></span></span>')[0], $('<span class="scroll-down"><span class="scroll-down-arrow"></span></span>')[0]]) 753 .bind({ 754 mouseenter: function() { 755 $sub.dataSM('scroll').up = $(this).hasClass('scroll-up'); 756 self.menuScroll($sub); 757 }, 758 mouseleave: function(e) { 759 self.menuScrollStop($sub); 760 self.menuScrollOut($sub, e); 761 }, 762 'mousewheel DOMMouseScroll': function(e) { e.preventDefault(); } 763 }) 764 .insertAfter($sub) 765 ); 766 } 767 // bind scroll events and save scroll data for this sub 768 var eNS = '.smartmenus_scroll'; 769 $sub.dataSM('scroll', { 770 y: this.cssTransforms3d ? 0 : y - itemH, 771 step: 1, 772 // cache stuff for faster recalcs later 773 itemH: itemH, 774 subH: subH, 775 arrowDownH: this.getHeight($sub.dataSM('scroll-arrows').eq(1)) 776 }) 777 .bind(getEventsNS([ 778 ['mouseover', function(e) { self.menuScrollOver($sub, e); }], 779 ['mouseout', function(e) { self.menuScrollOut($sub, e); }], 780 ['mousewheel DOMMouseScroll', function(e) { self.menuScrollMousewheel($sub, e); }] 781 ], eNS)) 782 .dataSM('scroll-arrows').css({ top: 'auto', left: '0', marginLeft: x + (parseInt($sub.css('border-left-width')) || 0), width: subW - (parseInt($sub.css('border-left-width')) || 0) - (parseInt($sub.css('border-right-width')) || 0), zIndex: $sub.css('z-index') }) 783 .eq(horizontalParent && this.opts.bottomToTopSubMenus ? 0 : 1).show(); 784 // when a menu tree is fixed positioned we allow scrolling via touch too 785 // since there is no other way to access such long sub menus if no mouse is present 786 if (this.isFixed()) { 787 $sub.css({ 'touch-action': 'none', '-ms-touch-action': 'none' }) 788 .bind(getEventsNS([ 789 [touchEvents ? 'touchstart touchmove touchend' : 'pointerdown pointermove pointerup MSPointerDown MSPointerMove MSPointerUp', function(e) { 790 self.menuScrollTouch($sub, e); 791 }] 792 ], eNS)); 793 } 794 } 795 } 796 $sub.css({ top: 'auto', left: '0', marginLeft: x, marginTop: y - itemH }); 797 // IE iframe shim 798 this.menuIframeShim($sub); 799 if ($sub.dataSM('ie-shim')) { 800 $sub.dataSM('ie-shim').css({ zIndex: $sub.css('z-index'), width: subW, height: subH, marginLeft: x, marginTop: y - itemH }); 801 } 802 }, 803 menuScroll: function($sub, once, step) { 804 var data = $sub.dataSM('scroll'), 805 $arrows = $sub.dataSM('scroll-arrows'), 806 end = data.up ? data.upEnd : data.downEnd, 807 diff; 808 if (!once && data.momentum) { 809 data.momentum *= 0.92; 810 diff = data.momentum; 811 if (diff < 0.5) { 812 this.menuScrollStop($sub); 813 return; 814 } 815 } else { 816 diff = step || (once || !this.opts.scrollAccelerate ? this.opts.scrollStep : Math.floor(data.step)); 817 } 818 // hide any visible deeper level sub menus 819 var level = $sub.dataSM('level'); 820 if (this.activatedItems[level - 1] && this.activatedItems[level - 1].dataSM('sub') && this.activatedItems[level - 1].dataSM('sub').is(':visible')) {
821 this.menuHideSubMenus(level - 1); 822 } 823 data.y = data.up && end <= data.y || !data.up && end >= data.y ? data.y : (Math.abs(end - data.y) > diff ? data.y + (data.up ? diff : -diff) : end); 824 $sub.add($sub.dataSM('ie-shim')).css(this.cssTransforms3d ? { '-webkit-transform': 'translate3d(0, ' + data.y + 'px, 0)', transform: 'translate3d(0, ' + data.y + 'px, 0)' } : { marginTop: data.y }); 825 // show opposite arrow if appropriate 826 if (mouse && (data.up && data.y > data.downEnd || !data.up && data.y < data.upEnd)) { 827 $arrows.eq(data.up ? 1 : 0).show(); 828 } 829 // if we've reached the end 830 if (data.y == end) { 831 if (mouse) { 832 $arrows.eq(data.up ? 0 : 1).hide(); 833 } 834 this.menuScrollStop($sub); 835 } else if (!once) { 836 if (this.opts.scrollAccelerate && data.step < this.opts.scrollStep) { 837 data.step += 0.2; 838 } 839 var self = this; 840 this.scrollTimeout = requestAnimationFrame(function() { self.menuScroll($sub); }); 841 } 842 }, 843 menuScrollMousewheel: function($sub, e) { 844 if (this.getClosestMenu(e.target) == $sub[0]) { 845 e = e.originalEvent; 846 var up = (e.wheelDelta || -e.detail) > 0; 847 if ($sub.dataSM('scroll-arrows').eq(up ? 0 : 1).is(':visible')) { 848 $sub.dataSM('scroll').up = up; 849 this.menuScroll($sub, true); 850 } 851 } 852 e.preventDefault(); 853 }, 854 menuScrollOut: function($sub, e) { 855 if (mouse) { 856 if (!/^scroll-(up|down)/.test((e.relatedTarget || '').className) && ($sub[0] != e.relatedTarget && !$.contains($sub[0], e.relatedTarget) || this.getClosestMenu(e.relatedTarget) != $sub[0])) { 857 $sub.dataSM('scroll-arrows').css('visibility', 'hidden'); 858 } 859 } 860 }, 861 menuScrollOver: function($sub, e) { 862 if (mouse) { 863 if (!/^scroll-(up|down)/.test(e.target.className) && this.getClosestMenu(e.target) == $sub[0]) { 864 this.menuScrollRefreshData($sub); 865 var data = $sub.dataSM('scroll'), 866 upEnd = $(window).scrollTop() - $sub.dataSM('parent-a').offset().top - data.itemH; 867 $sub.dataSM('scroll-arrows').eq(0).css('margin-top', upEnd).end() 868 .eq(1).css('margin-top', upEnd + this.getViewportHeight() - data.arrowDownH).end() 869 .css('visibility', 'visible'); 870 } 871 } 872 }, 873 menuScrollRefreshData: function($sub) { 874 var data = $sub.dataSM('scroll'), 875 upEnd = $(window).scrollTop() - $sub.dataSM('parent-a').offset().top - data.itemH; 876 if (this.cssTransforms3d) { 877 upEnd = -(parseFloat($sub.css('margin-top')) - upEnd); 878 } 879 $.extend(data, { 880 upEnd: upEnd, 881 downEnd: upEnd + this.getViewportHeight() - data.subH 882 }); 883 }, 884 menuScrollStop: function($sub) { 885 if (this.scrollTimeout) { 886 cancelAnimationFrame(this.scrollTimeout); 887 this.scrollTimeout = 0; 888 $sub.dataSM('scroll').step = 1; 889 return true; 890 } 891 }, 892 menuScrollTouch: function($sub, e) { 893 e = e.originalEvent; 894 if (isTouchEvent(e)) { 895 var touchPoint = this.getTouchPoint(e); 896 // neglect event if we touched a visible deeper level sub menu 897 if (this.getClosestMenu(touchPoint.target) == $sub[0]) { 898 var data = $sub.dataSM('scroll'); 899 if (/(start|down)$/i.test(e.type)) { 900 if (this.menuScrollStop($sub)) { 901 // if we were scrolling, just stop and don't activate any link on the first touch 902 e.preventDefault(); 903 this.$touchScrollingSub = $sub; 904 } else { 905 this.$touchScrollingSub = null; 906 } 907 // update scroll data since the user might have zoomed, etc. 908 this.menuScrollRefreshData($sub); 909 // extend it with the touch properties 910 $.extend(data, { 911 touchStartY: touchPoint.pageY, 912 touchStartTime: e.timeStamp 913 }); 914 } else if (/move$/i.test(e.type)) { 915 var prevY = data.touchY !== undefined ? data.touchY : data.touchStartY; 916 if (prevY !== undefined && prevY != touchPoint.pageY) { 917 this.$touchScrollingSub = $sub; 918 var up = prevY < touchPoint.pageY; 919 // changed direction? reset... 920 if (data.up !== undefined && data.up != up) { 921 $.extend(data, { 922 touchStartY: touchPoint.pageY, 923 touchStartTime: e.timeStamp 924 }); 925 } 926 $.extend(data, { 927 up: up, 928 touchY: touchPoint.pageY 929 }); 930 this.menuScroll($sub, true, Math.abs(touchPoint.pageY - prevY)); 931 } 932 e.preventDefault(); 933 } else { // touchend/pointerup 934 if (data.touchY !== undefined) { 935 if (data.momentum = Math.pow(Math.abs(touchPoint.pageY - data.touchStartY) / (e.timeStamp - data.touchStartTime), 2) * 15) { 936 this.menuScrollStop($sub); 937 this.menuScroll($sub); 938 e.preventDefault(); 939 } 940 delete data.touchY; 941 } 942 } 943 } 944 } 945 }, 946 menuShow: function($sub) { 947 if (!$sub.dataSM('beforefirstshowfired')) { 948 $sub.dataSM('beforefirstshowfired', true); 949 if (this.$root.triggerHandler('beforefirstshow.smapi', $sub[0]) === false) { 950 return; 951 } 952 } 953 if (this.$root.triggerHandler('beforeshow.smapi', $sub[0]) === false) { 954 return; 955 } 956 $sub.dataSM('shown-before', true) 957 .stop(true, true); 958 if (!$sub.is(':visible')) { 959 // highlight parent item 960 var $a = $sub.dataSM('parent-a'); 961 if (this.opts.keepHighlighted || this.isCollapsible()) { 962 $a.addClass('highlighted'); 963 } 964 if (this.isCollapsible()) { 965 $sub.removeClass('sm-nowrap').css({ zIndex: '', width: 'auto', minWidth: '', maxWidth: '', top: '', left: '', marginLeft: '', marginTop: '' }); 966 } else { 967 // set z-index 968 $sub.css('z-index', this.zIndexInc = (this.zIndexInc || this.get
968StartZIndex()) + 1); 969 // min/max-width fix - no way to rely purely on CSS as all UL's are nested 970 if (this.opts.subMenusMinWidth || this.opts.subMenusMaxWidth) { 971 $sub.css({ width: 'auto', minWidth: '', maxWidth: '' }).addClass('sm-nowrap'); 972 if (this.opts.subMenusMinWidth) { 973 $sub.css('min-width', this.opts.subMenusMinWidth); 974 } 975 if (this.opts.subMenusMaxWidth) { 976 var noMaxWidth = this.getWidth($sub); 977 $sub.css('max-width', this.opts.subMenusMaxWidth); 978 if (noMaxWidth > this.getWidth($sub)) { 979 $sub.removeClass('sm-nowrap').css('width', this.opts.subMenusMaxWidth); 980 } 981 } 982 } 983 this.menuPosition($sub); 984 // insert IE iframe shim 985 if ($sub.dataSM('ie-shim')) { 986 $sub.dataSM('ie-shim').insertBefore($sub); 987 } 988 } 989 var complete = function() { 990 // fix: "overflow: hidden;" is not reset on animation complete in jQuery < 1.9.0 in Chrome when global "box-sizing: border-box;" is used 991 $sub.css('overflow', ''); 992 }; 993 // if sub is collapsible (mobile view) 994 if (this.isCollapsible()) { 995 if (this.opts.collapsibleShowFunction) { 996 this.opts.collapsibleShowFunction.call(this, $sub, complete); 997 } else { 998 $sub.show(this.opts.collapsibleShowDuration, complete); 999 } 1000 } else { 1001 if (this.opts.showFunction) { 1002 this.opts.showFunction.call(this, $sub, complete); 1003 } else { 1004 $sub.show(this.opts.showDuration, complete); 1005 } 1006 } 1007 // accessibility 1008 $a.attr('aria-expanded', 'true'); 1009 $sub.attr({ 1010 'aria-expanded': 'true', 1011 'aria-hidden': 'false' 1012 }); 1013 // store sub menu in visible array 1014 this.visibleSubMenus.push($sub); 1015 this.$root.triggerHandler('show.smapi', $sub[0]); 1016 } 1017 }, 1018 popupHide: function(noHideTimeout) { 1019 if (this.hideTimeout) { 1020 clearTimeout(this.hideTimeout); 1021 this.hideTimeout = 0; 1022 } 1023 var self = this; 1024 this.hideTimeout = setTimeout(function() { 1025 self.menuHideAll(); 1026 }, noHideTimeout ? 1 : this.opts.hideTimeout); 1027 }, 1028 popupShow: function(left, top) { 1029 if (!this.opts.isPopup) { 1030 alert('SmartMenus jQuery Error:\n\nIf you want to show this menu via the "popupShow" method, set the isPopup:true option.'); 1031 return; 1032 } 1033 if (this.hideTimeout) { 1034 clearTimeout(this.hideTimeout); 1035 this.hideTimeout = 0; 1036 } 1037 this.$root.dataSM('shown-before', true) 1038 .stop(true, true); 1039 if (!this.$root.is(':visible')) { 1040 this.$root.css({ left: left, top: top }); 1041 // IE iframe shim 1042 this.menuIframeShim(this.$root); 1043 if (this.$root.dataSM('ie-shim')) { 1044 this.$root.dataSM('ie-shim').css({ zIndex: this.$root.css('z-index'), width: this.getWidth(this.$root), height: this.getHeight(this.$root), left: left, top: top }).insertBefore(this.$root); 1045 } 1046 // show menu 1047 var self = this, 1048 complete = function() { 1049 self.$root.css('overflow', ''); 1050 }; 1051 if (this.opts.showFunction) { 1052 this.opts.showFunction.call(this, this.$root, complete); 1053 } else { 1054 this.$root.show(this.opts.showDuration, complete); 1055 } 1056 this.visibleSubMenus[0] = this.$root; 1057 } 1058 }, 1059 refresh: function() { 1060 this.destroy(true); 1061 this.init(true); 1062 }, 1063 rootKeyDown: function(e) { 1064 if (!this.handleEvents()) { 1065 return; 1066 } 1067 switch (e.keyCode) { 1068 case 27: // reset on Esc 1069 var $activeTopItem = this.activatedItems[0]; 1070 if ($activeTopItem) { 1071 this.menuHideAll(); 1072 $activeTopItem[0].focus(); 1073 var $sub = $activeTopItem.dataSM('sub'); 1074 if ($sub) { 1075 this.menuHide($sub); 1076 } 1077 } 1078 break; 1079 case 32: // activate item's sub on Space 1080 var $target = $(e.target); 1081 if ($target.is('a') && this.handleItemEvents($target)) { 1082 var $sub = $target.dataSM('sub'); 1083 if ($sub && !$sub.is(':visible')) { 1084 this.itemClick({ currentTarget: e.target }); 1085 e.preventDefault(); 1086 } 1087 } 1088 break; 1089 } 1090 }, 1091 rootOut: function(e) { 1092 if (!this.handleEvents() || this.isTouchMode() || e.target == this.$root[0]) { 1093 return; 1094 } 1095 if (this.hideTimeout) { 1096 clearTimeout(this.hideTimeout); 1097 this.hideTimeout = 0; 1098 } 1099 if (!this.opts.showOnClick || !this.opts.hideOnClick) { 1100 var self = this; 1101 this.hideTimeout = setTimeout(function() { self.menuHideAll(); }, this.opts.hideTimeout); 1102 } 1103 }, 1104 rootOver: function(e) { 1105 if (!this.handleEvents() || this.isTouchMode() || e.target == this.$root[0]) { 1106 return; 1107 } 1108 if (this.hideTimeout) { 1109 clearTimeout(this.hideTimeout); 1110 this.hideTimeout = 0; 1111 } 1112 }, 1113 winResize: function(e) { 1114 if (!this.handleEvents()) { 1115 // we still need to resize the disable overlay if it's visible 1116 if (this.$disableOverlay) { 1117 var pos = this.$root.offset(); 1118 this.$disableOverlay.css({ 1119 top: pos.top, 1120 left: pos.left, 1121 width: this.$root.outerWidth(), 1122 height: this.$root.outerHeight() 1123 }); 1124 } 1125 return; 1126 } 1127 // hide sub menus on resize - on mobile do it only on orientation change 1128 if (!('onorientationchange' in window) || e.type == 'orientationchange') { 1129 var isCollapsible = this.isCollapsible(); 1130 // if it was collapsible before resize and still is, don't do it 1131 if (!(this.wasCollapsible && isCollapsible)) { 1132 if (this.activatedItems.length) { 1133 this.activatedItems[this.activatedItems.length - 1][0].blur(); 1134 } 1135 this.menuHideAll(); 1136 } 1137 this.wasCollapsible = isCollapsible; 1138 } 1139 } 1140 } 1141 }); 1142 1143 $.fn.dataSM = function(key, val) { 1144 if (val) { 1145 return this.data(key + '_smartmenus', val); 1146 } 1147 return this.data(key + '_smartmenus'); 1148 } 1149 1150 $.fn.removeDataSM = function(key) { 1151 return this.removeData(key + '_smartmenus'); 1152 } 1153 1154 $.fn.smartmenus = function(options) { 1155 if (typeof options == 'string') { 1156 var args = arguments, 1157 method = options; 1158 Array.prototype.shift.call(args); 1159 return this.each(function() { 1160 var smartmenus = $(this).data('smartmenus'); 1161 if (smartmenus && smartmenus[method]) { 1162 smartmenus[method].apply(smartmenus, args); 1163 } 1164 }); 1165 } 1166 var opts = $.extend({}, $.fn.smartmenus.defaults, options); 1167 return this.each(function() { 1168 new $.SmartMenus(this, opts); 1169 }); 1170 } 1171 1172 // default settings 1173 $.fn.smartmenus.defaults = { 1174 isPopup: false, // is this a popup menu (can be shown via the popupS
1174how/popupHide methods) or a permanent menu bar 1175 mainMenuSubOffsetX: 0, // pixels offset from default position 1176 mainMenuSubOffsetY: 0, // pixels offset from default position 1177 subMenusSubOffsetX: 0, // pixels offset from default position 1178 subMenusSubOffsetY: 0, // pixels offset from default position 1179 subMenusMinWidth: '10em', // min-width for the sub menus (any CSS unit) - if set, the fixed width set in CSS will be ignored 1180 subMenusMaxWidth: '20em', // max-width for the sub menus (any CSS unit) - if set, the fixed width set in CSS will be ignored 1181 subIndicators: true, // create sub menu indicators - creates a SPAN and inserts it in the A 1182 subIndicatorsPos: 'prepend', // position of the SPAN relative to the menu item content ('prepend', 'append') 1183 subIndicatorsText: '+', // [optionally] add text in the SPAN (e.g. '+') (you may want to check the CSS for the sub indicators too) 1184 scrollStep: 30, // pixels step when scrolling long sub menus that do not fit in the viewport height 1185 scrollAccelerate: true, // accelerate scrolling or use a fixed step 1186 showTimeout: 250, // timeout before showing the sub menus 1187 hideTimeout: 500, // timeout before hiding the sub menus 1188 showDuration: 0, // duration for show animation - set to 0 for no animation - matters only if showFunction:null 1189 showFunction: null, // custom function to use when showing a sub menu (the default is the jQuery 'show') 1190 // don't forget to call complete() at the end of whatever you do 1191 // e.g.: function($ul, complete) { $ul.fadeIn(250, complete); } 1192 hideDuration: 0, // duration for hide animation - set to 0 for no animation - matters only if hideFunction:null 1193 hideFunction: function($ul, complete) { $ul.fadeOut(200, complete); }, // custom function to use when hiding a sub menu (the default is the jQuery 'hide') 1194 // don't forget to call complete() at the end of whatever you do 1195 // e.g.: function($ul, complete) { $ul.fadeOut(250, complete); } 1196 collapsibleShowDuration:0, // duration for show animation for collapsible sub menus - matters only if collapsibleShowFunction:null 1197 collapsibleShowFunction:function($ul, complete) { $ul.slideDown(200, complete); }, // custom function to use when showing a collapsible sub menu 1198 // (i.e. when mobile styles are used to make the sub menus collapsible) 1199 collapsibleHideDuration:0, // duration for hide animation for collapsible sub menus - matters only if collapsibleHideFunction:null 1200 collapsibleHideFunction:function($ul, complete) { $ul.slideUp(200, complete); }, // custom function to use when hiding a collapsible sub menu 1201 // (i.e. when mobile styles are used to make the sub menus collapsible) 1202 showOnClick: false, // show the first-level sub menus onclick instead of onmouseover (i.e. mimic desktop app menus) (matters only for mouse input) 1203 hideOnClick: true, // hide the sub menus on click/tap anywhere on the page 1204 noMouseOver: false, // disable sub menus activation onmouseover (i.e. behave like in touch mode - use just mouse clicks) (matters only for mouse input) 1205 keepInViewport: true, // reposition the sub menus if needed to make sure they always appear inside the viewport 1206 keepHighlighted: true, // keep all ancestor items of the current sub menu highlighted (adds the 'highlighted' class to the A's) 1207 markCurrentItem: false, // automatically add the 'current' class to the A element of the item linking to the current URL 1208 markCurrentTree: true, // add the 'current' class also to the A elements of all ancestor items of the current item 1209 rightToLeftSubMenus: false, // right to left display of the sub menus (check the CSS for the sub indicators' position) 1210 bottomToTopSubMenus: false, // bottom to top display of the sub menus 1211 overlapControlsInIE: true // make sure sub menus appear on top of special OS controls in IE (i.e. SELECT, OBJECT, EMBED, etc.) 1212 }; 1213 1214 return $; 1215}));
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.