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 touchstart.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 337 // Lazy initialization 338 if (!$this.data('HSMenuItem')) { 339 self.initMenuItem($this, self.getType($this)); 340 } 341 342 self.closeAll($this.add( 343 $this.parents(self.itemsSelector) 344 )); 345 346 HSMenuItem = $this 347 .addClass('hs-event-prevented') 348 .data('HSMenuItem'); 349 350 if (HSMenuItem.isOpened) { 351 HSMenuItem.desktopHide(); 352 } 353 else { 354 HSMenuItem.desktopShow(); 355 } 356 357 e.preventDefault(); 358 e.stopPropagation(); 359 } 360 ); 361 362 if (!_isTouch()) { 363 this.$element 364 .on( 365 'mouseenter.HSMegaMenu', 366 this.options.classMap.hasMegaMenu + '[data-event="hover"],' + 367 this.options.classMap.hasSubMenu + '[data-event="hover"]', 368 function (e) { 369 370 var $this = $(this), 371 $parents = $this.parents(self.itemsSelector); 372 373 // Lazy initialization 374 if (!$this.data('HSMenuItem')) { 375 self.initMenuItem($this, self.getType($this)); 376 } 377 378 self.closeAll($this.add($parents)); 379 380 $parents.add($this).each(function (i, el) { 381 382 var HSMenuItem = $(el).data('HSMenuItem'); 383 384 if (HSMenuItem.hideTimeOutId) clearTimeout(HSMenuItem.hideTimeOutId); 385 HSMenuItem.desktopShow(); 386 387 }); 388 389 e.preventDefault(); 390 e.stopPropagation(); 391 } 392 ) 393 .on( 394 'mouseleave.HSMegaMenu', 395 this.options.classMap.hasMegaMenu + '[data-event="hover"],' + 396 this.options.classMap.hasSubMenu + '[data-event="hover"]', 397 function (e) { 398 399 var $this = $(this), 400 HSMenuItem = $this.data('HSMenuItem'); 401 402 HSMenuItem.hideTimeOutId = setTimeout(function () { 403 404 self.closeAll( 405 $(e.relatedTarget).parents(self.itemsSelector) 406 ); 407 408 }, self.options.hideTimeOut); 409 410 e.preventDefault(); 411 e.stopPropagation(); 412 } 413 ) 414 } 415 } 416 417 this.$element.on('keydown.HSMegaMenu', 418 this.options.classMap.hasMegaMenu + ' > a,' + 419 this.options.classMap.hasSubMenu + ' > a', 420 function (e) { 421 422 var $this = $(this), 423 $parent = $this.parent(), 424 HSMenuItem; 425 426 // Lazy initialization 427 if (!$parent.data('HSMenuItem')) { 428 429 self.initMenuItem($parent, self.getType($parent)); 430 431 } 432 433 HSMenuItem = $parent.data('HSMenuItem'); 434 435 if ($this.is(':focus')) { 436 437 if (e.keyCode && e.keyCode === 40) { 438 439 e.preventDefault(); 440 441 HSMenuItem.desktopShow(); 442 443 isMenuOpened = true; 444 445 } 446 447 if (e.keyCode && e.keyCode === 13) { 448 449 if (isMenuOpened === true) { 450 451 HSMenuItem.desktopHide(); 452 453 isMenuOpened = false;
454 455 } else { 456 457 HSMenuItem.desktopShow(); 458 459 isMenuOpened = true; 460 461 } 462 463 } 464 465 } 466 467 $this.on('focusout', function () { 468 469 isMenuOpened = false; 470 471 }); 472 473 HSMenuItem.menu.find('a').on('focusout', function () { 474 475 setTimeout(function () { 476 477 if (!HSMenuItem.menu.find('a').is(':focus')) { 478 479 HSMenuItem.desktopHide(); 480 481 isMenuOpened = false; 482 483 } 484 485 }); 486 487 }); 488 489 }) 490 }; 491 492 /** 493 * Initialization of certain menu item. 494 * 495 * @protected 496 */ 497 MegaMenu.prototype.initMenuItem = function (element, type) { 498 499 var self = this, 500 Item = new MenuItem( 501 element, 502 element.children( 503 self.options.classMap[type === 'mega-menu' ? 'megaMenu' : 'subMenu'] 504 ), 505 $.extend(true, {type: type}, self.options, element.data()), 506 self.$element 507 ); 508 509 element.data('HSMenuItem', Item); 510 this._items = this._items.add(element); 511 512 }; 513 514 /** 515 * Destroys of desktop behavior, then makes initialization of mobile behavior. 516 * 517 * @protected 518 */ 519 MegaMenu.prototype.initMobileBehavior = function () { 520 521 var self = this; 522 523 this.state = 'mobile'; 524 525 this.$element 526 .off('.HSMegaMenu') 527 .addClass(this.options.classMap.mobileState.slice(1)) 528 .on('click.HSMegaMenu', self.options.classMap.hasSubMenu + ' > a, ' + self.options.classMap.hasMegaMenu + ' > a', function (e) { 529 530 var $this = $(this).parent(), 531 MenuItemInstance; 532 533 // Lazy initialization 534 if (!$this.data('HSMenuItem')) { 535 self.initMenuItem($this, self.getType($this)); 536 } 537 538 self.closeAll($this.parents(self.itemsSelector).add($this)); 539 540 MenuItemInstance = $this 541 .data('HSMenuItem'); 542 543 if (MenuItemInstance.isOpened) { 544 MenuItemInstance.mobileHide(); 545 } 546 else { 547 MenuItemInstance.mobileShow(); 548 } 549 550 e.preventDefault(); 551 e.stopPropagation(); 552 553 }) 554 .find(this.itemsSelector) 555 .not( 556 this.options.classMap.hasSubMenuActive + ',' + 557 this.options.classMap.hasMegaMenuActive 558 ) 559 .children( 560 this.options.classMap.subMenu + ',' + 561 this.options.classMap.megaMenu 562 ) 563 .hide(); 564 565 }; 566 567 /** 568 * Destroys of mobile behavior, then makes initialization of desktop behavior. 569 * 570 * @protected 571 */ 572 MegaMenu.prototype.initDesktopBehavior = function () { 573 574 this.state = 'desktop'; 575 576 this.$element 577 .removeClass(this.options.classMap.mobileState.slice(1)) 578 .off('.HSMegaMenu') 579 .find(this.itemsSelector) 580 .not( 581 this.options.classMap.hasSubMenuActive + ',' + 582 this.options.classMap.hasMegaMenuActive 583 ) 584 .children( 585 this.options.classMap.subMenu + ',' + 586 this.options.classMap.megaMenu 587 ) 588 .hide(); 589 590 this.bindEvents(); 591 592 }; 593 594 /** 595 * Hides all of opened submenus/megamenus. 596 * 597 * @param {jQuery} except - collection of elements, which shouldn't be closed. 598 * @return {jQuery} 599 * @public 600 */ 601 MegaMenu.prototype.closeAll = function (except) { 602 603 var self = this; 604 605 return this._items.not(except && except.length ? except : $()).each(function (i, el) { 606 607 $(el) 608 .removeClass('hs-event-prevented') 609 .data('HSMenuItem')[self.state === 'mobile' ? 'mobileHide' : 'desktopHide'](); 610 611 }); 612 613 }; 614 615 /** 616 * Returns type of sub menu based on specified menu item. 617 * 618 * @param {jQuery} item 619 * @return {String|null} 620 * @public 621 */ 622 MegaMenu.prototype.getType = function (item) { 623 624 if (!item || !item.length) return null; 625 626 return item.hasClass(this.options.classMap.hasSubMenu.slice(1)) ? 'sub-menu' : 627 (item.hasClass(this.options.classMap.hasMegaMenu.slice(1)) ? 'mega-menu' : null); 628 629 }; 630 631 /** 632 * Returns current menu state. 633 * 634 * @return {String} 635 * @public 636 */ 637 MegaMenu.prototype.getState = function () { 638 return this.state; 639 }; 640 641 /** 642 * Updates bounds of all menu items. 643 * 644 * @return {jQuery} 645 * @public 646 */ 647 MegaMenu.prototype.refresh = function () { 648 649 return this._items.add(this._tempChain).each(function (i, el) { 650 $(el).data('HSMenuItem')._updateMenuBounds(); 651 }); 652 653 }; 654 655 656 /** 657 * Creates a mega-menu element. 658 * 659 * @param {jQuery} element 660 * @param {jQuery} menu 661 * @param {Object} options 662 * @param {jQuery} container 663 * @constructor 664 */ 665 function MenuItem(element, menu, options, container) { 666 667 var self = this; 668 669 /** 670 * Current menu item element. 671 * 672 * @public 673 */ 674 this.$element = element; 675 676 /** 677 * Current mega menu element. 678 * 679 * @public 680 */ 681 this.menu = menu; 682 683 /** 684 * Item options. 685 * 686 * @public 687 */ 688 this.options = options; 689 690 691 /** 692 * MegaMenu container. 693 * 694 * @public 695 */ 696 this.$container = container; 697 698 Object.defineProperties(this, { 699 700 /** 701 * Contains css class of menu item element. 702 * 703 * @public 704 */ 705 itemClass: { 706 get: function () { 707 return self.options.type === 'mega-menu' ? 708 self.options.classMap.hasMegaMenu : 709 self.options.classMap.hasSubMenu; 710 } 711 }, 712 713 /** 714 * Contains css active-class of menu item element. 715 * 716 * @public 717 */
718 activeItemClass: { 719 get: function () { 720 return self.options.type === 'mega-menu' ? 721 self.options.classMap.hasMegaMenuActive : 722 self.options.classMap.hasSubMenuActive; 723 } 724 }, 725 726 /** 727 * Contains css class of menu element. 728 * 729 * @public 730 */ 731 menuClass: { 732 get: function () { 733 return self.options.type === 'mega-menu' ? 734 self.options.classMap.megaMenu : 735 self.options.classMap.subMenu; 736 } 737 }, 738 739 isOpened: { 740 get: function () { 741 return this.$element.hasClass(this.activeItemClass.slice(1)); 742 } 743 } 744 745 }); 746 747 748 this.menu.addClass('animated').on('click.HSMegaMenu', function (e) { 749 self._updateMenuBounds(); 750 }); 751 752 if (this.$element.data('max-width')) this.menu.css('max-width', this.$element.data('max-width')); 753 if (this.$element.data('position')) this.menu.addClass('hs-position-' + this.$element.data('position')); 754 755 756 if (this.options.animationOut) { 757 758 this.menu.on('webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend', function (e) { 759 760 if (self.menu.hasClass(self.options.animationOut)) { 761 self.$element.removeClass(self.activeItemClass.slice(1)); 762 self.options.afterClose.call(self, self.$element, self.menu); 763 } 764 765 if (self.menu.hasClass(self.options.animationIn)) { 766 self.options.afterOpen.call(self, self.$element, self.menu); 767 } 768 769 e.stopPropagation(); 770 e.preventDefault(); 771 }); 772 } 773 774 } 775 776 /** 777 * Shows the mega-menu item. 778 * 779 * @public 780 * @return {MenuItem} 781 */ 782 MenuItem.prototype.desktopShow = function () { 783 if (!this.menu.length) return this; 784 785 this.$element.addClass(this.activeItemClass.slice(1)); 786 787 this._updateMenuBounds(); 788 789 this.menu.show(); 790 791 if (this.options.direction == 'horizontal') this.smartPosition(this.menu, this.options); 792 793 if (this.options.animationOut) { 794 this.menu.removeClass(this.options.animationOut); 795 } 796 else { 797 this.options.afterOpen.call(this, this.$element, this.menu); 798 } 799 800 if (this.options.animationIn) { 801 this.menu.addClass(this.options.animationIn) 802 } 803 804 return this; 805 } 806 807 /** 808 * Hides the mega-menu item. 809 * 810 * @public 811 * @return {MenuItem} 812 */ 813 MenuItem.prototype.desktopHide = function () { 814 815 var self = this; 816 817 if (!this.menu.length) return this; 818 819 this.$element.removeClass(this.activeItemClass.slice(1)); 820 821 this.menu.hide(); 822 823 if (this.options.animationIn) { 824 this.menu.removeClass(this.options.animationIn); 825 } 826 if (this.options.animationOut) { 827 this.menu.addClass(this.options.animationOut); 828 } 829 else { 830 this.options.afterClose.call(this, this.$element, this.menu); 831 } 832 833 return this; 834 } 835 836 /** 837 * Shows the mega-menu item. 838 * 839 * @public 840 * @return {MenuItem} 841 */ 842 MenuItem.prototype.mobileShow = function () { 843 var self = this; 844 845 if (!this.menu.length) return this; 846 847 848 this.menu 849 .removeClass(this.options.animationIn) 850 .removeClass(this.options.animationOut) 851 .stop() 852 .slideDown({ 853 duration: self.options.mobileSpeed, 854 easing: self.options.mobileEasing, 855 complete: function () { 856 self.options.afterOpen.call(self, self.$element, self.menu); 857 } 858 }); 859 860 this.$element.addClass(this.activeItemClass.slice(1)); 861 862 return this; 863 }; 864 865 /** 866 * Hides the mega-menu item. 867 * 868 * @public 869 * @return {MenuItem} 870 */ 871 MenuItem.prototype.mobileHide = function () { 872 var self = this; 873 874 if (!this.menu.length) return this; 875 876 this.menu.stop().slideUp({ 877 duration: self.options.mobileSpeed, 878 easing: self.options.mobileEasing, 879 complete: function () { 880 self.options.afterClose.call(self, self.$element, self.menu); 881 } 882 }); 883 884 this.$element.removeClass(this.activeItemClass.slice(1)); 885 886 return this; 887 }; 888 889 /** 890 * Check, if element is in viewport. 891 * 892 * @param {jQuery} element 893 * @param {Object} options 894 * @public 895 */ 896 MenuItem.prototype.smartPosition = function (element, options) { 897 898 MenuItem.smartPosition(element, options); 899 900 }; 901 902 /** 903 * Check, if element is in viewport. 904 * 905 * @param {jQuery} element 906 * @param {Object} options 907 * @static 908 * @public 909 */ 910 MenuItem.smartPosition = function (element, options) { 911 if (!element && !element.length) return; 912 913 var $w = $(window); 914 915 element.removeClass('hs-reversed'); 916 917 if (!options.rtl) { 918 919 if (element.offset().left + element.outerWidth() > window.innerWidth) { 920 element.addClass('hs-reversed'); 921 } 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 } 948 else { 949 width = 'auto'; 950 } 951 952 953 this.menu.css({ 954 'width': width, 955 'height': 'auto' 956 }); 957 958 if (this.menu.outerHeight() > this.$container.outerHeight()) { 959 return; 960 } 961 962 this.menu.css('height', '100%'); 963 } 964 965 }; 966 967 /** 968 * The jQuery plugin for the MegaMenu. 969 * 970 * @public 971 */ 972 973 $.fn.HSMegaMenu = function () { 974 var _ = this, 975 opt = arguments[0], 976 args = Array.prototype.slice.call(arguments, 1), 977 l = _.length, 978 i, 979 ret; 980 for (i = 0; i < l; i++) { 981 if (typeof opt == 'object' || typeof opt == 'undefined') 982 _[i].MegaMenu = new MegaMenu(_[i], opt); 983 else 984 ret = _[i].MegaMenu[opt].apply(_[i].MegaMenu, args); 985 if (typeof ret != 'undefined') return ret; 986 } 987 return _; 988 }; 989 990 /** 991 * Helper function for detect touch events in the environment. 992 * 993 * @return {Boolean} 994 * @private 995 */ 996 function _isTouch() { 997 return ('ontouchstart' in window); 998 } 999 1000})(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.