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