1/* 2 * SmartMenus jQuery v1.1.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 <= 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 }; 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('sub-arrow'); 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.sub-arrow').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('.sub-arrow'), 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 (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 we are in collapsible mode 513 } else if (collapsible && (behaviorToggle || subArrowClicked)) { 514 this.itemActivate($a, behaviorAccordion); 515 this.menuHide($sub); 516 if (behaviorToggle) { 517 this.focusActivated = false; 518 } 519 return false; 520 } 521 } 522 if (this.opts.showOnClick && firstLevelSub || $a.hasClass('disabled') || this.$root.triggerHandler('select.smapi', $a[0]) === false) { 523 return false; 524 } 525 }, 526 itemDown: function(e) { 527 var $a = $(e.currentTarget); 528 if (!this.handleItemEvents($a)) { 529 return; 530 } 531 $a.dataSM('mousedown', true); 532 }, 533 itemEnter: function(e) { 534 var $a = $(e.currentTarget); 535 if (!this.handleItemEvents($a)) { 536 return; 537 } 538 if (!this.isTouchMode()) { 539 if (this.showTimeout) { 540 clearTimeout(this.showTimeout); 541 this.showTimeout = 0; 542 } 543 var self = this; 544 this.showTimeout = setTimeout(function() { self.itemActivate($a); }, this.opts.showOnClick && $a.closest('ul').dataSM('level') == 1 ? 1 : this.opts.showTimeout); 545 } 546 this.$root.triggerHandler('mouseenter.smapi', $a[0]); 547 }, 548 itemFocus: function(e) { 549 var $a = $(e.currentTarget); 550 if (!this.handleItemEvents($a)) { 551 return; 552 } 553 // 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 554 if (this.focusActivated && (!this.isTouchMode() || !$a.dataSM('mousedown')) && (!this.activatedItems.length || this.activatedItems[this.activatedItems.length - 1][0] != $a[0])) { 555 this.itemActivate($a, true); 556 } 557 this.$root.triggerHandler('focus.smapi', $a[0]); 558 }, 559 itemLeave: function(e) { 560 var $a = $(e.currentTarget); 561 if (!this.handleItemEvents($a)) { 562 return; 563 } 564 if (!this.isTouchMode()) { 565 $a[0].blur(); 566 if (this.showTimeout) { 567 clearTimeout(this.showTimeout); 568 this.showTimeout = 0; 569 } 570 } 571 $a.removeDataSM('mousedown');
572 this.$root.triggerHandler('mouseleave.smapi', $a[0]); 573 }, 574 menuHide: function($sub) { 575 if (this.$root.triggerHandler('beforehide.smapi', $sub[0]) === false) { 576 return; 577 } 578 if (canAnimate) { 579 $sub.stop(true, true); 580 } 581 if ($sub.css('display') != 'none') { 582 var complete = function() { 583 // unset z-index 584 $sub.css('z-index', ''); 585 }; 586 // if sub is collapsible (mobile view) 587 if (this.isCollapsible()) { 588 if (canAnimate && this.opts.collapsibleHideFunction) { 589 this.opts.collapsibleHideFunction.call(this, $sub, complete); 590 } else { 591 $sub.hide(this.opts.collapsibleHideDuration, complete); 592 } 593 } else { 594 if (canAnimate && this.opts.hideFunction) { 595 this.opts.hideFunction.call(this, $sub, complete); 596 } else { 597 $sub.hide(this.opts.hideDuration, complete); 598 } 599 } 600 // deactivate scrolling if it is activated for this sub 601 if ($sub.dataSM('scroll')) { 602 this.menuScrollStop($sub); 603 $sub.css({ 'touch-action': '', '-ms-touch-action': '', '-webkit-transform': '', transform: '' }) 604 .off('.smartmenus_scroll').removeDataSM('scroll').dataSM('scroll-arrows').hide(); 605 } 606 // unhighlight parent item + accessibility 607 $sub.dataSM('parent-a').removeClass('highlighted').attr('aria-expanded', 'false'); 608 $sub.attr({ 609 'aria-expanded': 'false', 610 'aria-hidden': 'true' 611 }); 612 var level = $sub.dataSM('level'); 613 this.activatedItems.splice(level - 1, 1); 614 this.visibleSubMenus.splice($.inArray($sub, this.visibleSubMenus), 1); 615 this.$root.triggerHandler('hide.smapi', $sub[0]); 616 } 617 }, 618 menuHideAll: function() { 619 if (this.showTimeout) { 620 clearTimeout(this.showTimeout); 621 this.showTimeout = 0; 622 } 623 // hide all subs 624 // if it's a popup, this.visibleSubMenus[0] is the root UL 625 var level = this.opts.isPopup ? 1 : 0; 626 for (var i = this.visibleSubMenus.length - 1; i >= level; i--) { 627 this.menuHide(this.visibleSubMenus[i]); 628 } 629 // hide root if it's popup 630 if (this.opts.isPopup) { 631 if (canAnimate) { 632 this.$root.stop(true, true); 633 } 634 if (this.$root.is(':visible')) { 635 if (canAnimate && this.opts.hideFunction) { 636 this.opts.hideFunction.call(this, this.$root); 637 } else { 638 this.$root.hide(this.opts.hideDuration); 639 } 640 } 641 } 642 this.activatedItems = []; 643 this.visibleSubMenus = []; 644 this.clickActivated = false; 645 this.focusActivated = false; 646 // reset z-index increment 647 this.zIndexInc = 0; 648 this.$root.triggerHandler('hideAll.smapi'); 649 }, 650 menuHideSubMenus: function(level) { 651 for (var i = this.activatedItems.length - 1; i >= level; i--) { 652 var $sub = this.activatedItems[i].dataSM('sub'); 653 if ($sub) { 654 this.menuHide($sub); 655 } 656 } 657 }, 658 menuInit: function($ul) { 659 if (!$ul.dataSM('in-mega')) { 660 // mark UL's in mega drop downs (if any) so we can neglect them 661 if ($ul.hasClass('mega-menu')) { 662 $ul.find('ul').dataSM('in-mega', true); 663 } 664 // get level (much faster than, for example, using parentsUntil) 665 var level = 2, 666 par = $ul[0]; 667 while ((par = par.parentNode.parentNode) != this.$root[0]) { 668 level++; 669 } 670 // cache stuff for quick access 671 var $a = $ul.prevAll('a').eq(-1); 672 // if the link is nested (e.g. in a heading) 673 if (!$a.length) { 674 $a = $ul.prevAll().find('a').eq(-1); 675 } 676 $a.addClass('has-submenu').dataSM('sub', $ul); 677 $ul.dataSM('parent-a', $a) 678 .dataSM('level', level) 679 .parent().dataSM('sub', $ul); 680 // accessibility 681 var aId = $a.attr('id') || this.accessIdPrefix + (++this.idInc), 682 ulId = $ul.attr('id') || this.accessIdPrefix + (++this.idInc); 683 $a.attr({ 684 id: aId, 685 'aria-haspopup': 'true', 686 'aria-controls': ulId, 687 'aria-expanded': 'false' 688 }); 689 $ul.attr({ 690 id: ulId, 691 'role': 'group', 692 'aria-hidden': 'true', 693 'aria-labelledby': aId, 694 'aria-expanded': 'false' 695 });
696 // add sub indicator to parent item 697 if (this.opts.subIndicators) { 698 $a[this.opts.subIndicatorsPos](this.$subArrow.clone()); 699 } 700 } 701 }, 702 menuPosition: function($sub) { 703 var $a = $sub.dataSM('parent-a'), 704 $li = $a.closest('li'), 705 $ul = $li.parent(), 706 level = $sub.dataSM('level'), 707 subW = this.getWidth($sub), 708 subH = this.getHeight($sub), 709 itemOffset = $a.offset(), 710 itemX = itemOffset.left, 711 itemY = itemOffset.top, 712 itemW = this.getWidth($a), 713 itemH = this.getHeight($a), 714 $win = $(window), 715 winX = $win.scrollLeft(), 716 winY = $win.scrollTop(), 717 winW = this.getViewportWidth(), 718 winH = this.getViewportHeight(), 719 horizontalParent = $ul.parent().is('[data-sm-horizontal-sub]') || level == 2 && !$ul.hasClass('sm-vertical'), 720 rightToLeft = this.opts.rightToLeftSubMenus && !$li.is('[data-sm-reverse]') || !this.opts.rightToLeftSubMenus && $li.is('[data-sm-reverse]'), 721 subOffsetX = level == 2 ? this.opts.mainMenuSubOffsetX : this.opts.subMenusSubOffsetX, 722 subOffsetY = level == 2 ? this.opts.mainMenuSubOffsetY : this.opts.subMenusSubOffsetY, 723 x, y; 724 if (horizontalParent) { 725 x = rightToLeft ? itemW - subW - subOffsetX : subOffsetX; 726 y = this.opts.bottomToTopSubMenus ? -subH - subOffsetY : itemH + subOffsetY; 727 } else { 728 x = rightToLeft ? subOffsetX - subW : itemW - subOffsetX; 729 y = this.opts.bottomToTopSubMenus ? itemH - subOffsetY - subH : subOffsetY; 730 } 731 if (this.opts.keepInViewport) { 732 var absX = itemX + x, 733 absY = itemY + y; 734 if (rightToLeft && absX < winX) { 735 x = horizontalParent ? winX - absX + x : itemW - subOffsetX; 736 } else if (!rightToLeft && absX + subW > winX + winW) { 737 x = horizontalParent ? winX + winW - subW - absX + x : subOffsetX - subW; 738 } 739 if (!horizontalParent) { 740 if (subH < winH && absY + subH > winY + winH) { 741 y += winY + winH - subH - absY; 742 } else if (subH >= winH || absY < winY) { 743 y += winY - absY; 744 } 745 } 746 // do we need scrolling? 747 // 0.49 used for better precision when dealing with float values 748 if (horizontalParent && (absY + subH > winY + winH + 0.49 || absY < winY) || !horizontalParent && subH > winH + 0.49) { 749 var self = this; 750 if (!$sub.dataSM('scroll-arrows')) { 751 $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]]) 752 .on({ 753 mouseenter: function() { 754 $sub.dataSM('scroll').up = $(this).hasClass('scroll-up'); 755 self.menuScroll($sub); 756 }, 757 mouseleave: function(e) { 758 self.menuScrollStop($sub); 759 self.menuScrollOut($sub, e); 760 }, 761 'mousewheel DOMMouseScroll': function(e) { e.preventDefault(); } 762 }) 763 .insertAfter($sub) 764 ); 765 } 766 // bind scroll events and save scroll data for this sub 767 var eNS = '.smartmenus_scroll'; 768 $sub.dataSM('scroll', { 769 y: this.cssTransforms3d ? 0 : y - itemH, 770 step: 1, 771 // cache stuff for faster recalcs later 772 itemH: itemH, 773 subH: subH, 774 arrowDownH: this.getHeight($sub.dataSM('scroll-arrows').eq(1)) 775 }) 776 .on(getEventsNS({ 777 'mouseover': function(e) { self.menuScrollOver($sub, e); }, 778 'mouseout': function(e) { self.menuScrollOut($sub, e); }, 779 'mousewheel DOMMouseScroll': function(e) { self.menuScrollMousewheel($sub, e); } 780 }, eNS)) 781 .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') }) 782 .eq(horizontalParent && this.opts.bottomToTopSubMenus ? 0 : 1).show(); 783 // when a menu tree is fixed positioned we allow scrolling via touch too 784 // since there is no other way to access such long sub menus if no mouse is present 785 if (this.isFixed()) { 786 var events = {}; 787 events[touchEvents ? 'touchstart touchmove touchend' : 'pointerdown pointermove pointerup MSPointerDown MSPointerMove MSPointerUp'] = function(e) { 788 self.menuScrollTouch($sub, e); 789 }; 790 $sub.css({ 'touch-action': 'none', '-ms-touch-action': 'none' }).on(getEventsNS(events, eNS)); 791 } 792 } 793 } 794 $sub.css({ top: 'auto', left: '0', marginLeft: x, marginTop: y - itemH }); 795 }, 796 menuScroll: function($sub, once, step) { 797 var data = $sub.dataSM('scroll'), 798 $arrows = $sub.dataSM('scroll-arrows'), 799 end = data.up ? data.upEnd : data.downEnd, 800 diff; 801 if (!once && data.momentum) { 802 data.momentum *= 0.92; 803 diff = data.momentum; 804 if (diff < 0.5) { 805 this.menuScrollStop($sub); 806 return; 807 } 808 } else { 809 diff = step || (once || !this.opts.scrollAccelerate ? this.opts.scrollStep : Math.floor(data.step)); 810 } 811 // hide any visible deeper level sub menus 812 var level = $sub.dataSM('level'); 813 if (this.activatedItems[level - 1] && this.activatedItems[level - 1].dataSM('sub') && this.activatedItems[level - 1].dataSM('sub').is(':visible')) {
814 this.menuHideSubMenus(level - 1); 815 } 816 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); 817 $sub.css(this.cssTransforms3d ? { '-webkit-transform': 'translate3d(0, ' + data.y + 'px, 0)', transform: 'translate3d(0, ' + data.y + 'px, 0)' } : { marginTop: data.y }); 818 // show opposite arrow if appropriate 819 if (mouse && (data.up && data.y > data.downEnd || !data.up && data.y < data.upEnd)) { 820 $arrows.eq(data.up ? 1 : 0).show(); 821 } 822 // if we've reached the end 823 if (data.y == end) { 824 if (mouse) { 825 $arrows.eq(data.up ? 0 : 1).hide(); 826 } 827 this.menuScrollStop($sub); 828 } else if (!once) { 829 if (this.opts.scrollAccelerate && data.step < this.opts.scrollStep) { 830 data.step += 0.2; 831 } 832 var self = this; 833 this.scrollTimeout = requestAnimationFrame(function() { self.menuScroll($sub); }); 834 } 835 }, 836 menuScrollMousewheel: function($sub, e) { 837 if (this.getClosestMenu(e.target) == $sub[0]) { 838 e = e.originalEvent; 839 var up = (e.wheelDelta || -e.detail) > 0; 840 if ($sub.dataSM('scroll-arrows').eq(up ? 0 : 1).is(':visible')) { 841 $sub.dataSM('scroll').up = up; 842 this.menuScroll($sub, true); 843 } 844 } 845 e.preventDefault(); 846 }, 847 menuScrollOut: function($sub, e) { 848 if (mouse) { 849 if (!/^scroll-(up|down)/.test((e.relatedTarget || '').className) && ($sub[0] != e.relatedTarget && !$.contains($sub[0], e.relatedTarget) || this.getClosestMenu(e.relatedTarget) != $sub[0])) { 850 $sub.dataSM('scroll-arrows').css('visibility', 'hidden'); 851 } 852 } 853 }, 854 menuScrollOver: function($sub, e) { 855 if (mouse) { 856 if (!/^scroll-(up|down)/.test(e.target.className) && this.getClosestMenu(e.target) == $sub[0]) { 857 this.menuScrollRefreshData($sub); 858 var data = $sub.dataSM('scroll'), 859 upEnd = $(window).scrollTop() - $sub.dataSM('parent-a').offset().top - data.itemH; 860 $sub.dataSM('scroll-arrows').eq(0).css('margin-top', upEnd).end() 861 .eq(1).css('margin-top', upEnd + this.getViewportHeight() - data.arrowDownH).end() 862 .css('visibility', 'visible'); 863 } 864 } 865 }, 866 menuScrollRefreshData: function($sub) { 867 var data = $sub.dataSM('scroll'), 868 upEnd = $(window).scrollTop() - $sub.dataSM('parent-a').offset().top - data.itemH; 869 if (this.cssTransforms3d) { 870 upEnd = -(parseFloat($sub.css('margin-top')) - upEnd); 871 } 872 $.extend(data, { 873 upEnd: upEnd, 874 downEnd: upEnd + this.getViewportHeight() - data.subH 875 }); 876 }, 877 menuScrollStop: function($sub) { 878 if (this.scrollTimeout) { 879 cancelAnimationFrame(this.scrollTimeout); 880 this.scrollTimeout = 0; 881 $sub.dataSM('scroll').step = 1; 882 return true; 883 } 884 }, 885 menuScrollTouch: function($sub, e) { 886 e = e.originalEvent; 887 if (isTouchEvent(e)) { 888 var touchPoint = this.getTouchPoint(e); 889 // neglect event if we touched a visible deeper level sub menu 890 if (this.getClosestMenu(touchPoint.target) == $sub[0]) { 891 var data = $sub.dataSM('scroll'); 892 if (/(start|down)$/i.test(e.type)) { 893 if (this.menuScrollStop($sub)) { 894 // if we were scrolling, just stop and don't activate any link on the first touch 895 e.preventDefault(); 896 this.$touchScrollingSub = $sub; 897 } else { 898 this.$touchScrollingSub = null; 899 } 900 // update scroll data since the user might have zoomed, etc. 901 this.menuScrollRefreshData($sub); 902 // extend it with the touch properties 903 $.extend(data, { 904 touchStartY: touchPoint.pageY, 905 touchStartTime: e.timeStamp 906 }); 907 } else if (/move$/i.test(e.type)) { 908 var prevY = data.touchY !== undefined ? data.touchY : data.touchStartY; 909 if (prevY !== undefined && prevY != touchPoint.pageY) { 910 this.$touchScrollingSub = $sub; 911 var up = prevY < touchPoint.pageY; 912 // changed direction? reset... 913 if (data.up !== undefined && data.up != up) { 914 $.extend(data, { 915 touchStartY: touchPoint.pageY, 916 touchStartTime: e.timeStamp 917 });
918 } 919 $.extend(data, { 920 up: up, 921 touchY: touchPoint.pageY 922 }); 923 this.menuScroll($sub, true, Math.abs(touchPoint.pageY - prevY)); 924 } 925 e.preventDefault(); 926 } else { // touchend/pointerup 927 if (data.touchY !== undefined) { 928 if (data.momentum = Math.pow(Math.abs(touchPoint.pageY - data.touchStartY) / (e.timeStamp - data.touchStartTime), 2) * 15) { 929 this.menuScrollStop($sub); 930 this.menuScroll($sub); 931 e.preventDefault(); 932 } 933 delete data.touchY; 934 } 935 } 936 } 937 } 938 }, 939 menuShow: function($sub) { 940 if (!$sub.dataSM('beforefirstshowfired')) { 941 $sub.dataSM('beforefirstshowfired', true); 942 if (this.$root.triggerHandler('beforefirstshow.smapi', $sub[0]) === false) { 943 return; 944 } 945 } 946 if (this.$root.triggerHandler('beforeshow.smapi', $sub[0]) === false) { 947 return; 948 } 949 $sub.dataSM('shown-before', true); 950 if (canAnimate) { 951 $sub.stop(true, true); 952 } 953 if (!$sub.is(':visible')) { 954 // highlight parent item 955 var $a = $sub.dataSM('parent-a'), 956 collapsible = this.isCollapsible(); 957 if (this.opts.keepHighlighted || collapsible) { 958 $a.addClass('highlighted'); 959 } 960 if (collapsible) { 961 $sub.removeClass('sm-nowrap').css({ zIndex: '', width: 'auto', minWidth: '', maxWidth: '', top: '', left: '', marginLeft: '', marginTop: '' }); 962 } else { 963 // set z-index 964 $sub.css('z-index', this.zIndexInc = (this.zIndexInc || this.get
964StartZIndex()) + 1); 965 // min/max-width fix - no way to rely purely on CSS as all UL's are nested 966 if (this.opts.subMenusMinWidth || this.opts.subMenusMaxWidth) { 967 $sub.css({ width: 'auto', minWidth: '', maxWidth: '' }).addClass('sm-nowrap'); 968 if (this.opts.subMenusMinWidth) { 969 $sub.css('min-width', this.opts.subMenusMinWidth); 970 } 971 if (this.opts.subMenusMaxWidth) { 972 var noMaxWidth = this.getWidth($sub); 973 $sub.css('max-width', this.opts.subMenusMaxWidth); 974 if (noMaxWidth > this.getWidth($sub)) { 975 $sub.removeClass('sm-nowrap').css('width', this.opts.subMenusMaxWidth); 976 } 977 } 978 } 979 this.menuPosition($sub); 980 } 981 var complete = function() { 982 // fix: "overflow: hidden;" is not reset on animation complete in jQuery < 1.9.0 in Chrome when global "box-sizing: border-box;" is used 983 $sub.css('overflow', ''); 984 }; 985 // if sub is collapsible (mobile view) 986 if (collapsible) { 987 if (canAnimate && this.opts.collapsibleShowFunction) { 988 this.opts.collapsibleShowFunction.call(this, $sub, complete); 989 } else { 990 $sub.show(this.opts.collapsibleShowDuration, complete); 991 } 992 } else { 993 if (canAnimate && this.opts.showFunction) { 994 this.opts.showFunction.call(this, $sub, complete); 995 } else { 996 $sub.show(this.opts.showDuration, complete); 997 } 998 } 999 // accessibility 1000 $a.attr('aria-expanded', 'true'); 1001 $sub.attr({ 1002 'aria-expanded': 'true', 1003 'aria-hidden': 'false' 1004 }); 1005 // store sub menu in visible array 1006 this.visibleSubMenus.push($sub); 1007 this.$root.triggerHandler('show.smapi', $sub[0]); 1008 } 1009 }, 1010 popupHide: function(noHideTimeout) { 1011 if (this.hideTimeout) { 1012 clearTimeout(this.hideTimeout); 1013 this.hideTimeout = 0; 1014 } 1015 var self = this; 1016 this.hideTimeout = setTimeout(function() { 1017 self.menuHideAll(); 1018 }, noHideTimeout ? 1 : this.opts.hideTimeout); 1019 }, 1020 popupShow: function(left, top) { 1021 if (!this.opts.isPopup) { 1022 alert('SmartMenus jQuery Error:\n\nIf you want to show this menu via the "popupShow" method, set the isPopup:true option.'); 1023 return; 1024 } 1025 if (this.hideTimeout) { 1026 clearTimeout(this.hideTimeout); 1027 this.hideTimeout = 0; 1028 } 1029 this.$root.dataSM('shown-before', true); 1030 if (canAnimate) { 1031 this.$root.stop(true, true); 1032 } 1033 if (!this.$root.is(':visible')) { 1034 this.$root.css({ left: left, top: top }); 1035 // show menu 1036 var self = this, 1037 complete = function() { 1038 self.$root.css('overflow', ''); 1039 }; 1040 if (canAnimate && this.opts.showFunction) { 1041 this.opts.showFunction.call(this, this.$root, complete); 1042 } else { 1043 this.$root.show(this.opts.showDuration, complete); 1044 } 1045 this.visibleSubMenus[0] = this.$root; 1046 } 1047 }, 1048 refresh: function() { 1049 this.destroy(true); 1050 this.init(true); 1051 }, 1052 rootKeyDown: function(e) { 1053 if (!this.handleEvents()) { 1054 return; 1055 } 1056 switch (e.keyCode) { 1057 case 27: // reset on Esc 1058 var $activeTopItem = this.activatedItems[0]; 1059 if ($activeTopItem) { 1060 this.menuHideAll(); 1061 $activeTopItem[0].focus(); 1062 var $sub = $activeTopItem.dataSM('sub'); 1063 if ($sub) { 1064 this.menuHide($sub); 1065 } 1066 } 1067 break; 1068 case 32: // activate item's sub on Space 1069 var $target = $(e.target); 1070 if ($target.is('a') && this.handleItemEvents($target)) { 1071 var $sub = $target.dataSM('sub'); 1072 if ($sub && !$sub.is(':visible')) { 1073 this.itemClick({ currentTarget: e.target }); 1074 e.preventDefault(); 1075 } 1076 } 1077 break; 1078 } 1079 }, 1080 rootOut: function(e) { 1081 if (!this.handleEvents() || this.isTouchMode() || e.target == this.$root[0]) { 1082 return; 1083 } 1084 if (this.hideTimeout) { 1085 clearTimeout(this.hideTimeout); 1086 this.hideTimeout = 0; 1087 } 1088 if (!this.opts.showOnClick || !this.opts.hideOnClick) { 1089 var self = this; 1090 this.hideTimeout = setTimeout(function() { self.menuHideAll(); }, this.opts.hideTimeout); 1091 } 1092 }, 1093 rootOver: function(e) { 1094 if (!this.handleEvents() || this.isTouchMode() || e.target == this.$root[0]) { 1095 return; 1096 } 1097 if (this.hideTimeout) { 1098 clearTimeout(this.hideTimeout); 1099 this.hideTimeout = 0; 1100 } 1101 }, 1102 winResize: function(e) { 1103 if (!this.handleEvents()) { 1104 // we still need to resize the disable overlay if it's visible 1105 if (this.$disableOverlay) { 1106 var pos = this.$root.offset(); 1107 this.$disableOverlay.css({ 1108 top: pos.top, 1109 left: pos.left, 1110 width: this.$root.outerWidth(), 1111 height: this.$root.outerHeight() 1112 });
1113 } 1114 return; 1115 } 1116 // hide sub menus on resize - on mobile do it only on orientation change 1117 if (!('onorientationchange' in window) || e.type == 'orientationchange') { 1118 var collapsible = this.isCollapsible(); 1119 // if it was collapsible before resize and still is, don't do it 1120 if (!(this.wasCollapsible && collapsible)) { 1121 if (this.activatedItems.length) { 1122 this.activatedItems[this.activatedItems.length - 1][0].blur(); 1123 } 1124 this.menuHideAll(); 1125 } 1126 this.wasCollapsible = collapsible; 1127 } 1128 } 1129 } 1130 }); 1131 1132 $.fn.dataSM = function(key, val) { 1133 if (val) { 1134 return this.data(key + '_smartmenus', val); 1135 } 1136 return this.data(key + '_smartmenus'); 1137 }; 1138 1139 $.fn.removeDataSM = function(key) { 1140 return this.removeData(key + '_smartmenus'); 1141 }; 1142 1143 $.fn.smartmenus = function(options) { 1144 if (typeof options == 'string') { 1145 var args = arguments, 1146 method = options; 1147 Array.prototype.shift.call(args); 1148 return this.each(function() { 1149 var smartmenus = $(this).data('smartmenus'); 1150 if (smartmenus && smartmenus[method]) { 1151 smartmenus[method].apply(smartmenus, args); 1152 } 1153 }); 1154 } 1155 return this.each(function() { 1156 // [data-sm-options] attribute on the root UL 1157 var dataOpts = $(this).data('sm-options') || null; 1158 if (dataOpts) { 1159 try { 1160 dataOpts = eval('(' + dataOpts + ')'); 1161 } catch(e) { 1162 dataOpts = null; 1163 alert('ERROR\n\nSmartMenus jQuery init:\nInvalid "data-sm-options" attribute value syntax.'); 1164 }; 1165 } 1166 new $.SmartMenus(this, $.extend({}, $.fn.smartmenus.defaults, options, dataOpts)); 1167 }); 1168 }; 1169 1170 // default settings 1171 $.fn.smartmenus.defaults = { 1172 isPopup: false, // is this a popup menu (can be shown via the popupShow/popupHide methods) or a permanent menu bar 1173 mainMenuSubOffsetX: 0, // pixels offset from default position 1174 mainMenuSubOffsetY: 0, // pixels offset from default position 1175 subMenusSubOffsetX: 0, // pixels offset from default position 1176 subMenusSubOffsetY: 0, // pixels offset from default position 1177 //subMenusMinWidth: '10em', // min-width for the sub menus (any CSS unit) - if set, the fixed width set in CSS will be ignored //MinWidth: 10em 1178 //subMenusMaxWidth: '20em', // max-width for the sub menus (any CSS unit) - if set, the fixed width set in CSS will be ignored //MaxWidth: 20em 1179 subIndicators: true, // create sub menu indicators - creates a SPAN and inserts it in the A 1180 subIndicatorsPos: 'append', // position of the SPAN relative to the menu item content ('append', 'prepend') 1181 subIndicatorsText: '', // [optionally] add text in the SPAN (e.g. '+') (you may want to check the CSS for the sub indicators too) 1182 scrollStep: 30, // pixels step when scrolling long sub menus that do not fit in the viewport height 1183 scrollAccelerate: true, // accelerate scrolling or use a fixed step 1184 showTimeout: 250, // timeout before showing the sub menus 1185 hideTimeout: 500, // timeout before hiding the sub menus 1186 showDuration: 0, // duration for show animation - set to 0 for no animation - matters only if showFunction:null 1187 showFunction: null, // custom function to use when showing a sub menu (the default is the jQuery 'show') 1188 // don't forget to call complete() at the end of whatever you do 1189 // e.g.: function($ul, complete) { $ul.fadeIn(250, complete); } 1190 hideDuration: 0, // duration for hide animation - set to 0 for no animation - matters only if hideFunction:null 1191 hideFunction: function($ul, complete) { $ul.fadeOut(200, complete); }, // custom function to use when hiding a sub menu (the default is the jQuery 'hide') 1192 // don't forget to call complete() at the end of whatever you do 1193 // e.g.: function($ul, complete) { $ul.fadeOut(250, complete); } 1194 collapsibleShowDuration:0, // duration for show animation for collapsible sub menus - matters only if collapsibleShowFunction:null 1195 collapsibleShowFunction:function($ul, complete) { $ul.slideDown(200, complete); }, // custom function to use when showing a collapsible sub menu 1196 // (i.e. when mobile styles are used to make the sub menus collapsible) 1197 collapsibleHideDuration:0, // duration for hide animation for collapsible sub menus - matters only if c
1197ollapsibleHideFunction:null 1198 collapsibleHideFunction:function($ul, complete) { $ul.slideUp(200, complete); }, // custom function to use when hiding a collapsible sub menu 1199 // (i.e. when mobile styles are used to make the sub menus collapsible) 1200 showOnClick: false, // show the first-level sub menus onclick instead of onmouseover (i.e. mimic desktop app menus) (matters only for mouse input) 1201 hideOnClick: true, // hide the sub menus on click/tap anywhere on the page 1202 noMouseOver: false, // disable sub menus activation onmouseover (i.e. behave like in touch mode - use just mouse clicks) (matters only for mouse input) 1203 keepInViewport: true, // reposition the sub menus if needed to make sure they always appear inside the viewport 1204 keepHighlighted: true, // keep all ancestor items of the current sub menu highlighted (adds the 'highlighted' class to the A's) 1205 markCurrentItem: false, // automatically add the 'current' class to the A element of the item linking to the current URL 1206 markCurrentTree: true, // add the 'current' class also to the A elements of all ancestor items of the current item 1207 rightToLeftSubMenus: false, // right to left display of the sub menus (check the CSS for the sub indicators' position) 1208 bottomToTopSubMenus: false, // bottom to top display of the sub menus 1209 collapsibleBehavior: 'default' // parent items behavior in collapsible (mobile) view ('default', 'toggle', 'link', 'accordion', 'accordion-toggle', 'accordion-link') 1210 // 'default' - first tap on parent item expands sub, second tap loads its link 1211 // 'toggle' - the whole parent item acts just as a toggle button for its sub menu (expands/collapses on each tap) 1212 // 'link' - the parent item acts as a regular item (first tap loads its link), the sub menu can be expanded only via the +/- button 1213 // 'accordion' - like 'default' but on expand also resets any visible sub menus from deeper levels or other branches 1214 // 'accordion-toggle' - like 'toggle' but on expand also resets any visible sub menus from deeper levels or other branches 1215 // 'accordion-link' - like 'link' but on expand also resets any visible sub menus from deeper levels or other branches 1216 }; 1217 1218 return $; 1219}));
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.