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