1/* 2* ubermenu.js 3* 4* http://wpmegamenu.com 5* 6* Copyright Chris Mavricos, SevenSpark https://sevenspark.com 7*/ 8 9//Define dependencies first because deferred scripts don't respect document ready order 10; var uber_supports = (function () { 11 var div = document.createElement('div'), 12 vendors = 'Khtml Ms O Moz Webkit'.split(' '); 13 return function (prop) { 14 var len = vendors.length; 15 if (prop in div.style) return true; 16 prop = prop.replace(/^[a-z]/, function (val) { 17 return val.toUpperCase(); 18 }); 19 while (len--) { 20 if (vendors[len] + prop in div.style) { 21 return true; 22 } 23 } 24 return false; 25 }; 26})(); 27 28function uber_op(id, args, def) { 29 //If no id 30 if (!ubermenu_data.hasOwnProperty(id)) { 31 return def; 32 } 33 var val = ubermenu_data[id]; 34 if (args.hasOwnProperty('datatype')) { 35 switch (args['datatype']) { 36 case 'numeric': 37 val = parseInt(val); 38 break; 39 40 case 'boolean': 41 if (val == 'on' || val == 1 || val == '1') { 42 val = true; 43 } 44 else { 45 val = false; 46 } 47 break; 48 } 49 } 50 return val; 51} 52 53; (function ($, sr) { 54 // debouncing function from John Hann 55 // http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/ 56 var debounce = function (func, threshold, execAsap) { 57 var timeout; 58 return function debounced() { 59 var obj = this, args = arguments; 60 function delayed() { 61 if (!execAsap) 62 func.apply(obj, args); 63 timeout = null; 64 }; 65 if (timeout) 66 clearTimeout(timeout); 67 else if (execAsap) 68 func.apply(obj, args); 69 timeout = setTimeout(delayed, threshold || 100); 70 }; 71 } 72 jQuery.fn[sr] = function (fn) { return fn ? this.on('resize', debounce(fn)) : this.trigger(sr); }; 73 74})(jQuery, 'ubersmartresize'); 75 76; (function ($, window, document, undefined) { 77 'use strict'; 78 var pluginName = "ubermenu", 79 defaults = { 80 breakpoint: uber_op('responsive_breakpoint', { datatype: 'numeric' }, 959), 81 touchEvents: true, 82 mouseEvents: true, 83 retractors: true, 84 touchOffClose: uber_op('touch_off_close', { datatype: 'boolean' }, true), //true, 85 submenuIndicatorCloseMobile: uber_op('submenu_indicator_close_mobile', { datatype: 'boolean' }, true), 86 moveThreshold: 10, //Distance until tap is cancelled in deference to move/scroll 87 submenuAnimationDuration: 500, //ms duration or submenu close time 88 ignoreDummies: true, 89 clicktest: false, 90 windowstest: false, 91 debug: false, 92 debug_onscreen: false, 93 94 remove_conflicts: uber_op('remove_conflicts', { datatype: 'boolean' }, true), 95 reposition_on_load: uber_op('reposition_on_load', { datatype: 'boolean' }, false), 96 accessible: uber_op('accessible', { datatype: 'boolean' }, true), 97 retractor_display_strategy: uber_op('retractor_display_strategy', { datatype: 'string' }, 'responsive'), 98 mobile_menu_collapse_on_navigate: uber_op('mobile_menu_collapse_on_navigate', { datatype: 'boolean' }, true), 99 100 intent_delay: uber_op('intent_delay', { datatype: 'numeric' }, 300), //delay before the menu closes 101 intent_interval: uber_op('intent_interval', { datatype: 'numeric' }, 100), //polling interval for mouse comparisons 102 intent_threshold: uber_op('intent_threshold', { datatype: 'numeric' }, 300), //maximum number of pixels mouse can move to be considered intent 103 104 scrollto_offset: uber_op('scrollto_offset', { datatype: 'numeric' }, 0), 105 scrollto_duration: uber_op('scrollto_duration', { datatype: 'numeric' }, 1000), 106 collapse_after_scroll: uber_op('collapse_after_scroll', { datatype: 'boolean' }, true), 107 108 aria_role_navigation: uber_op('aria_role_navigation', { datatype: 'boolean' }, false), 109 aria_nav_label: uber_op('aria_nav_label', { datatype: 'boolean' }, false), 110 aria_expanded: uber_op('aria_expanded', { datatype: 'boolean' }, false), 111 aria_haspopup: uber_op('aria_haspopup', { datatype: 'boolean' }, false), 112 aria_hidden: uber_op('aria_hidden', { datatype: 'boolean' }, false), 113 aria_responsive_toggle: uber_op('aria_responsive_toggle', { datatype: 'boolean' }, false), 114 icon_tag: uber_op('icon_tag', { datatype: 'string' }, 'i'), 115 use_core_svgs: uber_op('use_core_svgs', { datatype: 'boolean' }, false), 116 esc_close_mobile: uber_op('esc_close_mobile', { datatype: 'boolean' }, true), 117 118 keyboardSubmenuTrigger: uber_op('keyboard_submenu_trigger', { datatype: 'string' }, 'enter'), // focus, spacebar, enter 119 120 }/*, 121 keys = { 122 BACKSPACE: 8, 123 COMMA: 188, 124 DELETE: 46, 125 DOWN: 40, 126 END: 35, 127 ENTER: 13, 128 ESCAPE: 27, 129 HOME: 36, 130 LEFT: 37, 131 PAGE_DOWN: 34, 132 PAGE_UP: 33, 133 PERIOD: 190, 134 RIGHT: 39, 135 SPACE: 32, 136 TAB: 9, 137 UP: 38 138 }*/; 139 140 // instantiate variables 141 // cX, cY = current X and Y position of mouse, updated by mousemove event 142 // pX, pY = previous X and Y position of mouse, set by mouseover and polling interval 143 var cX, cY, pX, pY; 144 145 function Plugin(element, options) { 146 147 var plugin = this; //reference for all 148 this.element = element; 149 this.$ubermenu = $(this.element); 150 151 this.orientation = this.$ubermenu.hasClass('ubermenu-vertical') ? 'v' : 'h'; 152 this.mobileAccordion = this.$ubermenu.hasClass('ubermenu-mobile-accordion'); 153 this.mobileAccordionFolding = this.$ubermenu.hasClass('ubermenu-mobile-accordion-single') ? 'single' : 'multiple'; 154 this.modalMobile = this.$ubermenu.hasClass('ubermenu-mobile-modal'); 155 this.interactionMode = null; //hover or press 156 this.vstretch = this.$ubermenu.hasClass('ubermenu-items-vstretch'); 157 this.inverted = this.$ubermenu.hasClass('ubermenu-invert'); 158 159 this.settings = $.extend({}, defaults, options); 160 this._defaults = defaults; 161 this._name = pluginName; 162 this.settings.responsive = this.$ubermenu.hasClass('ubermenu-responsive') ? true : false;
163 164 if (this.settings.debug && this.settings.debug_onscreen) { 165 $('body').append('<div id="uber-onscreen-debug" style="color:#eee;z-index:10000;background:#222;position:fixed;left:0; bottom:0; width:100%; height:50%; padding:10px;overflow:scroll;"> '); 166 this.debug_target = $('#uber-onscreen-debug'); 167 this.debug_target.on('click', function () { if ($(this).height() < 100) $(this).height('50%'); else $(this).height('50px'); }); 168 } 169 this.log('-- START UBERMENU DEBUG --'); 170 171 this.events_disabled = false; 172 this.suppress_clicks = false; //Set to true if the mouse events support 'pointerup', which handles both touch and click (but not hover) 173 174 this.touchenabled = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0); 175 if (this.touchenabled) { 176 this.$ubermenu.addClass('ubermenu-touch'); 177 } 178 else this.$ubermenu.addClass('ubermenu-notouch'); 179 180 if (window.navigator.pointerEnabled) { 181 this.touchStart = 'pointerdown'; 182 this.touchEnd = 'pointerup'; 183 this.touchMove = 'pointermove'; 184 185 this.suppress_clicks = true; 186 } 187 else if (window.navigator.msPointerEnabled) { 188 this.touchStart = 'MSPointerDown'; 189 this.touchEnd = 'MSPointerUp'; 190 this.touchMove = 'MSPointerMove'; 191 192 this.suppress_clicks = true; 193 } 194 else { 195 this.touchStart = 'touchstart'; 196 this.touchEnd = 'touchend'; 197 this.touchMove = 'touchmove'; 198 } 199 200 this.toggleevent = 'click'; // this.touchEnd == 'touchend' ? this.touchEnd + ' click' : this.touchEnd; //add click except for IE 201 this.transitionend = 'transitionend.ubermenu webkitTransitionEnd.ubermenu msTransitionEnd.ubermenu oTransitionEnd.ubermenu'; 202 203 //Are transitions supported? 204 this.transitions = uber_supports('transition') && !this.$ubermenu.hasClass('ubermenu-transition-none'); 205 if (!this.transitions) this.$ubermenu.addClass('ubermenu-no-transitions'); 206 207 //Detect crappy Android & Windows browsers and disable transitions 208 var ua = navigator.userAgent.toLowerCase(); 209 this.log(ua); 210 211 this.allow_trigger_overrides = true; 212 this.noTouchEnd = false; 213 214 var android = this.settings.android = /android/.test(ua); 215 var windowsmobile = this.settings.windowsmobile = /iemobile/.test(ua); 216 if (android || windowsmobile) { 217 //this.log( 'android or windows' ); 218 //alert( ua ); 219 if ((android && !(/chrome/.test(ua) || /firefox/.test(ua) || /opera/.test(ua))) || 220 (windowsmobile) 221 ) { 222 this.settings.touchOffClose = false; 223 this.disableTransitions(); 224 225 //Crap Android browsers don't fire touchend properly 226 if (android && !windowsmobile) { //adjust to better determine android UA string instead 227 this.$ubermenu 228 .removeClass('ubermenu-trigger-hover_intent') 229 .removeClass('ubermenu-trigger-hover') 230 .addClass('ubermenu-trigger-click'); 231 this.settings.touchEvents = false; 232 this.allow_trigger_overrides = false; 233 } 234 } 235 } 236 237 if (windowsmobile) { 238 this.log('disable touchoff close and accessibility'); 239 this.settings.touchOffClose = false; //improper event delegation in windows 240 this.settings.accessible = false; //WTF Microsoft 241 this.settings.mouseEvents = false; 242 } 243 244 var safari5 = (!/chrome/.test(ua)) && (/safari/.test(ua)) && (/version\/5/.test(ua)); 245 if (safari5) { 246 this.disableTransitions(); 247 } 248 249 250 //Reflow right items 251 this.last_width = window.innerWidth; 252 var cur_width = this.last_width; 253 var $right_items = plugin.$ubermenu.find('.ubermenu-item-level-0.ubermenu-align-right'); 254 if ($right_items.length) { 255 $(window).ubersmartresize(function () { 256 cur_width = window.innerWidth; 257 if (plugin.last_width <= plugin.settings.breakpoint && 258 cur_width >= plugin.settings.breakpoint) { 259 $right_items.hide(); $right_items[0].offsetHeight; //$right_items.show(); //reflow 260 $right_items.css('display', ''); 261 } 262 plugin.last_width = cur_width; 263 }); 264 } 265 266 //TESTING 267 if (this.settings.clicktest) this.touchEnd = 'click'; 268 269 this.init(); 270 271 } 272 273 Plugin.prototype = { 274 275 init: function () { 276 277 this.log('Initializing UberMenu'); 278 279 this.$ubermenu.removeClass('ubermenu-nojs'); //We're off and running 280 281 this.removeConflicts();
281 //Stop other JS from interfering when possible 282 283 //Initialize user interaction events 284 this.initializeSubmenuToggleTouchEvents(); 285 this.initializeSubmenuToggleMouseEvents(); 286 this.initializeRetractors(); 287 this.initializeResponsiveToggle(); 288 this.initializeMobileViewClasses(); 289 this.initializeMobileModal(); 290 this.initializeTouchoffClose(); 291 this.initializeTabs(); 292 this.initializeSubmenuPositioning(); 293 this.initializeSegmentCurrentStates(); 294 this.initializeAccessibilityOnTab(); 295 this.initializeAccessibilityStates(); 296 this.initializeImageLazyLoad(); 297 this.initializeImagePortal(); 298 299 this.$ubermenu.trigger('ubermenuinit'); 300 301 }, 302 303 removeConflicts: function () { 304 if (this.settings.remove_conflicts) { 305 this.$ubermenu.find('.ubermenu-item, .ubermenu-target, .ubermenu-submenu').add(this.$ubermenu) 306 .removeAttr('style') 307 .off(); 308 } 309 }, 310 311 312 313 /* Initalizers */ 314 315 initializeAccessibilityStates: function () { 316 var plugin = this; 317 318 //aria role="navigation" on nav element 319 if (this.settings.aria_role_navigation) { 320 plugin.$ubermenu.attr('role', 'navigation'); 321 } 322 323 //aria-label="{menu title}" on nav element 324 if (this.settings.aria_nav_label) { 325 plugin.$ubermenu.attr('aria-label', this.$ubermenu.find('> .ubermenu-nav').attr('data-title')); 326 } 327 328 var $anchors_with_subs = plugin.$ubermenu.find('.ubermenu-item.ubermenu-has-submenu-drop > .ubermenu-target'); 329 //aria-expanded on menu items with dropdowns 330 if (plugin.settings.aria_expanded) { 331 $anchors_with_subs.attr('aria-expanded', false); 332 } 333 if (plugin.settings.aria_haspopup) { 334 $anchors_with_subs.attr('aria-haspopup', true); 335 } 336 337 //aria-hidden on dropdowns 338 if (plugin.settings.aria_hidden) { 339 plugin.$ubermenu.find('.ubermenu-submenu-drop').attr('aria-hidden', true); 340 } 341 342 343 var $mobile_only_content = plugin.$ubermenu.find('.ubermenu-mobile-header, .ubermenu-mobile-footer'); 344 $mobile_only_content.attr('aria-hidden', !plugin.isMobile()); 345 $(window).on('ubermenu-resize-mobile', function () { 346 $mobile_only_content.attr('aria-hidden', false); 347 }); 348 $(window).on('ubermenu-resize-desktop', function () { 349 $mobile_only_content.attr('aria-hidden', true); 350 }); 351 352 }, 353 354 initializeAccessibilityOnTab: function () { 355 356 if (!this.settings.accessible) return; 357 358 var plugin = this; 359 360 //Initialize 361 $('body').on('keydown.ubermenu', function (e) { 362 var keyCode = e.keyCode || e.which; 363 if (keyCode == 9) { 364 $('body').off('keydown.ubermenu'); 365 plugin.initializeAccessibility(); 366 } 367 }); 368 369 //Esc to close on mobile - whether tabbed or not 370 if (plugin.settings.esc_close_mobile) { 371 $(window).on('keyup', function (e) { 372 if (plugin.isMobile() && !plugin.$ubermenu.hasClass('ubermenu-responsive-collapse')) { 373 if (e.which === 27) { 374 plugin.toggleMenuCollapse(); 375 } 376 } 377 }); 378 } 379 380 }, 381 382 initializeImageLazyLoad: function () { 383 var plugin = this; 384 plugin.$ubermenu.find('.ubermenu-item-level-0').one('ubermenuopen', function () { 385 $(this).find('.ubermenu-image-lazyload').each(function () { 386 //Responsive images - add srcset and sizes if present 387 if ($(this).data('srcset')) { 388 $(this) 389 .attr('srcset', $(this).data('srcset')) 390 .attr('sizes', $(this).data('sizes')) 391 } 392 //Whether responsive images (4.4+) or not, set src attribute 393 $(this) 394 .attr('src', $(this).data('src')) 395 .removeClass('ubermenu-image-lazyload'); 396 }); 397 setTimeout(function () { 398 plugin.clearTabSizes(); 399 plugin.sizeTabs(); 400 }, 300); 401 }); 402 }, 403 404 initializeImagePortal: function () { 405 this.$ubermenu.find('.ubermenu-image-portal').each(function () { 406 var $imageportal = $(this); 407 var selector = $imageportal.data('ubermenu-portal-select'); 408 var $images = []; // Collect the images 409 var $default_content = $imageportal.find('> .ubermenu-image-portal__default'); 410 411 $(selector).each(function () { 412 var $menuitem = $(this); 413 var $img = $menuitem.find('> .ubermenu-target > .ubermenu-image'); 414 415 $img.addClass('ubermenu-image-portal-hidden'); 416 $img.attr('data-ubermenu-item-ref', $menuitem.attr('id')); 417 $menuitem.find('> .ubermenu-target').on('mouseenter', function () { 418 $img.removeClass('ubermenu-image-portal-hidden'); 419 if ($default_content) $default_content.addClass('ubermenu-image-portal-hidden'); 420 }).on('mouseleave', function () { 421 $img.addClass('ubermenu-image-portal-hidden'); 422 if ($default_content) $default_content.removeClass('ubermenu-image-portal-hidden'); 423 }); 424 425 // console.log($img); 426 $images.push($img); 427 }); 428 // console.log($images); 429 $imageportal.append($images); 430 }); 431 }, 432 433 initializeAccessibility: function () { 434 435 var plugin = this; 436 var tabbables = '.ubermenu-target, a, input, select, textarea'; 437 438 plugin.$current_focus = false;
439 plugin.mousedown = false; 440 plugin.$ubermenu.addClass('ubermenu-accessible'); 441 442 //Focus 443 plugin.$ubermenu.on('focus', tabbables, function () { 444 445 // Ignore if focused by mouse 446 if (!plugin.mousedown) { 447 448 var $target = $(this); 449 450 plugin.$current_focus = $target; 451 var $item = $target.parent('.ubermenu-item'); //get the LI parent of A 452 453 if ($item.length) { 454 //Top level items - just close everything else 455 if ($item.is('.ubermenu-item-level-0')) { 456 plugin.closeAllSubmenus(); 457 } 458 459 if (plugin.settings.keyboardSubmenuTrigger === 'focus') { 460 //Items with submenus - open the submenus 461 if ($item.is('.ubermenu-has-submenu-drop')) { 462 //Delay .5s so that if we want we can tab right through 463 setTimeout(function () { 464 if (!$target.is(':focus')) return; //skip if item no longer focused 465 466 //Close the submenus of all siblings 467 $item.siblings('.ubermenu-has-submenu-drop').each(function () { 468 plugin.closeSubmenu($(this), 'umac', plugin); 469 }); 470 471 //Open this item's submenu 472 plugin.openSubmenu($item, 'umac', plugin); 473 474 }, 500); 475 } 476 } 477 478 //Focusout - any time an element is blurred, check to see if the 479 //xUse focusout because it handles blur on all types of child elements 480 //plugin.$ubermenu.on( 'focusout' , '.ubermenu-item-level-0' , function(){ 481 //plugin.$ubermenu.on( 'blur' , tabbables , function(e){ 482 $target.on('blur.ubermenu', tabbables, function (e) { 483 if (!plugin.mousedown) { 484 plugin.$current_focus = false; 485 $(this).off('blur.ubermenu'); 486 487 //If a new focus within the menu isn't set within .5s, assume we've left the menu focus 488 setTimeout(function () { 489 if (!plugin.$current_focus) { 490 //console.log( 'abandon ' , plugin.$current_focus ); 491 plugin.closeAllSubmenus(); 492 } 493 }, 500); 494 } 495 plugin.mousedown = false; 496 }); 497 } 498 } 499 500 plugin.mousedown = false; 501 502 }); 503 504 //Focus leaves UberMenu, close any open submenus 505 plugin.$ubermenu.on('focusout', function () { 506 setTimeout(function () { 507 if (!$(document.activeElement).closest(plugin.$ubermenu).length) { 508 plugin.closeAllSubmenus(); 509 } 510 }, 10); 511 }); 512 513 var openViaSpacebar = plugin.settings.keyboardSubmenuTrigger === 'spacebar'; 514 var openViaEnter = plugin.settings.keyboardSubmenuTrigger === 'enter'; 515 var openViaKey = openViaSpacebar || openViaEnter; 516 517 518 // Potentially override tab, enter, spacebar 519 if (openViaKey) { 520 plugin.$ubermenu.find('.ubermenu-item').on('keydown', function (e) { 521 522 var $li = $(this); 523 var hasDrop = $li.hasClass('ubermenu-has-submenu-drop'); 524 var active = $li.hasClass('ubermenu-active'); 525 526 // Override normal tabbing if we're opening with spacebar or enter 527 if (e.which === 9) { 528 // If there is a submenu and the submenu is not open, 529 // tab to other top level items 530 // otherwise, let normal tabbing work 531 if (hasDrop && !active) { 532 if (e.shiftKey) { 533 var $prev = $li.prev('.ubermenu-item'); 534 if ($prev.length) { 535 console.log($prev); 536 e.preventDefault(); 537 $prev.find('>.ubermenu-target').focus(); 538 } 539 } 540 else { 541 var $next = $li.next('.ubermenu-item'); 542 if ($next.length) { 543 e.preventDefault(); 544 $next.find('>.ubermenu-target').focus(); 545 } 546 } 547 } 548 } 549 if (openViaEnter) { 550 switch (e.which) { 551 // Enter - open submenu first time, follow link second time 552 case 13: 553 if (hasDrop && !active) { 554 e.preventDefault(); 555 e.stopPropagation(); 556 //Close the submenus of all siblings 557 $li.siblings('.ubermenu-has-submenu-drop').each(function () { 558 plugin.closeSubmenu($(this), 'umac', plugin); 559 }); 560 plugin.openSubmenu($(this)); 561 } 562 break; 563 } 564 } 565 if (openViaSpacebar) { 566 switch (e.which) { 567 // // Override the default Enter if using spacebar 568 // case 13: 569 // e.preventDefault(); 570 // $(this).find('>.ubermenu-target').click(); // Follow the link 571 // break; 572 // Spacebar open sub 573 case 32: 574 e.preventDefault(); // Prevent the scroll 575 e.stopPropagation(); // don't bubble up to parents 576 $li.siblings('.ubermenu-has-submenu-drop').each(function () { 577 plugin.closeSubmenu($(this), 'umac', plugin); 578 }); 579 if (active) plugin.closeSubmenu($(this)); // Close the submenus 580 else if (hasDrop) plugin.openSubmenu($(this)); // Open the submenu 581 break; 582 } 583 } 584 }); 585 } 586 //Top Level Keyboard interactions - left, right, escape 587 plugin.$ubermenu.find('.ubermenu-item-level-0').on('keyup', function (e) { 588 //var $uber = jQuery( '.ubermenu' ); 589 switch (e.which) { 590 case 39: //right 591 plugin.closeAllSubmenus(); 592 //console.log( 'right ' + jQuery( this ).next().attr('id') ); 593 $(this).next().find('>.ubermenu-target').focus(); 594 break; 595 596 case 37: //left 597 plugin.closeAllSubmenus(); 598 //console.log( 'left '+ jQuery( this ).prev().attr('id') ); 599 $(this).prev().find('>.ubermenu-target').focus(); 600 break; 601 602 case 27: //escape 603 // If we close this submenu, don't propagate the event which would close the whole menu 604 if ($(this).hasClass('ubermenu-active')) { 605 e.stopPropagation(); 606 } 607 plugin.closeAllSubmenus(); 608 $(this).find('>.ubermenu-target').focus(); 609 // $(this).next().find('>.ubermenu-target').focus(); 610 break; 611 } 612 }); 613 // Tabs 614 plugin.$ubermenu.find('.ubermenu-tab-layout-left > .ubermenu-tabs-group > .ubermenu-tab').on('keydown', function (e) { 615 switch (e.which) { 616 case 40: // Down Arrow 617 e.preventDefault(); 618 $(this).next().find('>.ubermenu-target').focus(); 619 break; 620 case 38: // Up arrow 621 e.preventDefault(); 622 $(this).prev().find('>.ubermenu-target').focus(); 623 break; 624 // default: console.log(e.which); 625 } 626 }); 627 628 629
630 //Mousedown - flag a mouse interaction - focus event above will be ignored if focus is result of mouse 631 plugin.$ubermenu.on('mousedown', function (e) { 632 plugin.mousedown = true; 633 setTimeout(function () { plugin.mousedown = false; }, 100); 634 }); 635 636 }, 637 638 639 initializeSubmenuPositioning: function () { 640 var plugin = this; 641 plugin.positionSubmenus(); 642 $(window).ubersmartresize(function () { 643 plugin.positionSubmenus(); 644 }); 645 646 if (this.settings.reposition_on_load) { 647 $(window).on('load', function () { 648 plugin.positionSubmenus(); 649 }); 650 } 651 652 // Reposition when menu resize occurs 653 if ('ResizeObserver' in window) { 654 var obs = new ResizeObserver(function () { 655 plugin.positionSubmenus(); 656 }); 657 obs.observe(plugin.$ubermenu[0]); 658 } 659 }, 660 661 initializeSubmenuToggleTouchEvents: function () { 662 663 if (!this.settings.touchEvents) return; 664 665 var plugin = this; 666 667 //Touch Events 668 //this.$ubermenu.on( this.touchStart , '.ubermenu-item' , function(e){ plugin.handleTouchInteraction( e , this , plugin ); } ); 669 670 this.$ubermenu.on(this.touchStart, '.ubermenu-target:not(.shiftnav-toggle)', function (e) { plugin.handleTouchInteraction(e, this, plugin); }); 671 //this.$ubermenu.on( this.touchEnd , '.ubermenu-item' , this.handleSubmenuToggle ); //Added only on touchStart 672 //this.$ubermenu.on( this.touchMove , '.ubermenu-item' , this.preventInteractionOnScroll ); 673 674 //Prevent "Ghost Clicks" 675 this.$ubermenu.on('click', '.ubermenu-has-submenu-drop > .ubermenu-target, .ubermenu-tab.ubermenu-item-has-children > .ubermenu-target', function (e) { plugin.handleClicks(e, this, plugin); }); 676 677 }, 678 679 initializeSubmenuToggleMouseEvents: function (plugin) { 680 681 plugin = plugin || this; //Assign this to plugin if not passed 682 683 684 //Don't initialize if mouse events are disabled 685 if (!plugin.settings.mouseEvents) return; 686 if (plugin.settings.clicktest) return; 687 if (plugin.settings.windowstest) return; 688 689 690 plugin.log('initializeSubmenuToggleMouseEvents'); 691 692 693 var evt = ''; 694 var trigger = 'hover'; 695 if (plugin.$ubermenu.hasClass('ubermenu-trigger-click')) { 696 trigger = 'click'; 697 } 698 else if (plugin.$ubermenu.hasClass('ubermenu-trigger-hover_intent')) { 699 trigger = 'hover_intent'; 700 } 701 702 if (trigger == 'click') { 703 //In the event this device supports 'pointerup', let that event handle the interactions rather than the click event 704 if (!this.suppress_clicks) { 705 evt = 'click.ubermenu-submenu-toggle'; 706 //evt = 'mouseup.ubermenu-submenu-toggle'; 707 this.$ubermenu.on(evt, '.ubermenu-item.ubermenu-has-submenu-drop:not([data-ubermenu-trigger]) > .ubermenu-target', function (e) { plugin.handleMouseClick(e, this, plugin); }); 708 this.$ubermenu.on('click.ubermenu-click-target', '.ubermenu-item:not(.ubermenu-has-submenu-drop):not([data-ubermenu-trigger]) > .ubermenu-target', function (e) { plugin.handleLink(e, this, plugin); }); 709 } 710 } 711 else if (trigger == 'hover_intent') { 712 evt = 'mouseenter.mouse_intent'; // > .ubermenu-target 713 this.$ubermenu.on(evt, '.ubermenu-item.ubermenu-has-submenu-drop:not([data-ubermenu-trigger])', function (e) { plugin.handleMouseIntent(e, this, plugin); }); 714 this.$ubermenu.on('click.ubermenu-click-target', '.ubermenu-item:not([data-ubermenu-trigger]) > .ubermenu-target', function (e) { plugin.handleLink(e, this, plugin); }); 715 } 716 else { 717 evt = 'mouseenter.ubermenu-submenu-toggle'; 718 this.$ubermenu.on(evt, '.ubermenu-item.ubermenu-has-submenu-drop:not([data-ubermenu-trigger]) > .ubermenu-target', function (e) { plugin.handleMouseover(e, this, plugin); }); 719 this.$ubermenu.on('click.ubermenu-click-target', '.ubermenu-item:not([data-ubermenu-trigger]) > .ubermenu-target', function (e) { plugin.handleLink(e, this, plugin); }); 720 } 721 722 //Now find divergents 723 if (this.allow_trigger_overrides) { 724 plugin.$ubermenu.find('.ubermenu-item[data-ubermenu-trigger]').each(function () { 725 726 var $li = $(this); 727 trigger = $li.data('ubermenu-trigger'); 728 729 if (trigger == 'click') { 730 if (!this.suppress_clicks) { //hmm 731 $li.on('click.ubermenu-submenu-toggle', '> .ubermenu-target', function (e) { plugin.handleMouseClick(e, this, plugin); }); 732 } 733 } 734 else if (trigger == 'hover_intent') { 735 $li.on('mouseenter.mouse_intent', function (e) { plugin.handleMouseIntent(e, this, plugin); }); 736 //$li.find( '> .ubermenu-target' ).on( 'click.ubermenu-click-target' , function(e){ plugin.handleLink( e , this , plugin ); } ); 737 } 738 //Hover 739 else { 740 $li.on('mouseenter.ubermenu-submenu-toggle', '> .ubermenu-target', function (e) { plugin.handleMouseover(e, this, plugin); }); 741 } 742 }); 743 } 744 else { 745 //If trigger overrides aren't allowed, default tabs to click 746 plugin.$ubermenu.find('.ubermenu-tab').on('click.ubermenu-submenu-toggle', '.ubermenu-target', function (e) { plugin.handleMouseClick(e, this, plugin); }); 747 } 748 749 //this.$ubermenu.on( 'mouseout.ubermenu-submenu-toggle' , '.ubermenu-item' , this.handleMouseout ); //now only added on mouseover 750 }, 751 752 disableSubmenuToggleMouseEvents: function () { 753 this.log('disableSubmenuToggleMouseEvents'); 754 this.events_disabled = true; 755 }, 756 757 758 reenableSubmenuToggleMouseEvents: function (plugin) { 759 plugin = plugin || this; 760 761 plugin.log('reenableSubmenuToggleMouseEvents'); 762 plugin.events_disabled = false;
763 }, 764 765 766 initializeRetractors: function () { 767 768 if (!this.settings.retractors) return; //Don't initialize if retractors are disabled 769 770 var plugin = this; 771 772 this.$ubermenu.on('click', '.ubermenu-retractor', function (e) { plugin.handleSubmenuRetractorEnd(e, this, plugin); }); 773 774 //set up the retractors 775 if (this.settings.touchEvents) this.$ubermenu.on(this.touchStart, '.ubermenu-retractor', function (e) { plugin.handleSubmenuRetractorStart(e, this, plugin); }); // > .ubermenu-target 776 777 //If this device does not support touch interactions, and the strategy is touch, remove the mobile retractors 778 if (!this.touchenabled && plugin.settings.retractor_display_strategy == 'touch') { 779 this.$ubermenu.find('.ubermenu-retractor-mobile').remove(); 780 this.$ubermenu.find('.ubermenu-submenu-retractor-top').removeClass('ubermenu-submenu-retractor-top').removeClass('ubermenu-submenu-retractor-top-2'); 781 } 782 783 //Indicator toggles 784 if (this.settings.submenuIndicatorCloseMobile) { 785 var close_icon = !plugin.settings.use_core_svgs 786 ? '<' + plugin.settings.icon_tag + ' class="fas fa-times"></' + plugin.settings.icon_tag + '>' 787 : '<span class="ubermenu-icon ubermenu-icon-essential ubermenu-icon-essential-times"><svg class="ubermenu-icon-svg-times"><use xlink:href="#ubermenu-icon-times"></use></svg></span>'; 788 var $target_subs = this.$ubermenu.find('.ubermenu-has-submenu-drop > .ubermenu-target') 789 .append('<span class="ubermenu-sub-indicator-close">' + close_icon + '</span>'); 790 var $indicator_toggles = $target_subs.find('>.ubermenu-sub-indicator-close'); 791 $indicator_toggles.on('click', function (e) { 792 e.preventDefault(); 793 e.stopPropagation(); 794 plugin.closeSubmenuInstantly($(this).closest('.ubermenu-item'), 'toggleUberMenuSubmenuClosed', plugin); 795 return false; 796 }); 797 if (this.settings.touchEvents) { 798 $indicator_toggles.on(this.touchStart, function (e) { 799 e.preventDefault(); 800 e.stopPropagation(); 801 plugin.closeSubmenuInstantly($(this).closest('.ubermenu-item'), 'toggleUberMenuSubmenuClosed', plugin); 802 return false; 803 }); 804 } 805 } 806 807 }, 808 809 810 initializeResponsiveToggle: function () { 811 var plugin = this; 812 var toggle_selector = '.ubermenu-responsive-toggle[data-ubermenu-target=' + plugin.$ubermenu.attr('id') + '], .ubermenu-responsive-toggle[data-ubermenu-target=_any_], #' + plugin.$ubermenu.attr('id') + ' .ubermenu-mobile-close-button'; 813 var $toggle = $(toggle_selector); 814 815 plugin.log('initializeResponsiveToggle ' + this.toggleevent); 816 817 //When the window is resized, adjust the toggle visibility ARIA 818 if (plugin.settings.aria_responsive_toggle) { 819 820 //aria-hidden for toggle 821 var toggle_hidden = window.innerWidth > plugin.settings.breakpoint; 822 $toggle.attr('aria-hidden', toggle_hidden); 823 $(window).ubersmartresize(function () { 824 $toggle.attr('aria-hidden', window.innerWidth > plugin.settings.breakpoint); 825 }); 826 var toggle_expanded = $toggle.hasClass('ubermenu-responsive-toggle-open'); 827 $toggle.attr('aria-expanded', toggle_expanded); 828 } 829 830 $(document).on(this.toggleevent, toggle_selector, 831 function (e) { plugin.handleResponsiveToggle(e, this, plugin); } 832 ); 833 834 // If the Collapse On Navigate setting is enabled and the menu is collapsed by default 835 if ( 836 plugin.settings.mobile_menu_collapse_on_navigate && // Setting enabled 837 !plugin.$ubermenu.hasClass('ubermenu-responsive-nocollapse') // Menu should be collapsed 838 ) { 839 $(window).on('pageshow', function () { 840 if (!plugin.$ubermenu.hasClass('ubermenu-responsive-collapse')) { 841 plugin.toggleMenuCollapse('close', false, plugin); 842 } 843 }); 844 } 845 846 //IE11 Keyboard accessibility 847 var isIE11 = /Trident.*rv[ :]*11\./.test(navigator.userAgent); 848 if (isIE11) { 849 $toggle.on('keypress', function (e) { 850 if (e.keyCode === 13 || e.keyCode === 32) { 851 plugin.handleResponsiveToggle(e, this, plugin); 852 } 853 }); 854 } 855 }, 856 857 initializeMobileViewClasses: function () { 858 var plugin = this; 859 plugin.toggleMobileClass(); 860 $(window).on('ubermenu-resize-mobile', function () { 861 plugin.toggleMobileClass(true); 862 }); 863 $(window).on('ubermenu-resize-desktop', function () { 864 plugin.toggleMobileClass(false); 865 }); 866 }, 867 868 initializeMobileModal: function () { 869 var plugin = this; 870 871 if (plugin.modalMobile) { 872 873 // If we initialize on mobile 874 if (window.innerWidth <= plugin.settings.breakpoint) {
875 plugin.swapModal(true); 876 } 877 878 // If window is resized 879 $(window).on('ubermenu-resize-mobile', function () { 880 plugin.swapModal(true); 881 }); 882 $(window).on('ubermenu-resize-desktop', function () { 883 plugin.swapModal(false); 884 }); 885 } 886 }, 887 888 swapModal: function (moveToFooter) { 889 if (moveToFooter) { 890 // console.log( 'swapModal to Footer' ); 891 this.$placeholder = $('<span class="ubermenu-swap-placeholder">'); 892 this.$ubermenu.after(this.$placeholder); 893 this.$ubermenu.appendTo('body'); 894 } 895 else { 896 if (this.$placeholder) { 897 // console.log( 'swapModal to header', this.$placeholder ); 898 this.$placeholder.replaceWith(this.$ubermenu); 899 this.$placeholder = false; 900 } 901 } 902 }, 903 904 initializeTouchoffClose: function () { 905 906 if (!this.settings.touchOffClose) return; //Don't initialize if touch off close is disabled 907 908 var plugin = this; 909 $(document).on(this.touchStart + '.ubermenu_touchoff', function (e) { plugin.handleTouchoffCloseStart(e, this, plugin); }); 910 $(document).on(this.touchEnd + '.ubermenu_touchoff', function (e) { plugin.handleTouchoffClose(e, this, 'touch', plugin); }); 911 if (!this.suppress_clicks) $(document).on('mouseup.ubermenu_clickoff', function (e) { plugin.handleTouchoffClose(e, this, 'click', plugin); }); //use mouseup instead of click for firefox 912 913 }, 914 915 916 initializeTabs: function () { 917 918 var plugin = this; 919 var responsive = plugin.settings.responsive && (window.innerWidth <= plugin.settings.breakpoint) ? true : false; 920 921 plugin.$tab_blocks = plugin.$ubermenu.find('.ubermenu-tabs'); 922 923 //Reverse order so we do the innermost panels first 924 plugin.$tab_blocks = $(plugin.$tab_blocks.get().reverse()); 925 926 //When all the images load, check the tabs 927 $(window).on('load', function () { 928 plugin.sizeTabs(); 929 }); 930 931 //When the window is resized, check the tabs 932 plugin.windowwidth = window.innerWidth; 933 $(window).ubersmartresize(function () { 934 plugin.oldwindowwidth = plugin.windowwidth; 935 plugin.windowwidth = window.innerWidth; 936 //only run if width has changed 937 if (plugin.windowwidth != plugin.oldwindowwidth) { 938 plugin.clearTabSizes(plugin); 939 plugin.sizeTabs(); 940 plugin.checkActiveTabs(plugin); 941 } 942 }); 943 944 //When the submenu is opened (first time only), check the tabs 945 plugin.$ubermenu.find('.ubermenu-item-level-0.ubermenu-has-submenu-drop').on('ubermenuopen.sizetabs', function () { 946 $(this).off('ubermenuopen.sizetabs'); 947 plugin.sizeTabs(); 948 }); 949 //plugin.sizeTabs(); 950 951 //Look for dynamically sized tabs, as we need to resize each time we switch tabs 952 plugin.$ubermenu.find('.ubermenu-tabs.ubermenu-tabs-dynamic-sizing').on('ubermenuopen', '> .ubermenu-tabs-group > .ubermenu-tab', function () { 953 plugin.sizeTabsDynamic($(this).closest('.ubermenu-tabs')); 954 }); 955 956 if (!responsive) { 957 plugin.initializeActiveTab(plugin); 958 } 959 960 // Close tabs if hovering empty toggle if not in Click mode 961 plugin.$ubermenu.find('.ubermenu-tabs-group:not(.ubermenu-tabs-group--trigger-click) > .ubermenu-tab:not(.ubermenu-has-submenu-drop):not(.ubermenu-ignore-empty-tab)').on('mouseenter', function () { 962 plugin.closeSubmenuInstantly($(this).siblings('.ubermenu-active')); 963 }); 964 }, 965 966 checkActiveTabs: function (plugin) { 967 if (window.innerWidth <= plugin.settings.breakpoint) { 968 //close all open tabs 969 plugin.$tab_blocks.find('.ubermenu-tab.ubermenu-active').removeClass('ubermenu-active'); 970 } 971 else { 972 plugin.initializeActiveTab(plugin); 973 } 974 }, 975 976 initializeActiveTab: function (plugin) { 977 978 plugin.$tab_blocks.each(function () { 979 var show_default = $(this).hasClass('ubermenu-tabs-show-default'); 980 var show_current = $(this).hasClass('ubermenu-tabs-show-current'); 981 var $tabs_group = $(this).find('> .ubermenu-tabs-group'); 982 983 //If there's already an active tab, skip - nothing to do 984 if ($tabs_group.find('> .ubermenu-tab.ubermenu-active').length) return; 985 986 var tab_shown = false; 987 988 //Current 989 if (show_current) { 990 //Fill in any ancestor tab gaps 991 $tabs_group.find('.ubermenu-current-menu-item').parentsUntil($tabs_group, '.ubermenu-tab:not( .ubermenu-nocurrent )').addClass('ubermenu-current-menu-ancestor'); 992 993 //Find any current tabs 994 var $current_tab = $tabs_group.find('> .ubermenu-tab.ubermenu-current-menu-ancestor, > .ubermenu-tab.ubermenu-current-menu-item'); 995 if ($current_tab.length) { 996 plugin.openSubmenu($current_tab.first(), 'tab current', plugin); 997 tab_shown = true; 998 } 999 } 1000 1001 //Default 1002 if (show_default && !tab_shown) { 1003 //If there are no active tabs, activate the first one 1004 if ($tabs_group.find('> .ubermenu-tab.ubermenu-active').length === 0) { 1005 plugin.openSubmenu($tabs_group.find('> .ubermenu-tab').first(), 'tab default', plugin); 1006 } 1007 } 1008 1009 //Close all others? .ubermenu( 'closeSubmenu' , $current_tabs.siblings() ); 1010 1011 }); 1012 }, 1013 1014 clearTabSizes: function (plugin) { 1015 plugin = plugin || this; 1016 plugin.$ubermenu.find('.ubermenu-submenu , .ubermenu-tabs , .ubermenu-tab-content-panel , .ubermenu-tabs-group').css('min-height', ''); 1017 }, 1018 1019 1020 sizeTabs: function () { 1021 1022 var plugin = this; 1023 var responsive = plugin.settings.responsive && (window.innerWidth <= plugin.settings.breakpoint) ? true : false;
1024 1025 //If this is responsive and we're below the breakpoint, don't size the tabs 1026 if (responsive) return; 1027 1028 plugin.initializeActiveTab(plugin); 1029 1030 //Do each tabs block 1031 plugin.$tab_blocks.each(function () { 1032 1033 //Stacked is if tabs are (a) Layout: Top, (b) Layout: Bottom, (c) Responsive Mode 1034 var stacked = false; 1035 1036 if (($(this).hasClass('ubermenu-tab-layout-top') || 1037 $(this).hasClass('ubermenu-tab-layout-bottom')) && 1038 !responsive) { 1039 1040 stacked = true; 1041 } 1042 1043 $(this).data('um-stacked', stacked); 1044 1045 var maxh = 0; 1046 1047 1048 var $tree; 1049 if (responsive) { 1050 $tree = $(this).parentsUntil('.ubermenu').add($(this).parents('.ubermenu')); 1051 } 1052 else { 1053 $tree = $(this).parentsUntil('.ubermenu-item-level-0'); 1054 } 1055 $tree.addClass('ubermenu-test-dimensions'); 1056 1057 //Find the maximum panel height for this group - only immediate tab panels, not nested 1058 var $panels = $(this).find(' > .ubermenu-tabs-group > .ubermenu-tab > .ubermenu-tab-content-panel'); 1059 var oh; 1060 $panels.each(function () { 1061 $(this).addClass('ubermenu-test-dimensions'); 1062 oh = $(this).outerHeight(); //outerheight 1063 if (oh > maxh) maxh = oh; 1064 $(this).data('um-oh', oh); 1065 $(this).removeClass('ubermenu-test-dimensions'); 1066 }); 1067 1068 $(this).data('um-max-panel-height', maxh); 1069 1070 //if( $(this).is( '#menu-item-185' ) ){ 1071 if ($(this).hasClass('ubermenu-tabs-dynamic-sizing')) { 1072 plugin.sizeTabsDynamic($(this), false); 1073 } 1074 else { 1075 plugin.sizeTabsMax($(this)); 1076 } 1077 1078 $tree.removeClass('ubermenu-test-dimensions'); 1079 1080 }); 1081 1082 }, 1083 1084 sizeTabsMax: function ($tab_block) { 1085 1086 var maxh = $tab_block.data('um-max-panel-height'); 1087 var stacked = $tab_block.data('um-stacked'); 1088 var $tabsgroup = $tab_block.find('> .ubermenu-tabs-group'); 1089 1090 //If left or right layout, set min-height on tabs group as well 1091 if (!stacked) { 1092 if ($tabsgroup.outerHeight() > maxh) { 1093 maxh = $tab_block.outerHeight(); 1094 } 1095 $tabsgroup.css('min-height', maxh); 1096 } 1097 //If top or bottom layout, set height for entire block 1098 else { 1099 //console.log( maxh , $tabsgroup.outerHeight() ); 1100 $tab_block.css('min-height', maxh + $tabsgroup.outerHeight()); 1101 } 1102 1103 //var $sub = $tab_block.closest( '.ubermenu-submenu-drop' ); 1104 //$sub.css( 'min-height' , false ); //this line does nothing, false is not valid - use '' to unset 1105 1106 //Set panel heights 1107 $tabsgroup.find('> .ubermenu-tab > .ubermenu-tab-content-panel').css('min-height', maxh); 1108 1109 }, 1110 1111 sizeTabsDynamic: function ($tab_block, animate) { 1112 1113 if (animate === undefined) animate = true; 1114 if (animate) { 1115 animate = $tab_block.hasClass('ubermenu-tabs-dynamic-sizing-animate'); 1116 } 1117 1118 var plugin = this; 1119 var responsive = plugin.settings.responsive && (window.innerWidth <= plugin.settings.breakpoint) ? true : false; 1120 if (responsive) return; //don't size when mobile 1121 1122 var stacked = $tab_block.data('um-stacked'); 1123 1124 //Look at the tabs group for the active item 1125 var $tabsgroup = $tab_block.find('> .ubermenu-tabs-group'); 1126 var prev_height = $tabsgroup.outerHeight(); 1127 $tabsgroup.css('min-height', '0'); //reset height 1128 var $active_panel = $tabsgroup.find('> .ubermenu-active > .ubermenu-tab-content-panel'); 1129 var active_oh = $active_panel.data('um-oh'); 1130 var tg_oh = $tabsgroup.outerHeight(); 1131 var maxh = tg_oh > active_oh ? $tab_block.outerHeight() : active_oh; //get the max height of the tabs group vs active tab 1132 1133 if (!stacked) { 1134 if (animate) { 1135 $tabsgroup.css('min-height', prev_height); 1136 $tabsgroup.stop().animate({ 'min-height': maxh }, 300, 'swing', function () { 1137 $active_panel.css('overflow', 'auto'); //force repaint for background images 1138 }); //set the tabs group height 1139 } 1140 else { 1141 $tabsgroup.css('min-height', maxh); //set the tabs group height 1142 } 1143 1144 } 1145 //If top or bottom layout, set height for entire block 1146 else { 1147 if (animate) { 1148 $tab_block.stop().animate({ 'min-height': maxh + $tabsgroup.outerHeight() }, 300, 'swing', function () { 1149 $active_panel.css('overflow', 'auto'); 1150 }); 1151 } 1152 else { 1153 $tab_block.css('min-height', maxh + $tabsgroup.outerHeight()); 1154 } 1155 } 1156 }, 1157 1158 initializeSegmentCurrentStates: function () { 1159 this.$ubermenu.find('.ubermenu-current-menu-item').first().parents('.ubermenu-item:not( .ubermenu-nocurrent )').addClass('ubermenu-current-menu-ancestor'); 1160 }, 1161 1162 disableTransitions: function () { 1163 this.transitions = false;
1164 this.$ubermenu 1165 .removeClass('ubermenu-transition-slide') 1166 .removeClass('ubermenu-transition-fade') 1167 .removeClass('ubermenu-transition-shift') 1168 .addClass('ubermenu-no-transitions') 1169 .addClass('ubermenu-transition-none'); 1170 }, 1171 1172 toggleMobileClass: function (on) { 1173 1174 if (on === undefined) { 1175 on = this.isMobile(); 1176 } 1177 1178 if (on) { 1179 this.$ubermenu.removeClass('ubermenu-desktop-view').addClass('ubermenu-mobile-view'); 1180 } 1181 else { 1182 this.$ubermenu.removeClass('ubermenu-mobile-view').addClass('ubermenu-desktop-view'); 1183 } 1184 }, 1185 1186 /* Handlers */ 1187 1188 handleClicks: function (e, a, plugin) { 1189 1190 var $a = $(a); 1191 1192 //Kill any clicks flagged by touchend 1193 if ($a.data('ubermenu-killClick')) { 1194 e.preventDefault(); 1195 //e.stopPropagation(); 1196 plugin.log('killed click after touchend ', e); 1197 } 1198 1199 //Reset flag in any event 1200 //plugin.log( 'reset kill click' ); 1201 //$a.data( 'ubermenu-killClick' , false ); 1202 }, 1203 1204 handleTouchInteraction: function (e, target, plugin) { 1205 1206 // e.preventDefault(); //this would stop clicks, but also scroll, which is a problem 1207 e.stopPropagation(); 1208 1209 //If we're touching Windows, disable transitions 1210 if (e.type.indexOf('pointer') >= 0) plugin.disableTransitions(); 1211 1212 var $target = $(target); 1213 //Move to HandleTap 1214 // $target.data( 'ubermenu-killClick' , true ); //Prevent Clicks from being handled normally 1215 // $target.data( 'ubermenu-killHover' , true ); //Prevent hover from being handled normally 1216 // setTimeout( function(){ $target.data( 'ubermenu-killClick' , false ).data( 'ubermenu-killHover' , false ); } , 1000 ); 1217 $target.parent().off('mouseleave.mouse_intent_none'); //don't allow hover intent out if we're touching 1218 1219 plugin.log('touchstart ' + e.type + ' ' + $target.text(), e); 1220 1221 //Setup touch events 1222 //plugin.log( 'setup touch events' ); 1223 $target.on(plugin.touchEnd, function (e) { plugin.handleTap(e, this, plugin); }); 1224 $target.on(plugin.touchMove, function (e) { plugin.preventInteractionOnScroll(e, this, plugin); }); 1225 1226 1227 //Note original event points for comparison 1228 //Standard 1229 if (e.originalEvent.touches) { 1230 $target.data('ubermenu-startX', e.originalEvent.touches[0].clientX); 1231 $target.data('ubermenu-startY', e.originalEvent.touches[0].clientY); 1232 } 1233 //Microsoft 1234 else if (e.originalEvent.clientY) { 1235 var pos = $target.offset(); 1236 //$target.data( 'ubermenu-pos' , pos ); 1237 $target.data('ubermenu-startX', e.originalEvent.clientX); 1238 $target.data('ubermenu-startY', e.originalEvent.clientY); 1239 } 1240 // else if( e.changedTouches ){ 1241 // $target.data( 'ubermenu-startX' , e.changedTouches[0].pageX ); 1242 // $target.data( 'ubermenu-startY' , e.changedTouches[0].pageY ); 1243 // } 1244 }, 1245 1246 preventInteractionOnScroll: function (e, target, plugin) { 1247 1248 plugin.log('touchmove interaction ' + e.type, e); 1249 1250 var $target = $(target); 1251 1252 //Check to see if the touch points were close enough together to be considered a tap 1253 //If not, they were a move/scroll, so unbind the touch event handlers and don't trigger menu 1254 if (e.originalEvent.touches) { 1255 if (Math.abs(e.originalEvent.touches[0].clientX - $target.data('ubermenu-startX')) > plugin.settings.moveThreshold || 1256 Math.abs(e.originalEvent.touches[0].clientY - $target.data('ubermenu-startY')) > plugin.settings.moveThreshold) { 1257 plugin.log('Preventing interaction on scroll, reset handlers (standard)'); 1258 plugin.resetHandlers($target, 'preventScroll touches', plugin); 1259 } 1260 else { 1261 plugin.log('diff = ' + Math.abs(e.originalEvent.touches[0].clientY - $target.data('ubermenu-startY'))); 1262 } 1263 } 1264 else if (e.originalEvent.clientY) { 1265 var pos = $target.data(pos); 1266 if (Math.abs(e.originalEvent.clientX - $target.data('ubermenu-startX')) > plugin.settings.moveThreshold || 1267 Math.abs(e.originalEvent.clientY - $target.data('ubermenu-startY')) > plugin.settings.moveThreshold) { 1268 plugin.log('Preventing interaction on scroll, reset handlers (standard)'); 1269 plugin.resetHandlers($target, 'preventScroll client', plugin); 1270 } 1271 else { 1272 plugin.log('diff = ' + e.originalEvent.clientY + ' - ' + $target.data('ubermenu-startY') + ' = ' + Math.abs(e.originalEvent.clientY - $target.data('ubermenu-startY'))); 1273 } 1274 } 1275 /* 1276 else if( e.changedTouches ){ 1277 if( Math.abs( e.changedTouches[0].pageX - $target.data( 'ubermenu-startX' ) ) > plugin.settings.moveThreshold || 1278 Math.abs( e.changedTouches[0].pageY - $target.data( 'ubermenu-startY' ) ) > plugin.settings.moveThreshold ){ 1279 plugin.log( 'Preventing interaction on scroll, reset handlers (Windows)' ); 1280 plugin.resetHandlers( $target ); 1281 } 1282 }*/ 1283 else plugin.log('no touch points found!'); 1284 1285 }, 1286 1287 1288 /* Handle tap - a touch interaction that completed */ 1289 handleTap: function (e, target, plugin) { 1290 1291 e.preventDefault(); 1292 e.stopPropagation(); 1293 1294 var $target = $(target); //.parent(); 1295 1296 plugin.setInteractionMode('press'); 1297 1298 //Quit if this was triggered after regular hover (which happens on IE) 1299 //Kill any touches flagged by mouseenter 1300 if ($target.data('ubermenu-killTouch')) { 1301 plugin.log('kill tap'); 1302 e.preventDefault(); 1303 e.stopPropagation(); 1304 } 1305 else { 1306 1307 var $li = $target.parent(); 1308 1309 plugin.log('handleTap [' + $target.text() + ']', e.type); 1310 1311 //$target.data( 'ubermenu-killHover' , true ); 1312 1313 $target.data('ubermenu-killClick', true); //Prevent Clicks from being handled normally 1314 $target.data('ubermenu-killHover', true); //Prevent hover from being handled normally 1315 setTimeout(function () { $target.data('ubermenu-killClick', false).data('ubermenu-killHover', false); }, 1000); 1316 1317 // 1318 //Close up other submenus before proceeding 1319 // - except if we're below the mobile breakpoint in accordion format 1320 // 1321 if (!plugin.isAccordion() || plugin.isAccordionFoldingSingle()) { 1322 plugin.closeSubmenuInstantly($li.siblings('.ubermenu-active')); 1323 } 1324 1325 1326 // 1327 //Toggle Submenus 1328 // 1329 1330 if ($li.hasClass('ubermenu-has-submenu-drop')) { 1331 1332 //if submenu is already open, close it and allow link to be followed 1333 if ($li.hasClass('ubermenu-active')) { 1334 1335 //Don't close tabs, unless we're on mobile 1336 if (!$li.hasClass('ubermenu-tab') || plugin.isMobile()) { 1337 plugin.closeSubmenu($li, 'toggleUberMenuActive', plugin); 1338 } 1339 //plugin.followLink( e , $li , plugin ); 1340 plugin.handleLink(e, target, plugin, true); 1341 } 1342 //if submenu is closed, open the submenu, prevent link from being followed 1343 else { 1344 plugin.openSubmenu($li, 'toggle', plugin); 1345 } 1346 } 1347 1348 // 1349 //Follow links that don't have submenus 1350 // 1351 else { 1352 //plugin.followLink( e , $li , plugin ); 1353 plugin.handleLink(e, target, plugin, true); 1354 } 1355 } 1356 1357 //Reset flag in any event 1358 $target.data('ubermenu-killTouch', false); 1359 1360 //plugin.resetHandlers( $li ); 1361 //plugin.log( 'reset handlers' ); 1362 plugin.resetHandlers($target, 'handleTap', plugin); 1363 1364 }, 1365 1366 1367 handleLink: function (e, link, plugin, follow) { 1368 1369 follow = follow || false; 1370 1371 plugin.log('handleLink'); 1372 1373 //e.preventDefault(); 1374 1375 var $link = $(link); 1376 1377 if (!$link.is('a')) return; 1378 1379 var href = $link.attr('href'); 1380 1381 var scrolltarget = $link.data('ubermenu-scrolltarget'); 1382 1383 if (scrolltarget) { 1384 1385 var $target_el = $(scrolltarget).first(); 1386 if ($target_el.length > 0) { 1387 e.preventDefault(); 1388 $link.trigger('ubermenuscrollto'); 1389 var $li = $link.parent('.ubermenu-item'); 1390 $li.addClass('ubermenu-current-menu-item'); 1391 $li.siblings().removeClass('ubermenu-current-menu-item').removeClass('ubermenu-current-menu-parent').removeClass('uberemnu-current-menu-ancestor'); 1392 1393 var anim_done = false;
1394 $('html,body').animate({ 1395 scrollTop: $target_el.offset().top - plugin.settings.scrollto_offset 1396 }, plugin.settings.scrollto_duration, 'swing', 1397 function () { 1398 //after scroll 1399 if (!anim_done) { 1400 plugin.closeSubmenu($link.closest('.ubermenu-item-level-0'), 'handeLink', plugin); 1401 if (plugin.settings.collapse_after_scroll && !plugin.$ubermenu.hasClass('ubermenu-responsive-nocollapse')) plugin.toggleMenuCollapse('toggle', false, plugin); 1402 $link.trigger('ubermenuscrollto_complete'); 1403 anim_done = true; 1404 } 1405 }); 1406 return false; //don't follow any links if this scroll target is present 1407 } 1408 //if target isn't present here, redirect with hash 1409 else { 1410 if (href && href.indexOf('#') == -1) { //check that hash does not already exist 1411 if (scrolltarget.indexOf('#') == -1) { //if this is a class, add a hash tag 1412 scrolltarget = '#' + scrolltarget; 1413 } 1414 window.location = href + scrolltarget; //append hash/scroll target to URL and redirect 1415 e.preventDefault(); 1416 return false; // So that the click event doesn't fall through to the next if/else and redirect to the href without hash 1417 } 1418 //No href, no worries 1419 } 1420 } 1421 1422 if (!href) { 1423 e.preventDefault(); 1424 } 1425 1426 //Handle clicks (where default was already prevented) 1427 else { 1428 if (follow && e.isDefaultPrevented()) { 1429 plugin.log('default prevented, follow link'); 1430 if ($link.attr('target') == '_blank') { 1431 window.open(href, '_blank'); 1432 } 1433 else { 1434 window.location = href; 1435 } 1436 } 1437 } 1438 1439 }, 1440 1441 1442 handleMouseClick: function (e, target, plugin) { 1443 plugin.log('handleMouseClick', e); 1444 1445 //Stop link follow 1446 //e.preventDefault(); //defer to 'else' so that the second click can be handled naturally 1447 1448 var $target = $(target); 1449 1450 if ($target.data('ubermenu-killClick')) { 1451 plugin.log('handleMouseClick: killClick'); 1452 return; //3.0.4.1 1453 } 1454 1455 var $item = $target.parent('.ubermenu-item'); //$( li ); 1456 1457 if ($item.length) { 1458 //Check if this is already open 1459 if ($item.hasClass('ubermenu-active')) { 1460 1461 //If it's a link, follow 1462 if ($target.is('a')) { 1463 plugin.handleLink(e, target, plugin); 1464 } 1465 1466 //If it's not a link (and not a tab), retract 1467 if (!$item.hasClass('ubermenu-tab')) { 1468 plugin.closeSubmenu($item, 'retract'); 1469 } 1470 } 1471 1472 //Close other menus, open this one 1473 else { 1474 1475 plugin.setInteractionMode('press'); 1476 1477 //Submenu to open 1478 if ($item.hasClass('ubermenu-has-submenu-drop')) { 1479 e.preventDefault(); //Not already active, don't allow click 1480 // Close other submenus, if not on mobile in accordion mode 1481 if (!plugin.isAccordion() || plugin.isAccordionFoldingSingle()) { 1482 plugin.closeSubmenuInstantly($item.siblings('.ubermenu-active')); 1483 } 1484 plugin.openSubmenu($item, 'click', plugin); 1485 } 1486 //Allow link to be followed 1487 else { 1488 //Just let it go 1489 //plugin.handleLink( e , target , plugin ); 1490 } 1491 } 1492 } 1493 1494 //Only attach mouseout after mouseover, this way menus opened by touch won't be closed by mouseout 1495 //$( li ).on( 'mouseout.ubermenu-submenu-toggle' , function( e ){ plugin.handleMouseout( e , this , plugin ); } ); 1496 }, 1497 1498 1499 1500 1501 1502 1503 handleMouseIntent: function (e, item, plugin) { ///*target*/ 1504 1505 plugin.log('handleMouseIntent'); 1506 plugin.setInteractionMode('hover'); 1507 1508 //var $target = $( target ); 1509 //var $item = $target.parent( '.ubermenu-item' ); 1510 var $item = $(item); 1511 1512 //console.log( '[' + $item.find('> .ubermenu-target').text() + '] handle mouse_intent' ); 1513 1514 //Cancel Timer 1515 if ($item.data('mouse_intent_timer')) { 1516 $item.data('mouse_intent_timer', clearTimeout($item.data('mouse_intent_timer'))); 1517 //console.log( 'clear timeout' ); 1518 } 1519 1520 //Prevent Touch events, which will occur on IE 1521 // var $target = $item.find( '> .ubermenu-target' ); 1522 // $target.data( 'ubermenu-killTouch' , true ); 1523 // setTimeout( function(){ $target.data( 'ubermenu-killTouch' , false ); } , 1000 ); 1524 1525 //Quit if this was triggered by touch 1526 var $a = $item.find('.ubermenu-target'); 1527 //Kill any hovers flagged by touchstart 1528 if ($a.data('ubermenu-killHover')) { 1529 plugin.log('killHover MouseIntent'); 1530 e.preventDefault(); 1531 e.stopPropagation(); 1532 return; 1533 } 1534 1535 pX = e.pageX; pY = e.pageY; 1536 1537 1538 $item.on('mousemove.mouse_intent', plugin.trackMouse); 1539 1540 $item.data('mouse_intent_timer', setTimeout( 1541 function () { 1542 plugin.compare(e, $item, plugin.handleMouseIntentSuccess, plugin); 1543 }, 1544 plugin.settings.intent_interval 1545 )); 1546 1547 //Cancel if out - since we only hook up the close submenu event after a successfull intent 1548 $item.on('mouseleave.mouse_intent_none', function () { 1549 //plugin.log( 'mouseleave' ); 1550 $(this).data('mouse_intent_timer', clearTimeout($(this).data('mouse_intent_timer'))); 1551 $item.data('mouse_intent_state', 0); 1552 $item.off('mouseleave.mouse_intent_none'); 1553 1554 if ($a.data('ubermenu-killHover')) { 1555 plugin.log('killHover MouseIntent_Cancel'); 1556 e.preventDefault(); 1557 e.stopPropagation(); 1558 return; 1559 } 1560 1561 plugin.closeSubmenu($item, 'mouse_intent_cancel', plugin); 1562 }); 1563 //} 1564 //else console.log( 'STATE 1' ); 1565 1566 1567 }, 1568 1569 handleMouseIntentSuccess: function (e, $item, plugin) { 1570 1571 plugin.log('handleMouseIntentSuccess'); 1572 1573 //Cancel the quickleave event 1574 $item.off('mouseleave.mouse_intent_none'); 1575 1576 //Quit if this was triggered by touch - on Android Stock, this is triggered after touch, but mouseenter is triggered before 1577 var $a = $item.find('.ubermenu-target'); 1578 //Kill any hovers flagged by touchstart 1579 if ($a.data('ubermenu-killHover')) { 1580 plugin.log('Kill hover on IntentSuccess'); 1581 e.preventDefault(); 1582 e.stopPropagation(); 1583 return; 1584 } 1585 //Reset flag in any event 1586 $a.data('ubermenu-killHover', false); 1587 1588 1589 plugin.triggerSubmenu(e, $item, plugin); 1590 1591 //Setup mouseleave, but not for tabs, except below breakpoint 1592 if (!$item.hasClass('ubermenu-tab') || window.innerWidth <= plugin.settings.breakpoint) { 1593 $item.on('mouseleave.mouse_intent', function (e) { plugin.handleMouseIntentLeave(e, this, plugin); }); 1594 } 1595 }, 1596 1597 handleMouseIntentLeave: function (e, item, plugin) { 1598 1599 //var $target = $( target ); 1600 //var $item = $target.parent( '.ubermenu-item' ); 1601 var $item = $(item); 1602 1603 //console.log( '[' + $item.find('> .ubermenu-target').text() + '] handle mouse_intent LEAVE' ); 1604 1605 //Cancel timer 1606 if ($item.data('mouse_intent_timer')) { 1607 $item.data('mouse_intent_timer', clearTimeout($item.data('mouse_intent_timer'))); 1608 //console.log( 'clear timeout' ); 1609 } 1610 1611 $item.off('mousemove.mouse_intent', plugin.trackMouse); 1612 //$item.off( 'mouseleave.mouse_intent' ); //special 1613 1614 if ($item.data('mouse_intent_state') == 1) { 1615 //$item.data( 'mouse_intent_state' , 0 ); 1616 $item.data('mouse_intent_timer', setTimeout( 1617 function () { 1618 plugin.delayMouseLeave(e, $item, plugin.handleMouseIntentLeaveSuccess, plugin); 1619 }, 1620 plugin.settings.intent_delay 1621 )); 1622 } 1623 1624 }, 1625 1626 handleMouseIntentLeaveSuccess: function (e, $el, plugin) { 1627 $el.off('mouseleave.mouse_intent'); //special 1628 if ($el.find('> .ubermenu-target').data('ubermenu-killHover')) return; 1629 plugin.closeSubmenu($el, 'mouse_intent_leave', plugin); 1630 1631 }, 1632 1633 delayMouseLeave: function (e, $el, func, plugin) { 1634 //console.log( 'delay mouse leave ' ); 1635 $el.data('mouse_intent_timer', clearTimeout($el.data('mouse_intent_timer'))); 1636 $el.data('mouse_intent_state', 0); 1637 return func.apply($el, [e, $el, plugin]); 1638 }, 1639 1640 trackMouse: function (e) { 1641 cX = e.pageX; 1642 cY = e.pageY; 1643 }, 1644 1645 compare: function (e, $el, func, plugin) { 1646 1647 //console.log( 'compare' ); 1648 1649 $el.data('mouse_intent_timer', clearTimeout($el.data('mouse_intent_timer'))); 1650 1651 // compare mouse positions to see if they've crossed the threshold 1652 if ((Math.abs(pX - cX) + Math.abs(pY - cY)) < plugin.settings.intent_threshold) { 1653 //console.log( 'threshold met' ); 1654 $el.off('mousemove.mouse_intent', plugin.track); 1655 // set hoverIntent state to true (so mouseOut can be called) 1656 $el.data('mouse_intent_state', 1); 1657 return func.apply($el, [e, $el, plugin]); 1658 } else { 1659 //console.log( 'keep polling' );
1660 // set previous coordinates for next time 1661 pX = cX; pY = cY; 1662 // use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs) 1663 $el.data('mouse_intent_timer', setTimeout(function () { plugin.compare(e, $el, func, plugin); }, plugin.settings.intent_interval)); 1664 } 1665 }, 1666 1667 1668 1669 1670 1671 triggerSubmenu: function (e, $item, plugin) { 1672 plugin.closeSubmenuInstantly($item.siblings('.ubermenu-active, .ubermenu-in-transition')); 1673 plugin.openSubmenu($item, 'mouseenter', plugin); 1674 }, 1675 1676 1677 1678 1679 handleMouseover: function (e, target, plugin) { 1680 1681 if (plugin.events_disabled) return; 1682 plugin.setInteractionMode('hover'); 1683 1684 var $target = $(target); 1685 1686 $target.data('ubermenu-killTouch', true); 1687 setTimeout(function () { $target.data('ubermenu-killTouch', false); }, 1000); 1688 1689 plugin.log('handleMouseenter, add mouseleave', e); 1690 1691 var $item = $target.parent('.ubermenu-item'); //$( li ); 1692 1693 if ($item.length) { 1694 if (!$item.hasClass('ubermenu-active')) { 1695 1696 plugin.triggerSubmenu(e, $item, plugin); 1697 1698 //Set up mouseleave event, but no hover out for Tabs 1699 if (!$item.hasClass('ubermenu-tab') || window.innerWidth <= plugin.settings.breakpoint) { 1700 $item.on('mouseleave.ubermenu-submenu-toggle', function (e) { plugin.handleMouseleave(e, this, plugin); }); 1701 } 1702 } 1703 } 1704 }, 1705 1706 //Mouseleave should be for LI so that we can use subs 1707 handleMouseleave: function (e, li, plugin) { 1708 1709 plugin.log('handleMouseleave, remove mouseleave', e); 1710 1711 $(li).off('mouseleave.ubermenu-submenu-toggle'); //Unbind mouseout event, it'll be rebind next mouseover 1712 1713 plugin.closeSubmenu($(li), 'mouseout'); 1714 }, 1715 1716 1717 1718 handleSubmenuRetractorStart: function (e, li, plugin) { 1719 e.preventDefault(); 1720 e.stopPropagation(); 1721 1722 $(li).on(plugin.touchEnd, function (e) { plugin.handleSubmenuRetractorEnd(e, this, plugin); }); 1723 1724 plugin.log('handleSubmenuRetractorStart ' + $(li).text()); 1725 1726 }, 1727 1728 handleSubmenuRetractorEnd: function (e, li, plugin) { 1729 e.preventDefault(); 1730 e.stopPropagation(); 1731 1732 var $parent_item = $(li).closest('.ubermenu-item'); 1733 plugin.closeSubmenu($parent_item, 'handleSubmenuRetractor'); 1734 1735 $(li).off(plugin.touchEnd); 1736 1737 plugin.log('handleSubmenuRetractorEnd ' + $parent_item.find('> .ubermenu-target').text()); 1738 return false; 1739 1740 }, 1741 1742 1743 handleResponsiveToggle: function (e, toggle, plugin) { 1744 1745 plugin.log('handleResponsiveToggle ' + e.type, e); 1746 1747 e.preventDefault(); 1748 e.stopPropagation(); 1749 1750 //Prevent Double-fire 1751 //some browsers fire click and touch, so prevent the click manually 1752 if (e.type == 'touchend') { 1753 plugin.$ubermenu.data('ubermenu-prevent-click', true); 1754 setTimeout(function () { 1755 plugin.$ubermenu.data('ubermenu-prevent-click', false); 1756 }, 500); 1757 } 1758 else if (e.type == 'click' && plugin.$ubermenu.data('ubermenu-prevent-click')) { 1759 plugin.$ubermenu.data('ubermenu-prevent-click', false); 1760 return; 1761 } 1762 1763 //TODO just call off/on click.xyz? or binding already fired? 1764 //Or should we just have a state set that returns, independent of event type 1765 1766 plugin.toggleMenuCollapse('toggle', toggle, plugin); 1767 1768 }, 1769 1770 handleTouchoffCloseStart: function (e, _this, plugin) { 1771 plugin.touchoffclosestart = $(window).scrollTop(); 1772 }, 1773 1774 handleTouchoffClose: function (e, _this, eventtype, plugin) { 1775 //Only fire if the touch event occurred outside the menu AND we've clicked OR we've touched but haven't scrolled 1776 if (!$(e.target).closest('.ubermenu').length && (eventtype == 'click' || plugin.touchoffclosestart == $(window).scrollTop())) { 1777 1778 plugin.log('touchoff close ', e); 1779 1780 //If there are any submenus to close, close them and temporarily disable hover events 1781 if (plugin.closeAllSubmenus()) { 1782 //console.log( plugin.settings.submenuAnimationDuration ); 1783 //temporarily disable hovering so that the mouseover event doesn't fire and reopen the menu 1784 plugin.disableSubmenuToggleMouseEvents(); 1785 window.setTimeout(function () { 1786 //plugin.initializeSubmenuToggleMouseEvents( plugin ); 1787 plugin.reenableSubmenuToggleMouseEvents(plugin); 1788 }, 1789 plugin.settings.submenuAnimationDuration); 1790 1791 //can't call /e.preventDefault(); because it will stop mouseover events from firing 1792 } 1793 } 1794 }, 1795 1796 1797 1798 1799 /* Controllers */ 1800 1801 toggleMenuCollapse: function (action, toggle, plugin) { 1802 1803 plugin = plugin || this; 1804 toggle = toggle || '.ubermenu-resposive-toggle'; 1805 //console.log( toggle + '[data-ubermenu-target="'+ plugin.$ubermenu.attr( 'id' ) + '"]' ); 1806 var $toggle; 1807 //Passed regular Object 1808 if (typeof toggle == 'object') { 1809 $toggle = $(toggle); 1810 } 1811 //Passed selector string 1812 else { 1813 $toggle = $(toggle + '[data-ubermenu-target="' + plugin.$ubermenu.attr('id') + '"]'); 1814 } 1815 1816 action = action || 'toggle'; 1817
1818 if (action == 'toggle') { 1819 if (plugin.$ubermenu.hasClass('ubermenu-responsive-collapse')) { 1820 action = 'open'; 1821 } 1822 else { 1823 action = 'close'; 1824 } 1825 } 1826 1827 //plugin.$ubermenu.slideToggle(); 1828 1829 var $allToggles = $('.ubermenu-responsive-toggle[data-ubermenu-target=' + plugin.$ubermenu.attr('id') + ']'); 1830 1831 if (action == 'open') { 1832 plugin.$ubermenu.removeClass('ubermenu-responsive-collapse').trigger('ubermenutoggledopen'); 1833 $allToggles.trigger('ubermenutoggledopen'); 1834 $allToggles.toggleClass('ubermenu-responsive-toggle-open'); 1835 1836 if (plugin.modalMobile) plugin.trapModalFocus($toggle); 1837 1838 if (plugin.settings.aria_responsive_toggle) { 1839 $allToggles.attr('aria-expanded', true); 1840 } 1841 } 1842 // Collapse menu 1843 else { 1844 plugin.$ubermenu.addClass('ubermenu-responsive-collapse').trigger('ubermenutoggledclose'); 1845 $allToggles.trigger('ubermenutoggledclose'); 1846 $allToggles.toggleClass('ubermenu-responsive-toggle-open'); 1847 1848 // Close open submenus 1849 var $actives = this.$ubermenu.find('.ubermenu-active'); 1850 if ($actives.length) this.closeSubmenuInstantly($actives); 1851 1852 if (plugin.settings.aria_responsive_toggle) { 1853 $allToggles.attr('aria-expanded', false); 1854 } 1855 } 1856 //plugin.$ubermenu.toggleClass( 'ubermenu-responsive-collapse' ); 1857 //$toggle.trigger( 'ubermenutoggled' ); 1858 1859 if (plugin.transitions && !plugin.$ubermenu.hasClass('ubermenu-responsive-nocollapse')) { 1860 plugin.$ubermenu.addClass('ubermenu-in-transition'); 1861 plugin.$ubermenu.on(plugin.transitionend + '_toggleubermenu', function () { 1862 plugin.$ubermenu.removeClass('ubermenu-in-transition'); 1863 plugin.$ubermenu.off(plugin.transitionend + '_toggleubermenu'); 1864 }); 1865 } 1866 }, 1867 1868 trapModalFocus: function ($toggle) { 1869 var plugin = this; 1870 var focusableElements = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'; 1871 var all = this.$ubermenu.find(focusableElements); 1872 this.firstFocus = all.first()[0]; 1873 this.lastFocus = all.last()[0]; 1874 1875 var checkFocus = this.checkModalFocusTab.bind(this); 1876 document.addEventListener('keydown', checkFocus); 1877 setTimeout(function () { 1878 plugin.firstFocus.focus(); 1879 }, 100); 1880 plugin.$ubermenu.on('ubermenutoggledclose', function () { 1881 document.removeEventListener('keydown', checkFocus); 1882 this.firstFocus = false; 1883 this.lastFocus = false; 1884 // Return focus to toggle 1885 $toggle[0].focus(); 1886 }); 1887 }, 1888 checkModalFocusTab: function (e) { 1889 if (e.key === 'Tab' || e.keyCode === 9) { 1890 if (e.shiftKey) { 1891 if (document.activeElement === this.firstFocus) { 1892 this.lastFocus.focus(); 1893 console.log('focus force to last'); 1894 e.preventDefault(); 1895 } 1896 } 1897 else if (document.activeElement === this.lastFocus) { 1898 this.firstFocus.focus(); 1899 console.log('focus force to first'); 1900 e.preventDefault(); 1901 } 1902 } 1903 }, 1904 1905 1906 positionSubmenus: function () { 1907 1908 var plugin = this; 1909 if (plugin.orientation == 'h') { 1910 1911 // Position vertically when in flex mode 1912 if (plugin.vstretch) { 1913 // Set submenu vertical position if submenu is not bounded by menu bar or inner menu bar 1914 if ( 1915 plugin.$ubermenu.css('position') === 'static' && 1916 plugin.$ubermenu.find('> .ubermenu-nav').css('position') === 'static' && 1917 !plugin.isMobile() 1918 ) { 1919 1920 // Does the main bar have a border we need to account for? 1921 var borderProp = plugin.inverted ? 'border-top-width' : 'border-bottom-width'; 1922 var addBorder = plugin.$ubermenu.css(borderProp) !== '0px'; 1923 1924 plugin.$ubermenu.find('.ubermenu-item-level-0 > .ubermenu-submenu-drop').each(function () { 1925 if (plugin.inverted) { 1926 // bottom:auto is overridden by top:auto, so we have to translate the submenu instead 1927 $(this).css({ 'transform': 'translateY(-100%)', 'bottom': 'auto', 'marginTop': addBorder ? '-1px' : 0 }); 1928 } 1929 else { 1930 var itemHeight = $(this).parent().outerHeight(); 1931 if (addBorder) itemHeight++; // Shift an extra pixel 1932 $(this).css({ 'top': 'auto', 'marginTop': itemHeight }); 1933 } 1934 }); 1935 } 1936 // Remove top positioning otherwise 1937 else { 1938 if (plugin.inverted) { 1939 plugin.$ubermenu.find('.ubermenu-item-level-0 > .ubermenu-submenu-drop').css({ 'transform': '', 'marginTop': '', 'bottom': '' }); 1940 } 1941 else { 1942 plugin.$ubermenu.find('.ubermenu-item-level-0 > .ubermenu-submenu-drop').css({ 'marginTop': '', 'top': '' }); 1943 } 1944 } 1945 } 1946 1947 // Center submenus 1948 plugin.$ubermenu.find('.ubermenu-submenu-drop.ubermenu-submenu-align-center').each(function () { 1949 1950 var $parent = $(this).parent('.ubermenu-item'); 1951 var $sub = $(this); 1952 var $container; 1953 if (plugin.$ubermenu.hasClass('ubermenu-bound')) { 1954 $container = $parent.closest('.ubermenu , .ubermenu-submenu'); // main menu bar 1955 } 1956 else if (plugin.$ubermenu.hasClass('ubermenu-bound-inner')) { 1957 $container = $parent.closest('.ubermenu-nav , .ubermenu-submenu'); // inner menu bar 1958 } 1959 else { 1960 var $parent_sub = $parent.closest('.ubermenu-submenu'); 1961 1962 //Find the closest relatively positioned element 1963 if ($parent_sub.length === 0) { 1964 $container = plugin.$ubermenu.offsetParent(); 1965 if (!$container) $container = $('body'); //If no offset parent, assume bounds of body tag 1966 } 1967 else { 1968 $container = $parent_sub; 1969 } 1970 } 1971 1972 var sub_width = $sub.outerWidth(); 1973 1974 var parent_width = $parent.outerWidth(); 1975 var parent_left_edge = $parent.offset().left; 1976 1977 var container_width = $container.width(); 1978 var container_left_edge = $container.offset().left; 1979 1980 // console.log( $container ); 1981 // console.log( 'parent_width: ' + parent_width ); 1982 // console.log( 'parent_left: ' + parent_left_edge ); 1983 // console.log( 'container_width: ' + container_width ); 1984 // console.log( 'container_left: ' + container_left_edge ); 1985 1986 1987 var center_left = (parent_left_edge + (parent_width / 2)) - (container_left_edge + (sub_width / 2)); 1988 1989 //If submenu is left of container edge, align left 1990 var left = center_left > 0 ? center_left : 0; 1991 1992 //If submenu width is larger than container width, center to container (menu bar) 1993 if (sub_width > container_width) { 1994 left = (sub_width - container_width) / -2; 1995 } 1996 //If submenu is right of container edge, align right 1997 else if (left + sub_width > container_width) { 1998 //left = container_width - sub_width; 1999 $sub.css({ 'right': 0, 'left': 'auto' }); 2000 left = false;
2001 } 2002 2003 if (left !== false) { 2004 $sub.css('left', left); 2005 } 2006 }); 2007 } 2008 }, 2009 2010 2011 openSubmenu: function ($li, tag, plugin) { 2012 2013 plugin = plugin || this; 2014 2015 plugin.log('Open Submenu ' + tag); 2016 2017 if (!$li.hasClass('ubermenu-active')) { 2018 $li.addClass('ubermenu-active'); 2019 2020 //ARIA 2021 if (plugin.settings.aria_expanded) { 2022 $li.find('>.ubermenu-target').attr('aria-expanded', 'true'); 2023 } 2024 if (plugin.settings.aria_hidden) { 2025 $li.find('>.ubermenu-submenu').attr('aria-hidden', 'false'); 2026 } 2027 2028 if (plugin.transitions) { 2029 $li.addClass('ubermenu-in-transition'); 2030 2031 $li.find('> .ubermenu-submenu').on(plugin.transitionend + '_opensubmenu', function () { 2032 plugin.log('finished submenu open transition'); 2033 $li.removeClass('ubermenu-in-transition'); 2034 $(this).off(plugin.transitionend + '_opensubmenu'); 2035 }); 2036 } 2037 $li.trigger('ubermenuopen'); 2038 } 2039 }, 2040 2041 closeSubmenu: function ($li, tag, plugin) { 2042 2043 plugin = plugin || this; 2044 2045 plugin.log('closeSubmenu ' + $li.find('>a').text() + ' [' + tag + ']'); 2046 2047 //If this menu is currently active and has a submenu, close it 2048 if ($li.hasClass('ubermenu-item-has-children') && $li.hasClass('ubermenu-active')) { 2049 2050 if (plugin.transitions) { 2051 $li.addClass('ubermenu-in-transition'); //transition class keeps visual flag until transition completes 2052 } 2053 2054 $li.each(function () { 2055 var _$li = $(this); 2056 var _$ul = _$li.find('> .ubermenu-submenu'); 2057 2058 //Remove the transition flag once the transition is completed 2059 if (plugin.transitions) { 2060 _$ul.on(plugin.transitionend + '_closesubmenu', function () { 2061 plugin.log('finished submenu close transition'); 2062 _$li.removeClass('ubermenu-in-transition'); 2063 _$ul.off(plugin.transitionend + '_closesubmenu'); 2064 }); 2065 } 2066 }); 2067 } 2068 2069 //Actually remove the active class, which causes the submenu to close 2070 $li.removeClass('ubermenu-active'); 2071 $li.trigger('ubermenuclose'); 2072 2073 //ARIA 2074 if (plugin.settings.aria_expanded) { 2075 $li.find('>.ubermenu-target').attr('aria-expanded', 'false'); 2076 } 2077 if (plugin.settings.aria_hidden) { 2078 $li.find('>.ubermenu-submenu').attr('aria-hidden', 'true'); 2079 } 2080 2081 }, 2082 2083 //Close subs without transition 2084 closeSubmenuInstantly: function ($li) { 2085 if ($li.length === 0) return; 2086 var plugin = this; 2087 $li.addClass('ubermenu-notransition'); 2088 $li.removeClass('ubermenu-active').removeClass('ubermenu-in-transition'); 2089 $li[0].offsetHeight; //triggers reflow 2090 $li.removeClass('ubermenu-notransition'); 2091 $li.trigger('ubermenuclose'); 2092 2093 //ARIA 2094 if (plugin.settings.aria_expanded) { 2095 $li.find('>.ubermenu-target,>.ubermenu-submenu').attr('aria-expanded', 'false'); 2096 } 2097 if (plugin.settings.aria_hidden) { 2098 $li.find('>.ubermenu-submenu').attr('aria-hidden', 'true'); 2099 } 2100 }, 2101 2102 //Top level subs only 2103 closeAllSubmenus: function () { 2104 //$( this.element ).find( 'li.menu-item-has-children' ).removeClass( 'ubermenu-active' ); 2105 var $actives = this.$ubermenu.find('.ubermenu-item-level-0.ubermenu-active'); 2106 if ($actives.length) this.closeSubmenuInstantly($actives); 2107 return $actives.length; 2108 }, 2109 2110 resetHandlers: function ($target, tag, plugin) { 2111 plugin.log('ResetHandlers: ' + tag); 2112 $target.off(this.touchEnd); 2113 $target.off(this.touchMove); 2114 2115 var $item = $target.parent(); 2116 $item.off('mousemove.mouse_intent'); 2117 $item.off('mouseleave.mouse_intent_none'); 2118 $item.data('mouse_intent_timer', clearTimeout($item.data('mouse_intent_timer'))); 2119 $item.data('mouse_intent_state', 0); 2120 }, 2121 2122 isMobile: function () { 2123 return window.innerWidth <= this.settings.breakpoint; 2124 }, 2125 isTablet: function () { 2126 return window.innerWidth <= this.settings.breakpoint && window.innerWidth > 480; 2127 }, 2128 //Currently in accordion mode? 2129 isAccordion: function () { 2130 var plugin = this; 2131 return plugin.mobileAccordion && 2132 ( 2133 window.innerWidth <= 480 // mobile single column 2134 || 2135 plugin.isTablet() && plugin.$ubermenu.hasClass('ubermenu-responsive-single-column') // tablet single column 2136 ) 2137 2138 }, 2139 isAccordionFoldingSingle: function () { 2140 return this.mobileAccordionFolding === 'single'; 2141 }, 2142 2143 //hover or press 2144 setInteractionMode: function (mode) { 2145 // If it changed 2146 if (this.interactionMode !== mode) { 2147 this.$ubermenu 2148 .removeClass('ubermenu-interaction-' + this.interactionMode) 2149 .addClass('ubermenu-interaction-' + mode); 2150 this.interactionMode = mode; 2151 } 2152 }, 2153 2154 2155 log: function (content, o, plugin) { 2156 plugin = plugin || this; 2157 2158 if (plugin.settings.debug) { 2159 if (plugin.settings.debug_onscreen) { 2160 this.debug_target.prepend('<div class="um-debug-content">' + content + '</div>'); 2161 } 2162 else console.log(content, o); 2163 } 2164 } 2165 }; 2166 2167 /* 2168 $.fn[ pluginName ] = function ( options ) { 2169 return this.each(function() { 2170 if ( !$.data( this, "plugin_" + pluginName ) ) { 2171 $.data( this, "plugin_" + pluginName, new Plugin( this, options ) ); 2172 } 2173 }); 2174 }; 2175 */ 2176 2177 $.fn[pluginName] = function (options) { 2178 2179 var args = arguments; 2180 2181 if (options === undefined || typeof options === 'object') { 2182 return this.each(function () { 2183 if (!$.data(this, "plugin_" + pluginName)) { 2184 $.data(this, "plugin_" + pluginName, new Plugin(this, options)); 2185 } 2186 }); 2187 } 2188 else if (typeof options === 'string' && options[0] !== '_' && options !== 'init') { 2189 // Cache the method call to make it possible to return a value 2190 var returns; 2191 2192 this.each(function () { 2193 var instance = $.data(this, 'plugin_' + pluginName); 2194 2195 // Tests that there's already a plugin-instance and checks that the requested public method exists 2196 if (instance instanceof Plugin && typeof instance[options] === 'function') { 2197 2198 // Call the method of our plugin instance, and pass it the supplied arguments. 2199 returns = instance[options].apply(instance, Array.prototype.slice.call(args, 1)); 2200 } 2201 2202 // Allow instances to be destroyed via the 'destroy' method 2203 if (options === 'destroy') { 2204 $.data(this, 'plugin_' + pluginName, null); 2205 } 2206 }); 2207 2208 // If the earlier cached method gives a value back return the value, otherwise return this to preserve chainability. 2209 return returns !== undefined ? returns : this; 2210 } 2211 }; 2212 2213})(jQuery, window, document); 2214 2215 2216 2217(function ($) { 2218 2219 var ubermenu_is_initialized = false;
2220 2221 //jQuery( document ).ready( function($){ 2222 jQuery(function ($) { 2223 initialize_ubermenu('document.ready'); 2224 }); 2225 2226 //Backup 2227 $(window).on('load', function () { 2228 initialize_ubermenu('window.load'); 2229 }); 2230 2231 function initialize_ubermenu(init_point) { 2232 2233 if (ubermenu_is_initialized) return; 2234 2235 ubermenu_is_initialized = true; 2236 2237 // if( ( typeof console != "undefined" ) && init_point == 'window.load' ) console.log( 'Notice: UberMenu initialized via ' + init_point + '. This indicates that an unrelated error on the site prevented it from loading via the normal document ready event.' ); 2238 2239 //Scroll to non-ID "hashes" 2240 if (window.location.hash.substring(1, 2) == '.') { 2241 var $scrollTarget = $('body').find(window.location.hash.substring(1)); 2242 if ($scrollTarget.length) { 2243 window.scrollTo(0, $scrollTarget.offset().top - ubermenu_data.scrollto_offset); 2244 $('.ubermenu').find('.ubermenu-target[data-ubermenu-scrolltarget="' + window.location.hash.substring(1) + '"]').parent().addClass('ubermenu-current-menu-item'); 2245 } 2246 } 2247 //Make sure offset is applied to normal hashes 2248 else if (window.location.hash.length) { 2249 setTimeout(function () { 2250 try { 2251 var $scrollTarget = $('body').find(window.location.hash); 2252 if ($scrollTarget.length) { 2253 window.scrollTo(0, $scrollTarget.offset().top - ubermenu_data.scrollto_offset); 2254 $('.ubermenu').find('.ubermenu-target[data-ubermenu-scrolltarget="' + window.location.hash + '"]').parent().addClass('ubermenu-current-menu-item'); 2255 } 2256 } 2257 catch (error) { 2258 //not an element identifier, don't scroll 2259 } 2260 }, 100); 2261 } 2262 2263 2264 //Clean out empty items left by unbalanced dynamic items 2265 $('.ubermenu-item:empty').each(function () { //find all empty items 2266 var $p = $(this).parent(); //get parent UL 2267 $(this).remove(); //remove the empty item 2268 if ($p.find('.ubermenu-item').length == 0) { //if this submenu is now empty (no items) 2269 //remove submenu classes, remove submenu 2270 $p.parent().removeClass('ubermenu-has-submenu-drop').removeClass('ubermenu-has-submenu-flyout').off().find('.ubermenu-target > .ubermenu-sub-indicator').remove(); 2271 $p.remove(); 2272 } 2273 }); 2274 $('.ubermenu-submenu:empty').each(function () { 2275 var $p = $(this).parent('li'); 2276 $p.removeClass('ubermenu-has-submenu-drop'); 2277 $p.find('.ubermenu-sub-indicator').remove(); 2278 }); 2279 2280 2281 $('#wp-admin-bar-ubermenu_loading').remove(); 2282 2283 $('.ubermenu').ubermenu({ 2284 //touchOffClose: false 2285 //debug: true, 2286 //debug_onscreen: true, 2287 //mouseEvents: false 2288 //clicktest: true 2289 }); 2290 2291 2292 //Search Autofocus 2293 $('.ubermenu-submenu .ubermenu-search-input-autofocus').closest('.ubermenu-has-submenu-drop').on('ubermenuopen', function () { 2294 var $input = $(this).find('.ubermenu-submenu .ubermenu-search-input'); 2295 if ($input.length) { 2296 setTimeout(function () { 2297 $input[0].focus(); 2298 }, 250); 2299 } 2300 }); 2301 2302 //Resizing detection - add class to body while resizing, 2303 // and trigger events when we cross the breakpoint 2304 var resizeTimer; 2305 var lastWidth = window.innerWidth; 2306 var curWidth = window.innerWidth; 2307 var breakpoint = ubermenu_data.hasOwnProperty('responsive_breakpoint') ? parseInt(ubermenu_data.responsive_breakpoint) : 959; 2308 2309 window.addEventListener('resize', function () { 2310 2311 // Add class while resizing 2312 document.body.classList.add('um-window-resizing'); 2313 clearTimeout(resizeTimer); 2314 resizeTimer = setTimeout(function () { 2315 document.body.classList.remove('um-window-resizing'); 2316 }, 400); 2317 2318 // Trigger custom resize even when crossing breakpoint 2319 curWidth = window.innerWidth; 2320 if (lastWidth <= breakpoint && curWidth > breakpoint) { 2321 // console.log( 'resized to desktop' ); 2322 $(window).trigger('ubermenu-resize-desktop'); 2323 } 2324 else if (lastWidth > breakpoint && curWidth <= breakpoint) { 2325 // console.log( 'resized to mobile' ); 2326 $(window).trigger('ubermenu-resize-mobile'); 2327 } 2328 lastWidth = curWidth; 2329 }); 2330 2331 ubermenu_init_googlemaps(); 2332 2333 } 2334 2335})(jQuery); 2336 2337function ubermenu_init_googlemaps() { 2338 var $ = jQuery; 2339 //Google Maps 2340 if ( 2341 typeof google !== 'undefined' && 2342 typeof google.maps !== 'undefined' && 2343 typeof google.maps.marker !== 'undefined' && 2344 typeof google.maps.LatLng !== 'undefined') { 2345 var map_count = 0; 2346 $('.ubermenu-map-canvas').each(function () { 2347 2348 var $canvas = $(this); 2349 2350 if ($canvas.data('um-map-id')) { 2351 return; 2352 } 2353 2354 // Set up ID 2355 map_count++; 2356 var map_id = 'UBERMENU_MAP_ID_' + map_count; 2357 $canvas.data('um-map-id', map_id); 2358 2359 var dataZoom = $canvas.attr('data-zoom') ? parseInt($canvas.attr('data-zoom')) : 8; 2360 2361 var latlng = $canvas.attr('data-lat') ? 2362 new google.maps.LatLng($canvas.attr('data-lat'), $canvas.attr('data-lng')) : 2363 new google.maps.LatLng(40.7143528, -74.0059731); 2364 2365 var myOptions = { 2366 zoom: dataZoom, 2367 mapId: map_id, 2368 mapTypeId: google.maps.MapTypeId.ROADMAP, 2369 center: latlng 2370 }; 2371 2372 var map = new google.maps.Map(this, myOptions); 2373 2374 if ($canvas.attr('data-address')) { 2375 var geocoder = new google.maps.Geocoder(); 2376 geocoder.geocode({ 2377 'address': $canvas.attr('data-address') 2378 }, 2379 function (results, status) { 2380 if (status == google.maps.GeocoderStatus.OK) { 2381 map.setCenter(results[0].geometry.location); 2382 latlng = results[0].geometry.location; 2383 // var marker = new google.maps.Marker({ 2384 var marker = new google.maps.marker.AdvancedMarkerElement({ 2385 map: map, 2386 position: results[0].geometry.location, 2387 title: $canvas.attr('data-mapTitle') 2388 }); 2389 } 2390 }); 2391 } 2392 else { 2393 //place marker for regular lat/long 2394 // var marker = new google.maps.Marker({ 2395 var marker = new google.maps.marker.AdvancedMarkerElement({ 2396 map: map, 2397 position: latlng, 2398 title: $canvas.attr('data-mapTitle') 2399 }); 2400 } 2401 2402 var $li = $(this).closest('.ubermenu-has-submenu-drop'); 2403 var mapHandler = function () { 2404 google.maps.event.trigger(map, "resize"); 2405 map.setCenter(latlng); 2406 map.setZoom(dataZoom); 2407 //Only resize the first time we open 2408 $li.off('ubermenuopen', mapHandler); 2409 }; 2410 $li.on('ubermenuopen', mapHandler); 2411 }); 2412 } 2413} 2414 2415 2416/* 2417 * Deprecated API Functions 2418 */ 2419//jQuery( '.ubermenu' ).ubermenu( 'openSubmenu' , jQuery( '#menu-item-401' ) ); 2420function uberMenu_openMega(id) { 2421 jQuery('.ubermenu').ubermenu('openSubmenu', jQuery(id)); 2422}
2423function uberMenu_openFlyout(id) { 2424 jQuery('.ubermenu').ubermenu('openSubmenu', jQuery(id)); 2425} 2426function uberMenu_close(id) { 2427 jQuery('.ubermenu').ubermenu('closeSubmenu', jQuery(id)); 2428} 2429function uberMenu_redrawSubmenus() { 2430 jQuery('.ubermenu').ubermenu('positionSubmenus'); 2431}
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.