1/*! 2 * SmartMenus jQuery Plugin - v1.0.1 - November 1, 2016 3 * http://www.smartmenus.org/ 4 * 5 * Copyright Vasil Dinkov, Vadikom Web Ltd. 6 * http://vadikom.com 7 * 8 * Licensed MIT 9 */ 10 11(function (factory) { 12 if (typeof define === 'function' && define.amd) { 13 // AMD 14 define(['jquery'], factory); 15 } else if (typeof module === 'object' && typeof module.exports === 'object') { 16 // CommonJS 17 module.exports = factory(require('jquery')); 18 } else { 19 // Global jQuery 20 factory(jQuery); 21 } 22}(function ($) { 23 24 var menuTrees = [], 25 IE = !!window.createPopup, // detect it for the iframe shim 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 32 // Handle detection for mouse input (i.e. desktop browsers, tablets with a mouse, etc.) 33 function initMouseDetection(disable) { 34 var eNS = '.smartmenus_mouse'; 35 if (!mouseDetectionEnabled && !disable) { 36 // if we get two consecutive mousemoves within 2 pixels from each other and within 300ms, we assume a real mouse/cursor is present 37 // in practice, this seems like impossible to trick unintentianally with a real mouse and a pretty safe detection on touch devices (even with older browsers that do not support touch events) 38 var firstTime = true, 39 lastMove = null; 40 $(document).bind(getEventsNS([ 41 ['mousemove', function (e) { 42 var thisMove = { x: e.pageX, y: e.pageY, timeStamp: new Date().getTime() }; 43 if (lastMove) { 44 var deltaX = Math.abs(lastMove.x - thisMove.x), 45 deltaY = Math.abs(lastMove.y - thisMove.y); 46 if ((deltaX > 0 || deltaY > 0) && deltaX <= 2 && deltaY <= 2 && thisMove.timeStamp - lastMove.timeStamp <= 300) { 47 mouse = true; 48 // if this is the first check after page load, check if we are not over some item by chance and call the mouseenter handler if yes 49 if (firstTime) { 50 var $a = $(e.target).closest('a'); 51 if ($a.is('a')) { 52 $.each(menuTrees, function () { 53 if ($.contains(this.$root[0], $a[0])) { 54 this.itemEnter({ currentTarget: $a[0] }); 55 return false; 56 } 57 }); 58 } 59 firstTime = false; 60 } 61 } 62 } 63 lastMove = thisMove; 64 }], 65 [touchEvents ? 'touchstart' : 'pointerover pointermove pointerout MSPointerOver MSPointerMove MSPointerOut', function (e) { 66 if (isTouchEvent(e.originalEvent)) { 67 mouse = false; 68 } 69 }] 70 ], eNS)); 71 mouseDetectionEnabled = true; 72 } else if (mouseDetectionEnabled && disable) { 73 $(document).unbind(eNS); 74 mouseDetectionEnabled = false; 75 } 76 } 77 78 function isTouchEvent(e) { 79 return !/^(4|mouse)$/.test(e.pointerType); 80 } 81 82 // returns a jQuery bind() ready object 83 function getEventsNS(defArr, eNS) { 84 if (!eNS) { 85 eNS = ''; 86 } 87 var obj = {}; 88 $.each(defArr, function (index, value) { 89 obj[value[0].split(' ').join(eNS + ' ') + eNS] = value[1]; 90 }); 91 return obj; 92 } 93 94 $.SmartMenus = function (elm, options) { 95 this.$root = $(elm); 96 this.opts = options; 97 this.rootId = ''; // internal 98 this.accessIdPrefix = ''; 99 this.$subArrow = null; 100 this.activatedItems = []; // stores last activated A's for each level 101 this.visibleSubMenus = []; // stores visible sub menus UL's (might be in no particular order) 102 this.showTimeout = 0; 103 this.hideTimeout = 0; 104 this.scrollTimeout = 0; 105 this.clickActivated = false; 106 this.focusActivated = false; 107 this.zIndexInc = 0; 108 this.idInc = 0; 109 this.$firstLink = null; // we'll use these for some tests 110 this.$firstSub = null; // at runtime so we'll cache them 111 this.disabled = false; 112 this.$disableOverlay = null; 113 this.$touchScrollingSub = null; 114 this.cssTransforms3d = 'perspective' in elm.style || 'webkitPerspective' in elm.style; 115 this.wasCollapsible = false;
116 this.init(); 117 }; 118 119 $.extend($.SmartMenus, { 120 hideAll: function () { 121 $.each(menuTrees, function () { 122 this.menuHideAll(); 123 }); 124 }, 125 destroy: function () { 126 while (menuTrees.length) { 127 menuTrees[0].destroy(); 128 } 129 initMouseDetection(true); 130 }, 131 prototype: { 132 init: function (refresh) { 133 var self = this; 134 135 if (!refresh) { 136 menuTrees.push(this); 137 138 this.rootId = (new Date().getTime() + Math.random() + '').replace(/\D/g, ''); 139 this.accessIdPrefix = 'sm-' + this.rootId + '-'; 140 141 if (this.$root.hasClass('sm-rtl')) { 142 this.opts.rightToLeftSubMenus = true; 143 } 144 145 // init root (main menu) 146 var eNS = '.smartmenus'; 147 this.$root 148 .data('smartmenus', this) 149 .attr('data-smartmenus-id', this.rootId) 150 .dataSM('level', 1) 151 .bind(getEventsNS([ 152 ['mouseover focusin', $.proxy(this.rootOver, this)], 153 ['mouseout focusout', $.proxy(this.rootOut, this)], 154 ['keydown', $.proxy(this.rootKeyDown, this)] 155 ], eNS)) 156 .delegate('a', getEventsNS([ 157 ['mouseenter', $.proxy(this.itemEnter, this)], 158 ['mouseleave', $.proxy(this.itemLeave, this)], 159 ['mousedown', $.proxy(this.itemDown, this)], 160 ['focus', $.proxy(this.itemFocus, this)], 161 ['blur', $.proxy(this.itemBlur, this)], 162 ['click', $.proxy(this.itemClick, this)] 163 ], eNS)); 164 165 // hide menus on tap or click outside the root UL 166 eNS += this.rootId; 167 if (this.opts.hideOnClick) { 168 $(document).bind(getEventsNS([ 169 ['touchstart', $.proxy(this.docTouchStart, this)], 170 ['touchmove', $.proxy(this.docTouchMove, this)], 171 ['touchend', $.proxy(this.docTouchEnd, this)], 172 // for Opera Mobile < 11.5, webOS browser, etc. we'll check click too 173 ['click', $.proxy(this.docClick, this)] 174 ], eNS)); 175 } 176 // hide sub menus on resize 177 $(window).bind(getEventsNS([['resize orientationchange', $.proxy(this.winResize, this)]], eNS)); 178 179 if (this.opts.subIndicators) { 180 this.$subArrow = $('<span/>').addClass('sub-arrow'); 181 if (this.opts.subIndicatorsText) { 182 this.$subArrow.html(this.opts.subIndicatorsText); 183 } 184 } 185 186 // make sure mouse detection is enabled 187 initMouseDetection(); 188 } 189 190 // init sub menus 191 this.$firstSub = this.$root.find('ul').each(function () { self.menuInit($(this)); }).eq(0); 192 193 this.$firstLink = this.$root.find('a').eq(0); 194 195 // find current item 196 if (this.opts.markCurrentItem) { 197 var reDefaultDoc = /(index|default)\.[^#\?\/]*/i, 198 reHash = /#.*/, 199 locHref = window.location.href.replace(reDefaultDoc, ''), 200 locHrefNoHash = locHref.replace(reHash, ''); 201 this.$root.find('a').each(function () { 202 var href = this.href.replace(reDefaultDoc, ''), 203 $this = $(this); 204 if (href == locHref || href == locHrefNoHash) { 205 $this.addClass('current'); 206 if (self.opts.markCurrentTree) { 207 $this.parentsUntil('[data-smartmenus-id]', 'ul').each(function () { 208 $(this).dataSM('parent-a').addClass('current'); 209 }); 210 } 211 } 212 }); 213 } 214 215 // save initial state 216 this.wasCollapsible = this.isCollapsible(); 217 }, 218 destroy: function (refresh) { 219 if (!refresh) { 220 var eNS = '.smartmenus'; 221 this.$root 222 .removeData('smartmenus') 223 .removeAttr('data-smartmenus-id') 224 .removeDataSM('level') 225 .unbind(eNS) 226 .undelegate(eNS); 227 eNS += this.rootId; 228 $(document).unbind(eNS); 229 $(window).unbind(eNS); 230 if (this.opts.subIndicators) { 231 this.$subArrow = null; 232 } 233 } 234 this.menuHideAll(); 235 var self = this; 236 this.$root.find('ul').each(function () { 237 var $this = $(this); 238 if ($this.dataSM('scroll-arrows')) { 239 $this.dataSM('scroll-arrows').remove(); 240 } 241 if ($this.dataSM('shown-before')) { 242 if (self.opts.subMenusMinWidth || self.opts.subMenusMaxWidth) { 243 $this.css({ width: '', minWidth: '', maxWidth: '' }).removeClass('sm-nowrap'); 244 } 245 if ($this.dataSM('scroll-arrows')) { 246 $this.dataSM('scroll-arrows').remove(); 247 } 248 $this.css({ zIndex: '', top: '', left: '', marginLeft: '', marginTop: '', display: '' }); 249 } 250 if (($this.attr('id') || '').indexOf(self.accessIdPrefix) == 0) { 251 $this.removeAttr('id'); 252 } 253 }) 254 .removeDataSM('in-mega') 255 .removeDataSM('shown-before') 256 .removeDataSM('ie-shim') 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).is('a')) { 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') == 'block'; 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, focus) { 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() || focus) { 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 subArrowClicked = $(e.target).is('span.sub-arrow'), 490 $sub = $a.dataSM('sub'), 491 firstLevelSub = $sub ? $sub.dataSM('level') == 2 : false; 492 // if the sub is not visible 493 if ($sub && !$sub.is(':visible')) { 494 if (this.opts.showOnClick && firstLevelSub) { 495 this.clickActivated = true; 496 } 497 // try to activate the item and show the sub 498 this.itemActivate($a); 499 // if "itemActivate" showed the sub, prevent the click so that the link is not loaded 500 // 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 501 if ($sub.is(':visible')) { 502 this.focusActivated = true; 503 return false; 504 } 505 } else if (this.isCollapsible() && subArrowClicked) { 506 this.itemActivate($a); 507 this.menuHide($sub); 508 return false; 509 } 510 if (this.opts.showOnClick && firstLevelSub || $a.hasClass('disabled') || this.$root.triggerHandler('select.smapi', $a[0]) === false) { 511 return false; 512 } 513 }, 514 itemDown: function (e) { 515 var $a = $(e.currentTarget); 516 if (!this.handleItemEvents($a)) { 517 return; 518 } 519 $a.dataSM('mousedown', true); 520 }, 521 itemEnter: function (e) { 522 var $a = $(e.currentTarget); 523 if (!this.handleItemEvents($a)) { 524 return; 525 } 526 if (!this.isTouchMode()) { 527 if (this.showTimeout) { 528 clearTimeout(this.showTimeout); 529 this.showTimeout = 0; 530 } 531 var self = this; 532 this.showTimeout = setTimeout(function () { self.itemActivate($a); }, this.opts.showOnClick && $a.closest('ul').dataSM('level') == 1 ? 1 : this.opts.showTimeout); 533 } 534 this.$root.triggerHandler('mouseenter.smapi', $a[0]); 535 }, 536 itemFocus: function (e) { 537 var $a = $(e.currentTarget); 538 if (!this.handleItemEvents($a)) { 539 return; 540 } 541 // 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 542 if (this.focusActivated && (!this.isTouchMode() || !$a.dataSM('mousedown')) && (!this.activatedItems.length || this.activatedItems[this.activatedItems.length - 1][0] != $a[0])) { 543 this.itemActivate($a, true); 544 } 545 this.$root.triggerHandler('focus.smapi', $a[0]); 546 }, 547 itemLeave: function (e) { 548 var $a = $(e.currentTarget); 549 if (!this.handleItemEvents($a)) { 550 return; 551 } 552 if (!this.isTouchMode()) { 553 $a[0].blur(); 554 if (this.showTimeout) { 555 clearTimeout(this.showTimeout); 556 this.showTimeout = 0; 557 } 558 } 559 $a.removeDataSM('mousedown');
560 this.$root.triggerHandler('mouseleave.smapi', $a[0]); 561 }, 562 menuHide: function ($sub) { 563 if (this.$root.triggerHandler('beforehide.smapi', $sub[0]) === false) { 564 return; 565 } 566 $sub.stop(true, true); 567 if ($sub.css('display') != 'none') { 568 var complete = function () { 569 // unset z-index 570 $sub.css('z-index', ''); 571 }; 572 // if sub is collapsible (mobile view) 573 if (this.isCollapsible()) { 574 if (this.opts.collapsibleHideFunction) { 575 this.opts.collapsibleHideFunction.call(this, $sub, complete); 576 } else { 577 $sub.hide(this.opts.collapsibleHideDuration, complete); 578 } 579 } else { 580 if (this.opts.hideFunction) { 581 this.opts.hideFunction.call(this, $sub, complete); 582 } else { 583 $sub.hide(this.opts.hideDuration, complete); 584 } 585 } 586 // remove IE iframe shim 587 if ($sub.dataSM('ie-shim')) { 588 $sub.dataSM('ie-shim').remove().css({ '-webkit-transform': '', transform: '' }); 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 .unbind('.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 this.$root.stop(true, true); 622 if (this.$root.is(':visible')) { 623 if (this.opts.hideFunction) { 624 this.opts.hideFunction.call(this, this.$root); 625 } else { 626 this.$root.hide(this.opts.hideDuration); 627 } 628 // remove IE iframe shim 629 if (this.$root.dataSM('ie-shim')) { 630 this.$root.dataSM('ie-shim').remove(); 631 } 632 } 633 } 634 this.activatedItems = []; 635 this.visibleSubMenus = []; 636 this.clickActivated = false; 637 this.focusActivated = false; 638 // reset z-index increment 639 this.zIndexInc = 0; 640 this.$root.triggerHandler('hideAll.smapi'); 641 }, 642 menuHideSubMenus: function (level) { 643 for (var i = this.activatedItems.length - 1; i >= level; i--) { 644 var $sub = this.activatedItems[i].dataSM('sub'); 645 if ($sub) { 646 this.menuHide($sub); 647 } 648 } 649 }, 650 menuIframeShim: function ($ul) { 651 // create iframe shim for the menu 652 if (IE && this.opts.overlapControlsInIE && !$ul.dataSM('ie-shim')) { 653 $ul.dataSM('ie-shim', $('<iframe/>').attr({ src: 'javascript:0', tabindex: -9 }) 654 .css({ position: 'absolute', top: 'auto', left: '0', opacity: 0, border: '0' }) 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 .bind({ 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 .bind(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 $sub.css({ 'touch-action': 'none', '-ms-touch-action': 'none' }) 787 .bind(getEventsNS([ 788 [touchEvents ? 'touchstart touchmove touchend' : 'pointerdown pointermove pointerup MSPointerDown MSPointerMove MSPointerUp', function (e) { 789 self.menuScrollTouch($sub, e); 790 }] 791 ], eNS)); 792 } 793 } 794 } 795 $sub.css({ top: 'auto', left: '0', marginLeft: x, marginTop: y - itemH }); 796 // IE iframe shim 797 this.menuIframeShim($sub); 798 if ($sub.dataSM('ie-shim')) { 799 $sub.dataSM('ie-shim').css({ zIndex: $sub.css('z-index'), width: subW, height: subH, marginLeft: x, marginTop: y - itemH }); 800 } 801 }, 802 menuScroll: function ($sub, once, step) { 803 var data = $sub.dataSM('scroll'), 804 $arrows = $sub.dataSM('scroll-arrows'), 805 end = data.up ? data.upEnd : data.downEnd, 806 diff; 807 if (!once && data.momentum) { 808 data.momentum *= 0.92; 809 diff = data.momentum; 810 if (diff < 0.5) { 811 this.menuScrollStop($sub); 812 return; 813 } 814 } else { 815 diff = step || (once || !this.opts.scrollAccelerate ? this.opts.scrollStep : Math.floor(data.step)); 816 } 817 // hide any visible deeper level sub menus 818 var level = $sub.dataSM('level'); 819 if (this.activatedItems[level - 1] && this.activatedItems[level - 1].dataSM('sub') && this.activatedItems[level - 1].dataSM('sub').is(':visible')) {
820 this.menuHideSubMenus(level - 1); 821 } 822 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); 823 $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 }); 824 // show opposite arrow if appropriate 825 if (mouse && (data.up && data.y > data.downEnd || !data.up && data.y < data.upEnd)) { 826 $arrows.eq(data.up ? 1 : 0).show(); 827 } 828 // if we've reached the end 829 if (data.y == end) { 830 if (mouse) { 831 $arrows.eq(data.up ? 0 : 1).hide(); 832 } 833 this.menuScrollStop($sub); 834 } else if (!once) { 835 if (this.opts.scrollAccelerate && data.step < this.opts.scrollStep) { 836 data.step += 0.2; 837 } 838 var self = this; 839 this.scrollTimeout = requestAnimationFrame(function () { self.menuScroll($sub); }); 840 } 841 }, 842 menuScrollMousewheel: function ($sub, e) { 843 if (this.getClosestMenu(e.target) == $sub[0]) { 844 e = e.originalEvent; 845 var up = (e.wheelDelta || -e.detail) > 0; 846 if ($sub.dataSM('scroll-arrows').eq(up ? 0 : 1).is(':visible')) { 847 $sub.dataSM('scroll').up = up; 848 this.menuScroll($sub, true); 849 } 850 } 851 e.preventDefault(); 852 }, 853 menuScrollOut: function ($sub, e) { 854 if (mouse) { 855 if (!/^scroll-(up|down)/.test((e.relatedTarget || '').className) && ($sub[0] != e.relatedTarget && !$.contains($sub[0], e.relatedTarget) || this.getClosestMenu(e.relatedTarget) != $sub[0])) { 856 $sub.dataSM('scroll-arrows').css('visibility', 'hidden'); 857 } 858 } 859 }, 860 menuScrollOver: function ($sub, e) { 861 if (mouse) { 862 if (!/^scroll-(up|down)/.test(e.target.className) && this.getClosestMenu(e.target) == $sub[0]) { 863 this.menuScrollRefreshData($sub); 864 var data = $sub.dataSM('scroll'), 865 upEnd = $(window).scrollTop() - $sub.dataSM('parent-a').offset().top - data.itemH; 866 $sub.dataSM('scroll-arrows').eq(0).css('margin-top', upEnd).end() 867 .eq(1).css('margin-top', upEnd + this.getViewportHeight() - data.arrowDownH).end() 868 .css('visibility', 'visible'); 869 } 870 } 871 }, 872 menuScrollRefreshData: function ($sub) { 873 var data = $sub.dataSM('scroll'), 874 upEnd = $(window).scrollTop() - $sub.dataSM('parent-a').offset().top - data.itemH; 875 if (this.cssTransforms3d) { 876 upEnd = -(parseFloat($sub.css('margin-top')) - upEnd); 877 } 878 $.extend(data, { 879 upEnd: upEnd, 880 downEnd: upEnd + this.getViewportHeight() - data.subH 881 }); 882 }, 883 menuScrollStop: function ($sub) { 884 if (this.scrollTimeout) { 885 cancelAnimationFrame(this.scrollTimeout); 886 this.scrollTimeout = 0; 887 $sub.dataSM('scroll').step = 1; 888 return true; 889 } 890 }, 891 menuScrollTouch: function ($sub, e) { 892 e = e.originalEvent; 893 if (isTouchEvent(e)) { 894 var touchPoint = this.getTouchPoint(e); 895 // neglect event if we touched a visible deeper level sub menu 896 if (this.getClosestMenu(touchPoint.target) == $sub[0]) { 897 var data = $sub.dataSM('scroll'); 898 if (/(start|down)$/i.test(e.type)) { 899 if (this.menuScrollStop($sub)) { 900 // if we were scrolling, just stop and don't activate any link on the first touch 901 e.preventDefault(); 902 this.$touchScrollingSub = $sub; 903 } else { 904 this.$touchScrollingSub = null; 905 } 906 // update scroll data since the user might have zoomed, etc. 907 this.menuScrollRefreshData($sub); 908 // extend it with the touch properties 909 $.extend(data, { 910 touchStartY: touchPoint.pageY, 911 touchStartTime: e.timeStamp 912 }); 913 } else if (/move$/i.test(e.type)) { 914 var prevY = data.touchY !== undefined ? data.touchY : data.touchStartY; 915 if (prevY !== undefined && prevY != touchPoint.pageY) { 916 this.$touchScrollingSub = $sub; 917 var up = prevY < touchPoint.pageY; 918 // changed direction? reset... 919 if (data.up !== undefined && data.up != up) { 920 $.extend(data, { 921 touchStartY: touchPoint.pageY, 922 touchStartTime: e.timeStamp 923 }); 924 } 925 $.extend(data, { 926 up: up, 927 touchY: touchPoint.pageY 928 }); 929 this.menuScroll($sub, true, Math.abs(touchPoint.pageY - prevY)); 930 } 931 e.preventDefault(); 932 } else { // touchend/pointerup 933 if (data.touchY !== undefined) { 934 if (data.momentum = Math.pow(Math.abs(touchPoint.pageY - data.touchStartY) / (e.timeStamp - data.touchStartTime), 2) * 15) { 935 this.menuScrollStop($sub); 936 this.menuScroll($sub); 937 e.preventDefault(); 938 } 939 delete data.touchY; 940 } 941 } 942 } 943 } 944 }, 945 menuShow: function ($sub) { 946 if (!$sub.dataSM('beforefirstshowfired')) { 947 $sub.dataSM('beforefirstshowfired', true); 948 if (this.$root.triggerHandler('beforefirstshow.smapi', $sub[0]) === false) { 949 return; 950 } 951 } 952 if (this.$root.triggerHandler('beforeshow.smapi', $sub[0]) === false) { 953 return; 954 } 955 $sub.dataSM('shown-before', true) 956 .stop(true, true); 957 if (!$sub.is(':visible')) { 958 // highlight parent item 959 var $a = $sub.dataSM('parent-a'); 960 if (this.opts.keepHighlighted || this.isCollapsible()) { 961 $a.addClass('highlighted'); 962 } 963 if (this.isCollapsible()) { 964 $sub.removeClass('sm-nowrap').css({ zIndex: '', width: 'auto', minWidth: '', maxWidth: '', top: '', left: '', marginLeft: '', marginTop: '' }); 965 } else { 966 // set z-index 967 $sub.css('z-index', this.zIndexInc = (this.zIndexInc || this.get
967StartZIndex()) + 1); 968 // min/max-width fix - no way to rely purely on CSS as all UL's are nested 969 if (this.opts.subMenusMinWidth || this.opts.subMenusMaxWidth) { 970 $sub.css({ width: 'auto', minWidth: '', maxWidth: '' }).addClass('sm-nowrap'); 971 if (this.opts.subMenusMinWidth) { 972 $sub.css('min-width', this.opts.subMenusMinWidth); 973 } 974 if (this.opts.subMenusMaxWidth) { 975 var noMaxWidth = this.getWidth($sub); 976 $sub.css('max-width', this.opts.subMenusMaxWidth); 977 if (noMaxWidth > this.getWidth($sub)) { 978 $sub.removeClass('sm-nowrap').css('width', this.opts.subMenusMaxWidth); 979 } 980 } 981 } 982 this.menuPosition($sub); 983 // insert IE iframe shim 984 if ($sub.dataSM('ie-shim')) { 985 $sub.dataSM('ie-shim').insertBefore($sub); 986 } 987 } 988 var complete = function () { 989 // fix: "overflow: hidden;" is not reset on animation complete in jQuery < 1.9.0 in Chrome when global "box-sizing: border-box;" is used 990 $sub.css('overflow', ''); 991 }; 992 // if sub is collapsible (mobile view) 993 if (this.isCollapsible()) { 994 if (this.opts.collapsibleShowFunction) { 995 this.opts.collapsibleShowFunction.call(this, $sub, complete); 996 } else { 997 $sub.show(this.opts.collapsibleShowDuration, complete); 998 } 999 } else { 1000 if (this.opts.showFunction) { 1001 this.opts.showFunction.call(this, $sub, complete); 1002 } else { 1003 $sub.show(this.opts.showDuration, complete); 1004 } 1005 } 1006 // accessibility 1007 $a.attr('aria-expanded', 'true'); 1008 $sub.attr({ 1009 'aria-expanded': 'true', 1010 'aria-hidden': 'false' 1011 }); 1012 // store sub menu in visible array 1013 this.visibleSubMenus.push($sub); 1014 this.$root.triggerHandler('show.smapi', $sub[0]); 1015 } 1016 }, 1017 popupHide: function (noHideTimeout) { 1018 if (this.hideTimeout) { 1019 clearTimeout(this.hideTimeout); 1020 this.hideTimeout = 0; 1021 } 1022 var self = this; 1023 this.hideTimeout = setTimeout(function () { 1024 self.menuHideAll(); 1025 }, noHideTimeout ? 1 : this.opts.hideTimeout); 1026 }, 1027 popupShow: function (left, top) { 1028 if (!this.opts.isPopup) { 1029 alert('SmartMenus jQuery Error:\n\nIf you want to show this menu via the "popupShow" method, set the isPopup:true option.'); 1030 return; 1031 } 1032 if (this.hideTimeout) { 1033 clearTimeout(this.hideTimeout); 1034 this.hideTimeout = 0; 1035 } 1036 this.$root.dataSM('shown-before', true) 1037 .stop(true, true); 1038 if (!this.$root.is(':visible')) { 1039 this.$root.css({ left: left, top: top }); 1040 // IE iframe shim 1041 this.menuIframeShim(this.$root); 1042 if (this.$root.dataSM('ie-shim')) { 1043 this.$root.dataSM('ie-shim').css({ zIndex: this.$root.css('z-index'), width: this.getWidth(this.$root), height: this.getHeight(this.$root), left: left, top: top }).insertBefore(this.$root); 1044 } 1045 // show menu 1046 var self = this, 1047 complete = function () { 1048 self.$root.css('overflow', ''); 1049 }; 1050 if (this.opts.showFunction) { 1051 this.opts.showFunction.call(this, this.$root, complete); 1052 } else { 1053 this.$root.show(this.opts.showDuration, complete); 1054 } 1055 this.visibleSubMenus[0] = this.$root; 1056 } 1057 }, 1058 refresh: function () { 1059 this.destroy(true); 1060 this.init(true); 1061 }, 1062 rootKeyDown: function (e) { 1063 if (!this.handleEvents()) { 1064 return; 1065 } 1066 switch (e.keyCode) { 1067 case 27: // reset on Esc 1068 var $activeTopItem = this.activatedItems[0]; 1069 if ($activeTopItem) { 1070 this.menuHideAll(); 1071 $activeTopItem[0].focus(); 1072 var $sub = $activeTopItem.dataSM('sub'); 1073 if ($sub) { 1074 this.menuHide($sub); 1075 } 1076 } 1077 break; 1078 case 32: // activate item's sub on Space 1079 var $target = $(e.target); 1080 if ($target.is('a') && this.handleItemEvents($target)) { 1081 var $sub = $target.dataSM('sub'); 1082 if ($sub && !$sub.is(':visible')) { 1083 this.itemClick({ currentTarget: e.target }); 1084 e.preventDefault(); 1085 } 1086 } 1087 break; 1088 } 1089 }, 1090 rootOut: function (e) { 1091 if (!this.handleEvents() || this.isTouchMode() || e.target == this.$root[0]) { 1092 return; 1093 } 1094 if (this.hideTimeout) { 1095 clearTimeout(this.hideTimeout); 1096 this.hideTimeout = 0; 1097 } 1098 if (!this.opts.showOnClick || !this.opts.hideOnClick) { 1099 var self = this; 1100 this.hideTimeout = setTimeout(function () { self.menuHideAll(); }, this.opts.hideTimeout); 1101 } 1102 }, 1103 rootOver: function (e) { 1104 if (!this.handleEvents() || this.isTouchMode() || e.target == this.$root[0]) { 1105 return; 1106 } 1107 if (this.hideTimeout) { 1108 clearTimeout(this.hideTimeout); 1109 this.hideTimeout = 0; 1110 } 1111 }, 1112 winResize: function (e) { 1113 if (!this.handleEvents()) { 1114 // we still need to resize the disable overlay if it's visible 1115 if (this.$disableOverlay) { 1116 var pos = this.$root.offset(); 1117 this.$disableOverlay.css({ 1118 top: pos.top, 1119 left: pos.left, 1120 width: this.$root.outerWidth(), 1121 height: this.$root.outerHeight() 1122 }); 1123 } 1124 return; 1125 } 1126 // hide sub menus on resize - on mobile do it only on orientation change 1127 if (!('onorientationchange' in window) || e.type == 'orientationchange') { 1128 var isCollapsible = this.isCollapsible(); 1129 // if it was collapsible before resize and still is, don't do it 1130 if (!(this.wasCollapsible && isCollapsible)) { 1131 if (this.activatedItems.length) { 1132 this.activatedItems[this.activatedItems.length - 1][0].blur(); 1133 } 1134 this.menuHideAll(); 1135 } 1136 this.wasCollapsible = isCollapsible; 1137 } 1138 } 1139 } 1140 }); 1141 1142 $.fn.dataSM = function (key, val) { 1143 if (val) { 1144 return this.data(key + '_smartmenus', val); 1145 } 1146 return this.data(key + '_smartmenus'); 1147 }; 1148 1149 $.fn.removeDataSM = function (key) { 1150 return this.removeData(key + '_smartmenus'); 1151 }; 1152 1153 $.fn.smartmenus = function (options) { 1154 if (typeof options == 'string') { 1155 var args = arguments, 1156 method = options; 1157 Array.prototype.shift.call(args); 1158 return this.each(function () { 1159 var smartmenus = $(this).data('smartmenus'); 1160 if (smartmenus && smartmenus[method]) { 1161 smartmenus[method].apply(smartmenus, args); 1162 } 1163 }); 1164 } 1165 // [data-sm-options] attribute on the root UL 1166 var dataOpts = this.data('sm-options') || null; 1167 if (dataOpts) { 1168 try { 1169 dataOpts = eval('(' + dataOpts + ')'); 1170 } catch (e) { 1171 dataOpts = null;
1172 alert('ERROR\n\nSmartMenus jQuery init:\nInvalid "data-sm-options" attribute value syntax.'); 1173 }; 1174 } 1175 return this.each(function () { 1176 new $.SmartMenus(this, $.extend({}, $.fn.smartmenus.defaults, options, dataOpts)); 1177 }); 1178 }; 1179 1180 // default settings 1181 $.fn.smartmenus.defaults = { 1182 isPopup: false, // is this a popup menu (can be shown via the popupShow/popupHide methods) or a permanent menu bar 1183 mainMenuSubOffsetX: 0, // pixels offset from default position 1184 mainMenuSubOffsetY: 0, // pixels offset from default position 1185 subMenusSubOffsetX: 0, // pixels offset from default position 1186 subMenusSubOffsetY: 0, // pixels offset from default position 1187 subMenusMinWidth: '10em', // min-width for the sub menus (any CSS unit) - if set, the fixed width set in CSS will be ignored 1188 subMenusMaxWidth: '20em', // max-width for the sub menus (any CSS unit) - if set, the fixed width set in CSS will be ignored 1189 subIndicators: true, // create sub menu indicators - creates a SPAN and inserts it in the A 1190 subIndicatorsPos: 'prepend', // position of the SPAN relative to the menu item content ('prepend', 'append') 1191 subIndicatorsText: '+', // [optionally] add text in the SPAN (e.g. '+') (you may want to check the CSS for the sub indicators too) 1192 scrollStep: 30, // pixels step when scrolling long sub menus that do not fit in the viewport height 1193 scrollAccelerate: true, // accelerate scrolling or use a fixed step 1194 showTimeout: 250, // timeout before showing the sub menus 1195 hideTimeout: 500, // timeout before hiding the sub menus 1196 showDuration: 0, // duration for show animation - set to 0 for no animation - matters only if showFunction:null 1197 showFunction: null, // custom function to use when showing a sub menu (the default is the jQuery 'show') 1198 // don't forget to call complete() at the end of whatever you do 1199 // e.g.: function($ul, complete) { $ul.fadeIn(250, complete); } 1200 hideDuration: 0, // duration for hide animation - set to 0 for no animation - matters only if hideFunction:null 1201 hideFunction: function ($ul, complete) { $ul.fadeOut(200, complete); }, // custom function to use when hiding a sub menu (the default is the jQuery 'hide') 1202 // don't forget to call complete() at the end of whatever you do 1203 // e.g.: function($ul, complete) { $ul.fadeOut(250, complete); } 1204 collapsibleShowDuration: 0, // duration for show animation for collapsible sub menus - matters only if collapsibleShowFunction:null 1205 collapsibleShowFunction: function ($ul, complete) { $ul.slideDown(200, complete); }, // custom function to use when showing a collapsible sub menu 1206 // (i.e. when mobile styles are used to make the sub menus collapsible) 1207 collapsibleHideDuration: 0, // duration for hide animation for collapsible sub menus - matters only if collapsibleHideFunction:null 1208 collapsibleHideFunction: function ($ul, complete) { $ul.slideUp(200, complete); }, // custom function to use when hiding a collapsible sub menu 1209 // (i.e. when mobile styles are used to make the sub menus collapsible) 1210 showOnClick: false, // show the first-level sub menus onclick instead of onmouseover (i.e. mimic desktop app menus) (matters only for mouse input) 1211 hideOnClick: true, // hide the sub menus on click/tap anywhere on the page 1212 noMouseOver: false, // disable sub menus activation onmouseover (i.e. behave like in touch mode - use just mouse clicks) (matters only for mouse input) 1213 keepInViewport: true, // reposition the sub menus if needed to make sure they always appear inside the viewport 1214 keepHighlighted: true, // keep all ancestor items of the current sub menu highlighted (adds the 'highlighted' class to the A's) 1215 markCurrentItem: false, // automatically add the 'current' class to the A element of the item linking to the current URL 1216 markCurrentTree: true, // add the 'current' class also to the A elements of all ancestor items of the current item 1217 rightToLeftSubMenus: false, // right to left display of the sub menus (check the CSS for the sub indicators' position) 1218 bottomToTopSubMenus: false, // bottom to top display of the sub menus
1219 overlapControlsInIE: true // make sure sub menus appear on top of special OS controls in IE (i.e. SELECT, OBJECT, EMBED, etc.) 1220 }; 1221 1222 return $; 1223}));
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.