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