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