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