1/* 2 * HS Mega Menu - jQuery Plugin 3 * @version: 1.0.0 (Sun, 26 Feb 2017) 4 * @requires: jQuery v1.6 or later 5 * @author: HtmlStream 6 * @event-namespace: .HSMegaMenu 7 * @browser-support: IE9+ 8 * @license: 9 * 10 * Copyright 2017 HtmlStream 11 * 12 */ 13(function ($) { 14 'use strict'; 15 16 /** 17 * Creates a mega menu. 18 * 19 * @constructor 20 * @param {HTMLElement|jQuery} element - The element to create the mega menu for. 21 * @param {Object} options - The options 22 */ 23 24 var MegaMenu = window.MegaMenu || {}; 25 26 MegaMenu = (function () { 27 28 var isMenuOpened = false; 29 30 function MegaMenu(element, options) { 31 32 var self = this; 33 34 /** 35 * Current element. 36 * 37 * @public 38 */ 39 this.$element = $(element); 40 41 /** 42 * Current options set by the caller including defaults. 43 * 44 * @public 45 */ 46 this.options = $.extend(true, {}, MegaMenu.defaults, options); 47 48 49 /** 50 * Collection of menu elements. 51 * 52 * @private 53 */ 54 this._items = $(); 55 56 57 Object.defineProperties(this, { 58 59 /** 60 * Contains composed selector of menu items. 61 * 62 * @public 63 */ 64 itemsSelector: { 65 get: function () { 66 return self.options.classMap.hasSubMenu + ',' + 67 self.options.classMap.hasMegaMenu; 68 } 69 }, 70 71 /** 72 * Contains chain of active items. 73 * 74 * @private 75 */ 76 _tempChain: { 77 value: null, 78 writable: true 79 }, 80 81 /** 82 * Contains current behavior state. 83 * 84 * @public 85 */ 86 state: { 87 value: null, 88 writable: true 89 } 90 91 }); 92 93 this.initialize(); 94 95 } 96 97 return MegaMenu; 98 99 }()); 100 101 102 /** 103 * Default options of the mega menu. 104 * 105 * @public 106 */ 107 MegaMenu.defaults = { 108 event: 'hover', 109 direction: 'horizontal', 110 breakpoint: 991, 111 animationIn: false, 112 animationOut: false, 113 114 rtl: false, 115 hideTimeOut: 300, 116 117 // For 'vertical' direction only 118 sideBarRatio: 1 / 4, 119 pageContainer: $('body'), 120 121 classMap: { 122 initialized: '.hs-menu-initialized', 123 mobileState: '.hs-mobile-state', 124 125 subMenu: '.hs-sub-menu', 126 hasSubMenu: '.hs-has-sub-menu', 127 hasSubMenuActive: '.hs-sub-menu-opened', 128 129 megaMenu: '.hs-mega-menu', 130 hasMegaMenu: '.hs-has-mega-menu', 131 hasMegaMenuActive: '.hs-mega-menu-opened' 132 }, 133 134 mobileSpeed: 400, 135 mobileEasing: 'linear', 136 137 beforeOpen: function () {}, 138 beforeClose: function () {}, 139 afterOpen: function () {}, 140 afterClose: function () {} 141 }; 142 143 /** 144 * Initialization of the plugin. 145 * 146 * @protected 147 */ 148 MegaMenu.prototype.initialize = function () { 149 var self = this, 150 $w = $(window); 151 152 if (this.options.rtl) this.$element.addClass('hs-rtl'); 153 154 this.$element 155 .addClass(this.options.classMap.initialized.slice(1)) 156 .addClass('hs-menu-' + this.options.direction); 157 158 159 // Independent events 160 $w.on('resize.HSMegaMenu', function (e) { 161 162 if (self.resizeTimeOutId) clearTimeout(self.resizeTimeOutId); 163 164 self.resizeTimeOutId = setTimeout(function () { 165 166 if (window.innerWidth <= self.options.breakpoint && self.state == 'desktop') self.initMobileBehavior(); 167 else if (window.innerWidth > self.options.breakpoint && self.state == 'mobile') self.initDesktopBehavior(); 168 169 self.refresh(); 170 171 }, 50); 172 173 }); 174 175 $(document).on('click.HSMegaMenu touchstart.HSMegaMenu', 'body', function (e) { 176 177 var $parents = $(e.target).parents(self.itemsSelector); 178 179 self.closeAll($parents.add($(e.target))); 180 181 }); 182 183 $w.on('keyup.HSMegaMenu', function (e) { 184 185 if (e.keyCode && e.keyCode === 27) { 186 187 self.closeAll(); 188 189 isMenuOpened = false; 190 } 191 192 }); 193 194 if (window.innerWidth <= this.options.breakpoint) this.initMobileBehavior(); 195 else if (window.innerWidth > this.options.breakpoint) this.initDesktopBehavior(); 196 197 this.smartPositions(); 198 199 return this; 200 201 }; 202 203 MegaMenu.prototype.smartPositions = function () { 204 205 var self = this, 206 $submenus = this.$element.find(this.options.classMap.subMenu); 207 208 $submenus.each(function (i, el) { 209 210 MenuItem.smartPosition($(el), self.options); 211 212 }); 213 214 }; 215 216 /** 217 * Binding events to menu elements. 218 * 219 * @protected 220 */ 221 MegaMenu.prototype.bindEvents = function () { 222 223 var self = this, 224 selector; 225 226 // Hover case 227 if (this.options.event === 'hover' && !_isTouch()) { 228 this.$element 229 .on( 230 'mouseenter.HSMegaMenu', 231 this.options.classMap.hasMegaMenu + ':not([data-event="click"]),' + 232 this.options.classMap.hasSubMenu + ':not([data-event="click"])', 233 function (e) { 234 235 var $this = $(this), 236 $chain = $this.parents(self.itemsSelector); 237 238 // Lazy initialization 239 if (!$this.data('HSMenuItem')) { 240 self.initMenuItem($this, self.getType($this)); 241 } 242 243 $chain = $chain.add($this); 244 245 self.closeAll($chain); 246 247 $chain.each(function (i, el) { 248 249 var HSMenuItem = $(el).data('HSMenuItem'); 250 251 if (HSMenuItem.hideTimeOutId) clearTimeout(HSMenuItem.hideTimeOutId); 252 HSMenuItem.desktopShow(); 253 254 }); 255 256 self._items = self._items.not($chain); 257 self._tempChain = $chain; 258 259 e.preventDefault(); 260 e.stopPropagation(); 261 } 262 ) 263 .on( 264 'mouseleave.HSMegaMenu', 265 this.options.classMap.hasMegaMenu + ':not([data-event="click"]),' + 266 this.options.classMap.hasSubMenu + ':not([data-event="click"])', 267 function (e) { 268 269 var $this = $(this), 270 HSMenuItem = $this.data('HSMenuItem'), 271 $chain = $(e.relatedTarget).parents(self.itemsSelector); 272 273 HSMenuItem.hideTimeOutId = setTimeout(function () { 274 self.closeAll($chain); 275 }, self.options.hideTimeOut); 276 277 self._items = self._items.add(self._tempChain); 278 self._tempChain = null; 279 280 e.preventDefault(); 281 e.stopPropagation(); 282 } 283 ) 284 .on( 285 'click.HSMegaMenu', 286 this.options.classMap.hasMegaMenu + '[data-event="click"] > a, ' + 287 this.options.classMap.hasSubMenu + '[data-event="click"] > a', 288 function (e) { 289 290 var $this = $(this).parent('[data-event="click"]'), 291 HSMenuItem; 292 293 // Lazy initialization 294 if (!$this.data('HSMenuItem')) { 295 self.initMenuItem($this, self.getType($this)); 296 } 297 298 299 self.closeAll($this.add( 300 $this.parents(self.itemsSelector) 301 )); 302 303 HSMenuItem = $this 304 .data('HSMenuItem'); 305 306 if (HSMenuItem.isOpened) { 307 HSMenuItem.desktopHide(); 308 } 309 else { 310 HSMenuItem.desktopShow(); 311 } 312 313 314 e.preventDefault(); 315 e.stopPropagation(); 316 317 } 318 ); 319 } 320 // Click case 321 else { 322 323 this.$element 324 .on( 325 'click.HSMegaMenu', 326 (_isTouch() ? 327 this.options.classMap.hasMegaMenu + ' > a, ' + 328 this.options.classMap.hasSubMenu + ' > a' : 329 this.options.classMap.hasMegaMenu + ':not([data-event="hover"]) > a,' + 330 this.options.classMap.hasSubMenu + ':not([data-event="hover"]) > a'), 331 function (e) { 332 333 var $this = $(this).parent(), 334 HSMenuItem, 335 $parents = $this.parents(self.itemsSelector); 336 if ($(this).attr('id') == 'homeMegaMenu') { 337 window.location.href = '/' 338 } 339 if ($(this).attr('id') == 'contactMegaMenu') { 340 window.location.href = $(this).attr('href') 341 } 342 343 // Lazy initialization 344 if (!$this.data('HSMenuItem')) { 345 self.initMenuItem($this, self.getType($this)); 346 } 347 348 self.closeAll($this.add( 349 $this.parents(self.itemsSelector) 350 )); 351 352 HSMenuItem = $this 353 .addClass('hs-event-prevented') 354 .data('HSMenuItem'); 355 356 if (HSMenuItem.isOpened) { 357 HSMenuItem.desktopHide(); 358 } 359 else { 360 HSMenuItem.desktopShow(); 361 } 362 363 e.preventDefault(); 364 e.stopPropagation(); 365 } 366 ); 367 368 if (!_isTouch()) { 369 this.$element 370 .on( 371 'mouseenter.HSMegaMenu', 372 this.options.classMap.hasMegaMenu + '[data-event="hover"],' + 373 this.options.classMap.hasSubMenu + '[data-event="hover"]', 374 function (e) { 375 376 var $this = $(this), 377 $parents = $this.parents(self.itemsSelector); 378 379 // Lazy initialization 380 if (!$this.data('HSMenuItem')) { 381 self.initMenuItem($this, self.getType($this)); 382 } 383 384 self.closeAll($this.add($parents)); 385 386 $parents.add($this).each(function (i, el) { 387 388 var HSMenuItem = $(el).data('HSMenuItem'); 389 390 if (HSMenuItem.hideTimeOutId) clearTimeout(HSMenuItem.hideTimeOutId); 391 HSMenuItem.desktopShow(); 392 393 }); 394 395 e.preventDefault(); 396 e.stopPropagation(); 397 } 398 ) 399 .on( 400 'mouseleave.HSMegaMenu', 401 this.options.classMap.hasMegaMenu + '[data-event="hover"],' + 402 this.options.classMap.hasSubMenu + '[data-event="hover"]', 403 function (e) { 404 405 var $this = $(this), 406 HSMenuItem = $this.data('HSMenuItem'); 407 408 HSMenuItem.hideTimeOutId = setTimeout(function () { 409 410 self.closeAll( 411 $(e.relatedTarget).parents(self.itemsSelector) 412 ); 413 414 }, self.options.hideTimeOut); 415 416 e.preventDefault(); 417 e.stopPropagation(); 418 } 419 ) 420 } 421 } 422 423 this.$element.on('keydown.HSMegaMenu', 424 this.options.classMap.hasMegaMenu + ' > a,' + 425 this.options.classMap.hasSubMenu + ' > a', 426 function (e) { 427 428 var $this = $(this), 429 $parent = $this.parent(), 430 HSMenuItem; 431 432 // Lazy initialization 433 if (!$parent.data('HSMenuItem')) { 434 435 self.initMenuItem($parent, self.getType($parent)); 436 437 } 438 439 HSMenuItem = $parent.data('HSMenuItem'); 440 441 if ($this.is(':focus')) { 442 443 if (e.keyCode && e.keyCode === 40) { 444 445 e.preventDefault(); 446 447 HSMenuItem.desktopShow(); 448 449 isMenuOpened = true; 450 451 } 452 453 if (e.keyCode && e.keyCode === 13) { 454 455 if (isMenuOpened === true) { 456 457 HSMenuItem.desktopHide(); 458 459 isMenuOpened = false;
460 461 } else { 462 463 HSMenuItem.desktopShow(); 464 465 isMenuOpened = true; 466 467 } 468 469 } 470 471 } 472 473 $this.on('focusout', function () { 474 475 isMenuOpened = false; 476 477 }); 478 479 HSMenuItem.menu.find('a').on('focusout', function () { 480 481 setTimeout(function () { 482 483 if (!HSMenuItem.menu.find('a').is(':focus')) { 484 485 HSMenuItem.desktopHide(); 486 487 isMenuOpened = false; 488 489 } 490 491 }); 492 493 }); 494 495 }) 496 }; 497 498 /** 499 * Initialization of certain menu item. 500 * 501 * @protected 502 */ 503 MegaMenu.prototype.initMenuItem = function (element, type) { 504 505 var self = this, 506 Item = new MenuItem( 507 element, 508 element.children( 509 self.options.classMap[type === 'mega-menu' ? 'megaMenu' : 'subMenu'] 510 ), 511 $.extend(true, {type: type}, self.options, element.data()), 512 self.$element 513 ); 514 515 element.data('HSMenuItem', Item); 516 this._items = this._items.add(element); 517 518 }; 519 520 /** 521 * Destroys of desktop behavior, then makes initialization of mobile behavior. 522 * 523 * @protected 524 */ 525 MegaMenu.prototype.initMobileBehavior = function () { 526 527 var self = this; 528 529 this.state = 'mobile'; 530 531 this.$element 532 .off('.HSMegaMenu') 533 .addClass(this.options.classMap.mobileState.slice(1)) 534 .on('click.HSMegaMenu', self.options.classMap.hasSubMenu + ' > a, ' + self.options.classMap.hasMegaMenu + ' > a', function (e) { 535 536 var $this = $(this).parent(), 537 MenuItemInstance; 538 539 // Lazy initialization 540 if (!$this.data('HSMenuItem')) { 541 self.initMenuItem($this, self.getType($this)); 542 } 543 544 self.closeAll($this.parents(self.itemsSelector).add($this)); 545 546 MenuItemInstance = $this 547 .data('HSMenuItem'); 548 549 if (MenuItemInstance.isOpened) { 550 MenuItemInstance.mobileHide(); 551 } 552 else { 553 MenuItemInstance.mobileShow(); 554 } 555 556 e.preventDefault(); 557 e.stopPropagation(); 558 559 }) 560 .find(this.itemsSelector) 561 .not( 562 this.options.classMap.hasSubMenuActive + ',' + 563 this.options.classMap.hasMegaMenuActive 564 ) 565 .children( 566 this.options.classMap.subMenu + ',' + 567 this.options.classMap.megaMenu 568 ) 569 .hide(); 570 571 }; 572 573 /** 574 * Destroys of mobile behavior, then makes initialization of desktop behavior. 575 * 576 * @protected 577 */ 578 MegaMenu.prototype.initDesktopBehavior = function () { 579 580 this.state = 'desktop'; 581 582 this.$element 583 .removeClass(this.options.classMap.mobileState.slice(1)) 584 .off('.HSMegaMenu') 585 .find(this.itemsSelector) 586 .not( 587 this.options.classMap.hasSubMenuActive + ',' + 588 this.options.classMap.hasMegaMenuActive 589 ) 590 .children( 591 this.options.classMap.subMenu + ',' + 592 this.options.classMap.megaMenu 593 ) 594 .hide(); 595 596 this.bindEvents(); 597 598 }; 599 600 /** 601 * Hides all of opened submenus/megamenus. 602 * 603 * @param {jQuery} except - collection of elements, which shouldn't be closed. 604 * @return {jQuery} 605 * @public 606 */ 607 MegaMenu.prototype.closeAll = function (except) { 608 609 var self = this; 610 611 return this._items.not(except && except.length ? except : $()).each(function (i, el) { 612 613 $(el) 614 .removeClass('hs-event-prevented') 615 .data('HSMenuItem')[self.state === 'mobile' ? 'mobileHide' : 'desktopHide'](); 616 617 }); 618 619 }; 620 621 /** 622 * Returns type of sub menu based on specified menu item. 623 * 624 * @param {jQuery} item 625 * @return {String|null} 626 * @public 627 */ 628 MegaMenu.prototype.getType = function (item) { 629 630 if (!item || !item.length) return null; 631 632 return item.hasClass(this.options.classMap.hasSubMenu.slice(1)) ? 'sub-menu' : 633 (item.hasClass(this.options.classMap.hasMegaMenu.slice(1)) ? 'mega-menu' : null); 634 635 }; 636 637 /** 638 * Returns current menu state. 639 * 640 * @return {String} 641 * @public 642 */ 643 MegaMenu.prototype.getState = function () { 644 return this.state; 645 }; 646 647 /** 648 * Updates bounds of all menu items. 649 * 650 * @return {jQuery} 651 * @public 652 */ 653 MegaMenu.prototype.refresh = function () { 654 655 return this._items.add(this._tempChain).each(function (i, el) { 656 $(el).data('HSMenuItem')._updateMenuBounds(); 657 }); 658 659 }; 660 661 662 /** 663 * Creates a mega-menu element. 664 * 665 * @param {jQuery} element 666 * @param {jQuery} menu 667 * @param {Object} options 668 * @param {jQuery} container 669 * @constructor 670 */ 671 function MenuItem(element, menu, options, container) { 672 673 var self = this; 674 675 /** 676 * Current menu item element. 677 * 678 * @public 679 */ 680 this.$element = element; 681 682 /** 683 * Current mega menu element. 684 * 685 * @public 686 */ 687 this.menu = menu; 688 689 /** 690 * Item options. 691 * 692 * @public 693 */ 694 this.options = options; 695 696 697 /** 698 * MegaMenu container. 699 * 700 * @public 701 */ 702 this.$container = container; 703 704 Object.defineProperties(this, { 705 706 /** 707 * Contains css class of menu item element. 708 * 709 * @public 710 */ 711 itemClass: { 712 get: function () { 713 return self.options.type === 'mega-menu' ? 714 self.options.classMap.hasMegaMenu : 715 self.options.classMap.hasSubMenu; 716 } 717 }, 718 719 /** 720 * Contains css active-class of menu item element. 721 * 722 * @public 723 */
724 activeItemClass: { 725 get: function () { 726 return self.options.type === 'mega-menu' ? 727 self.options.classMap.hasMegaMenuActive : 728 self.options.classMap.hasSubMenuActive; 729 } 730 }, 731 732 /** 733 * Contains css class of menu element. 734 * 735 * @public 736 */ 737 menuClass: { 738 get: function () { 739 return self.options.type === 'mega-menu' ? 740 self.options.classMap.megaMenu : 741 self.options.classMap.subMenu; 742 } 743 }, 744 745 isOpened: { 746 get: function () { 747 return this.$element.hasClass(this.activeItemClass.slice(1)); 748 } 749 } 750 751 }); 752 753 754 this.menu.addClass('animated').on('click.HSMegaMenu', function (e) { 755 self._updateMenuBounds(); 756 }); 757 758 if (this.$element.data('max-width')) this.menu.css('max-width', this.$element.data('max-width')); 759 if (this.$element.data('position')) this.menu.addClass('hs-position-' + this.$element.data('position')); 760 761 762 if (this.options.animationOut) { 763 764 this.menu.on('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function (e) { 765 766 if (self.menu.hasClass(self.options.animationOut)) { 767 self.$element.removeClass(self.activeItemClass.slice(1)); 768 self.options.afterClose.call(self, self.$element, self.menu); 769 } 770 771 if (self.menu.hasClass(self.options.animationIn)) { 772 self.options.afterOpen.call(self, self.$element, self.menu); 773 } 774 775 e.stopPropagation(); 776 e.preventDefault(); 777 }); 778 } 779 780 } 781 782 /** 783 * Shows the mega-menu item. 784 * 785 * @public 786 * @return {MenuItem} 787 */ 788 MenuItem.prototype.desktopShow = function () { 789 if (!this.menu.length) return this; 790 791 this.$element.addClass(this.activeItemClass.slice(1)); 792 793 this._updateMenuBounds(); 794 795 this.menu.show(); 796 797 if (this.options.direction == 'horizontal') this.smartPosition(this.menu, this.options); 798 799 if (this.options.animationOut) { 800 this.menu.removeClass(this.options.animationOut); 801 } 802 else { 803 this.options.afterOpen.call(this, this.$element, this.menu); 804 } 805 806 if (this.options.animationIn) { 807 this.menu.addClass(this.options.animationIn) 808 } 809 810 return this; 811 } 812 813 /** 814 * Hides the mega-menu item. 815 * 816 * @public 817 * @return {MenuItem} 818 */ 819 MenuItem.prototype.desktopHide = function () { 820 821 var self = this; 822 823 if (!this.menu.length) return this; 824 825 this.$element.removeClass(this.activeItemClass.slice(1)); 826 827 this.menu.hide(); 828 829 if (this.options.animationIn) { 830 this.menu.removeClass(this.options.animationIn); 831 } 832 if (this.options.animationOut) { 833 this.menu.addClass(this.options.animationOut); 834 } 835 else { 836 this.options.afterClose.call(this, this.$element, this.menu); 837 } 838 839 return this; 840 } 841 842 /** 843 * Shows the mega-menu item. 844 * 845 * @public 846 * @return {MenuItem} 847 */ 848 MenuItem.prototype.mobileShow = function () { 849 var self = this; 850 851 if (!this.menu.length) return this; 852 853 854 this.menu 855 .removeClass(this.options.animationIn) 856 .removeClass(this.options.animationOut) 857 .stop() 858 .slideDown({ 859 duration: self.options.mobileSpeed, 860 easing: self.options.mobileEasing, 861 complete: function () { 862 self.options.afterOpen.call(self, self.$element, self.menu); 863 } 864 }); 865 866 this.$element.addClass(this.activeItemClass.slice(1)); 867 868 return this; 869 }; 870 871 /** 872 * Hides the mega-menu item. 873 * 874 * @public 875 * @return {MenuItem} 876 */ 877 MenuItem.prototype.mobileHide = function () { 878 var self = this; 879 880 if (!this.menu.length) return this; 881 882 this.menu.stop().slideUp({ 883 duration: self.options.mobileSpeed, 884 easing: self.options.mobileEasing, 885 complete: function () { 886 self.options.afterClose.call(self, self.$element, self.menu); 887 } 888 }); 889 890 this.$element.removeClass(this.activeItemClass.slice(1)); 891 892 return this; 893 }; 894 895 /** 896 * Check, if element is in viewport. 897 * 898 * @param {jQuery} element 899 * @param {Object} options 900 * @public 901 */ 902 MenuItem.prototype.smartPosition = function (element, options) { 903 904 MenuItem.smartPosition(element, options); 905 906 }; 907 908 /** 909 * Check, if element is in viewport. 910 * 911 * @param {jQuery} element 912 * @param {Object} options 913 * @static 914 * @public 915 */ 916 MenuItem.smartPosition = function (element, options) { 917 if (!element && !element.length) return; 918 919 var $w = $(window); 920 921 element.removeClass('hs-reversed'); 922 923 if (!options.rtl) { 924 925 if (element.offset().left + element.outerWidth() > window.innerWidth) { 926 element.addClass('hs-reversed'); 927 } 928 929 } 930 else { 931 932 if (element.offset().left < 0) { 933 element.addClass('hs-reversed'); 934 } 935 936 } 937 }; 938 939 /** 940 * Updates bounds of current opened menu. 941 * 942 * @private 943 */ 944 MenuItem.prototype._updateMenuBounds = function () { 945 946 var width = 'auto'; 947 948 if (this.options.direction == 'vertical' && this.options.type == 'mega-menu') { 949 950 if (this.$container && this.$container.data('HSMegaMenu').get
950State() == 'desktop') { 951 if (!this.options.pageContainer.length) this.options.pageContainer = $('body'); 952 width = this.options.pageContainer.outerWidth() * (1 - this.options.sideBarRatio); 953 } 954 else { 955 width = 'auto'; 956 } 957 958 959 this.menu.css({ 960 'width': width, 961 'height': 'auto' 962 }); 963 964 if (this.menu.outerHeight() > this.$container.outerHeight()) { 965 return; 966 } 967 968 this.menu.css('height', '100%'); 969 } 970 971 }; 972 973 /** 974 * The jQuery plugin for the MegaMenu. 975 * 976 * @public 977 */ 978 979 $.fn.HSMegaMenu = function () { 980 var _ = this, 981 opt = arguments[0], 982 args = Array.prototype.slice.call(arguments, 1), 983 l = _.length, 984 i, 985 ret; 986 for (i = 0; i < l; i++) { 987 if (typeof opt == 'object' || typeof opt == 'undefined') 988 _[i].MegaMenu = new MegaMenu(_[i], opt); 989 else 990 ret = _[i].MegaMenu[opt].apply(_[i].MegaMenu, args); 991 if (typeof ret != 'undefined') return ret; 992 } 993 return _; 994 }; 995 996 /** 997 * Helper function for detect touch events in the environment. 998 * 999 * @return {Boolean} 1000 * @private 1001 */ 1002 function _isTouch() { 1003 return ('ontouchstart' in window); 1004 } 1005 1006})(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.