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