1(function($) { 2 $(function() { 3 var mobileBreakpoints = ["1024px", "768px", "640px","480px","360px","270px"]; 4 5 function fitElementWidth(fitEl){ 6 var elementLength = $(fitEl).length; 7 var elementPercent = 100 / elementLength; 8 var fitElementStyle = '<style>@media only screen and (max-width: '+mobileBreakpoints[1]+') { '+fitEl+'{ width:'+elementPercent+'%; }}</style>'; 9 $(fitElementStyle).appendTo('head'); 10 } 11 function setElementWidth(setEl){ 12 var totalWidth = 0; 13 setEl.each(function() { 14 totalWidth = $(this).outerWidth() + totalWidth; 15 }); 16 setEl.parent().css('width',totalWidth+10); 17 } 18 function clearClassDropdowns(){ 19 $('.sport-crumbs-list').removeClass('thisCrumb'); 20 $('#sport-crumbs ul li').removeClass('crumbSelected'); 21 $('.dropdown-wrapper').removeClass('mdd-active-open'); 22 } 23 setElementWidth($('#sport-crumbs .sport-crumbs-btns > ul li')); 24 fitElementWidth('#sport-header .sport-tools li'); 25 26 $('#sport-crumbs .sport-crumbs-btns a[data-id]').on( "click", function(e) { 27 thisCrumb = $(this).attr('data-id'); 28 thisCrumbId = $('#'+thisCrumb); 29 parentList = $(this).parent(); 30 31 if(thisCrumb != undefined){ 32 if(thisCrumbId.hasClass('thisCrumb') == true){ 33 clearClassDropdowns(); 34 } 35 else{ 36 clearClassDropdowns(); 37 parentList.addClass('crumbSelected'); 38 thisCrumbId.addClass('thisCrumb'); 39 } 40 thisCrumbId.mouseleave(function(e) { 41 clearClassDropdowns(); 42 e.stopPropagation(); 43 }); 44 } 45 e.preventDefault(); 46 e.stopPropagation(); 47 }); 48 $('.sport-crumbs-list-close a').on( "click", function(e) { 49 clearClassDropdowns(); 50 e.preventDefault(); 51 e.stopPropagation(); 52 }); 53 54 /* Mega Dropdown Season */ 55 $('#megaDropDown-season').on( "click", function(e) { 56 nextDiv = $(this).next(); 57 parentList = $(this).parent(); 58 if(nextDiv.length == true){ 59 if(parentList.hasClass('mdd-active-open') == true){ 60 parentList.removeClass('mdd-active-open'); 61 } 62 else{ 63 parentList.addClass('mdd-active-open'); 64 } 65 $('.sport-crumbs-list').removeClass('thisCrumb'); 66 $('#sport-crumbs ul li').removeClass('crumbSelected'); 67 } 68 e.preventDefault(); 69 e.stopPropagation(); 70 }); 71 $('#megaDropDown-season-callout a.close').on( "click", function(e) { 72 $('.dropdown-wrapper').removeClass('mdd-active-open'); 73 }); 74 /* Roster Player Dropdown */ 75 $('#rosterPlayerConnections-button').on( "click", function(e) { 76 $('#rosterPlayerConnections-dropdown').toggle(0, function() { 77 if($(this).is(':visible')) { 78 $('#rosterPlayerConnections-button').addClass('rpc-open'); 79 } 80 else{ 81 $('#rosterPlayerConnections-button').removeClass('rpc-open'); 82 } 83 }); 84 }); 85 /* make textBlockElement liquid remove this */ 86 $j('.textBlockElement .text > table').each( function() { 87 $j(this).wrap('<div class="tableWrapper"></div>'); 88 }); 89 // 90 $j(document).on('topnav.loaded', 'body', function(){ 91 $('#topNav ul ul ul li a.disabled').parent().addClass('disabled'); 92 }); 93 /* Network Bar Select Option */ 94 $('#network_sites').change(function() { 95 window.location = $(this).val(); 96 }); 97 /* Touch detection */ 98 if (('ontouchstart' in window) || (navigator.maxTouchPoints > 0) || (navigator.msMaxTouchPoints > 0)){ 99 $j("body").addClass("touch-screen") 100 } 101 }); 102}(jQuery)); 103;(function($) { 104 105 function hasTouch() { 106 return 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0; 107 } 108 109 function setTouchScrolling(i, el) { 110 if (el.style) el.style.webkitOverflowScrolling = 'touch'; 111 } 112 113 $.fn.scrollGuard = function() { 114 this.each(function() { 115 var el = this; 116 var lastTouchY = null; 117 118 // Wheel event (desktop) 119 el.addEventListener('wheel', function(e) { 120 var d = -e.deltaY; 121 var atTop = el.scrollTop === 0; 122 var atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight; 123 if ((atTop && atBottom) || (d < 0 && atBottom) || (d > 0 && atTop)) { 124 e.preventDefault(); 125 } 126 }, {passive: false}); 127 128 // Touch events (mobile) 129 if (hasTouch()) { 130 setTouchScrolling(0, el); 131 132 el.addEventListener('touchstart', function(e) { 133 lastTouchY = e.touches[0].pageY; 134 }, {passive: true}); 135 136 el.addEventListener('touchmove', function(e) { 137 var currentY = e.touches[0].pageY; 138 var d = currentY - lastTouchY; 139 lastTouchY = currentY; 140 var atTop = el.scrollTop === 0; 141 var atBottom = el.scrollTop + el.clientHeight >= el.scrollHeight; 142 if ((atTop && atBottom) || (d < 0 && atBottom) || (d > 0 && atTop)) { 143 e.preventDefault(); 144 } 145 }, {passive: false}); 146 147 el.addEventListener('touchend', function() { 148 lastTouchY = null; 149 }, {passive: true}); 150 } 151 }); 152 return this; 153 }; 154 155})(jQuery); 156;(function($){ 157 158 var NAR = NGIN.AdResizer = function(selector, config) { 159 this.$el = $(selector); // this might need to be a direct reference to the ad-wrapper element 160 this.el = this.$el[0]; 161 this.bindEvents(); 162 } 163 164 $.extend(NAR, { 165 setup: function() { 166 googletag.pubads().addEventListener('slotRenderEnded', function(e) { 167 var adElement = $('#' + e.slot.getSlotElementId()); 168 adElement.trigger('adSlotRendered', e); 169 }); 170 } 171 }); 172 173 $.extend(NAR.prototype, { 174 175 bindEvents: function() { 176 $(window).on('resize', this.resize.bind(this)); 177 this.$el.on('adSlotRendered', this.resize.bind(this)); 178 setTimeout(this.resize.bind(this), 0); 179 }, 180 181 resize: function() { 182 this.setSize(1, 0); // reset 183 var scale = Math.min(this.el.clientWidth / this.el.scrollWidth, 1); 184 var marginBottom = this.el.scrollHeight * (scale - 1); 185 this.setSize(scale, marginBottom); 186 }, 187 188 setSize: function(scale, marginBottom) { 189 this.$el.children().css('transform', 'scale(' + scale + ')'); 190 this.$el.css('marginBottom', marginBottom + 'px'); 191 } 192 193 }); 194 195 if (typeof googletag == 'object') googletag.cmd.push(NGIN.AdResizer.setup); 196 197})(jQuery); 198;(function($) { 199 200 if (!NGIN) NGIN = {} 201 if (!NGIN.Modules) NGIN.Modules = {} 202 203 var $win = $(window); 204 205 var NS = NGIN.Modules.NavService = { 206 207 promises: {}, 208 modules: {}, 209 210 register: function(className, methods) { 211 var initialize = methods.initialize || $.noop; 212 var module = this.modules[className] = function(opts) { 213 this._setup(initialize, opts) // shared setup method 214 } 215 $.extend(module.prototype, this.sharedMethods, methods); 216 delete module.prototype.initialize; 217 }, 218 219 create: function(className, opts) { 220 var Module = this.modules[className]; 221 return Module && new Module(opts); 222 }, 223 224 fixedNav: function(el) {
225 var $nav = $(el); 226 var $els = $nav.add('html'); 227 var navTop = $nav.offset().top; 228 var wasFixed = false; 229 230 $(window).scroll(function(){ 231 var scrollTop = window.scrollY || window.pageYOffset || 0; 232 var isFixed = navTop < scrollTop; 233 if (!wasFixed && isFixed) { 234 $els.addClass('nav-fixed nav-fixed-start'); 235 setTimeout(function(){ 236 $els.removeClass('nav-fixed-start'); 237 }, 1) 238 } 239 else if (wasFixed && !isFixed) { 240 $els.removeClass('nav-fixed'); 241 } 242 wasFixed = isFixed; 243 }); 244 }, 245 246 sharedMethods: { 247 248 // PUBLIC METHODS 249 250 getNavData: function(url) { 251 var nav = this; 252 return this.preload((this.nav_url = url)); 253 }, 254 255 preload: function(e) { 256 var url = typeof e === 'string' ? e : $(e.currentTarget).attr('data-nav-url'); 257 return NS.promises[url] = NS.promises[url] || $.ajax({ 258 dataType: "json", 259 url: url, 260 error: this._preloadError.bind(this, url) 261 }); 262 }, 263 264 // PRIVATE 265 266 _setup: function(initialize, opts) { 267 this.$html = $('html'); 268 this.$el = $(opts.selector).first(); 269 this.$navLinks = $($('#navLinksTemplate').html()); 270 this.$pageSearch = $($('#pageSearchTemplate').html()); 271 272 this.activeClass = opts.activeClass || ''; 273 this.collapsedClass = opts.collapsedClass || ''; 274 this.navUrl = opts.data.nav_url; 275 this.paywallImage = '<img src="' + opts.paywallImage + '" class="paywall_image">'; 276 this.minWidth = opts.minWidth || 0; 277 this.collapseWidth = opts.collapseWidth || 0; 278 this.maxWidth = opts.maxWidth || Infinity; 279 this.currentId = opts.currentId; 280 this.opts = opts; 281 282 this._cache(opts.data); 283 this._bindEvents(); 284 285 initialize.call(this, opts); 286 287 // render should come last 288 this.getNavData(this.navUrl) 289 .then(this._render.bind(this)); 290 }, 291 292 _bindEvents: function() { 293 $win.on('resize', this._resize.bind(this)); 294 }, 295 296 _render: function(navData, opts) { 297 this.render(navData, opts); 298 this.rendered = this.visible = true; 299 this._resize(); 300 }, 301 302 _okToShow: function() { 303 return window.innerWidth >= this.minWidth && window.innerWidth <= this.maxWidth; 304 }, 305 306 _resize: function() { 307 if (!this.rendered) return; 308 309 // note, you can specify a collapse class in your config OR in the nav module 310 var collapse = this.collapsedClass && window.innerWidth <= this.collapseWidth; 311 var show = this._okToShow(); 312 313 // set html class if visible 314 this.$html.toggleClass(this.activeClass, show); 315 if (this.collapsedClass) this.$html.toggleClass(this.collapsedClass, show && collapse); 316 317 // show or hide as needed 318 if (show && !this.visible) { 319 this.visible = true; 320 this.$el.show(); 321 } else if (!show && this.visible) { 322 this.visible = false; 323 this.$el.hide(); 324 } 325 326 if (this.resize) this.resize(); 327 }, 328 329 _cache: function(navData) { 330 var dfd = $.Deferred(); 331 dfd.resolve(navData); 332 NS.promises[navData.nav_url] = dfd.promise(); 333 }, 334 335 _preloadError: function(url) { 336 delete NS.promises[url]; 337 } 338 } 339 340 } 341 342})(jQuery); 343;(function($) { 344 345 var NEXT_TICK = 30; 346 var OPEN_DELAY = 200; 347 var CLOSE_DELAY = 500; 348 var OPENING_CLASS = 'dropdown-opening'; 349 var OPEN_CLASS = 'dropdown-open'; 350 var OPEN_CLASSES = OPEN_CLASS + ' ' + OPENING_CLASS; 351 var DROPDOWN_MARGIN_TOP = 0; 352 var DROPDOWN_MARGIN_BOTTOM = 30; 353 var DROPDOWN_WIDTH = 200; 354 var DROPDOWN_ALIGN_LEFT = 'dropdown-align-left'; 355 var DROPDOWN_ALIGN_RIGHT = 'dropdown-align-right'; 356 var DROPDOWN_ALIGN_CLASSES = DROPDOWN_ALIGN_LEFT + ' ' + DROPDOWN_ALIGN_RIGHT; 357 var LOAD_ERROR_RETRY_DELAY = 5000; 358 var RATE_LIMIT_RETRY_DELAY = 30000; 359 360 function realOuterWidth(el) { 361 if (!el || !el.style) return 0; 362 var s = window.getComputedStyle(el); 363 return parseFloat(s.width) + parseFloat(s.paddingRight) + parseFloat(s.paddingLeft) + parseFloat(s.borderRightWidth) + parseFloat(s.borderLeftWidth); 364 } 365 366 NGIN.Modules.NavService.register('DropdownNav', { 367 368 initialize: function(opts) { 369 this.openDelay = opts.openDelay || OPEN_DELAY; 370 this.closeDelay = opts.closeDelay || CLOSE_DELAY; 371 this.$navItems = $('<ul class="theme-nav theme-nav-style-dropdown" data-nav-level="1" />').appendTo(this.$el); 372 this.$moreButton = $('<li class="theme-nav-item theme-nav-more hasChild"><a class="theme-nav-link">More</a></li>'); 373 this.$moreMenu = $('<ul class="theme-nav-dropdown" data-nav-level="2" />').width(DROPDOWN_WIDTH).scrollGuard(); 374 this.$moreButton.data('dropdown', this.$moreMenu); 375 this.$moreMenu.data('navItem', this.$moreButton); 376 this.bindEvents(); 377 }, 378 379 bindEvents: function() { 380 $(window).one('load', this.checkOverflowAfterLoad.bind(this)); 381 $(document).on('scroll', this.closeAllDropdowns.bind(this)); 382 this.$el 383 .on('mouseenter', '.theme-nav-dropdown', this.dropdownOver.bind(this)) 384 .on('mouseleave', '.theme-nav-dropdown', this.dropdownOut.bind(this)) 385 .on('mouseenter', '.theme-nav-item', this.itemOver.bind(this)) 386 .on('mouseleave', '.theme-nav-item', this.itemOut.bind(this)) 387 }, 388 389 itemOver: function(e) {
390 var $navItem = $(e.currentTarget); 391 $navItem.data('over', true); 392 if ($navItem.hasClass('hasChild')) this.showDropdown($navItem); 393 this.keepDropdownOpen($navItem.data('parentDropdown')); 394 }, 395 396 itemOut: function(e) { 397 var $navItem = $(e.currentTarget); 398 var $dropdown = $navItem.data('dropdown'); 399 $navItem.data('over', false); 400 this.allowDropdownToClose($dropdown); 401 }, 402 403 dropdownOver: function(e) { 404 var $dropdown = $(e.currentTarget); 405 $dropdown.data('over', true); 406 this.keepDropdownOpen($dropdown); 407 }, 408 409 dropdownOut: function(e) { 410 var $dropdown = $(e.currentTarget); 411 $dropdown.data('over', false); 412 this.allowDropdownToClose($dropdown); 413 }, 414 415 closeAllDropdowns: function() { 416 this.closeItems(this.$navItems.children()); 417 }, 418 419 render: function(navData) { 420 var showChildren = this.opts.showChildren && navData.children.length; 421 var items = showChildren ? navData.children : navData.siblings; 422 var renderOpts = { 423 items: items, 424 level: 1 425 } 426 427 if (this.opts.showTitle) { 428 var titleObj = showChildren ? navData : navData.parent; 429 renderOpts.title = titleObj.name; 430 renderOpts.titleLink = titleObj.url; 431 } 432 433 this.renderNavItems(this.$navItems, renderOpts); 434 this.$el.append(this.$moreMenu); 435 this.handleOverflow(); 436 }, 437 438 resize: function() { 439 // need to only run this when showing the first time??? 440 this.resetOverflow(); 441 }, 442 443 showDropdown: function($navItem) { 444 var nav = this; 445 var $dropdown = this.renderDropdown($navItem); 446 var $itemAndDropdown = $navItem.add($dropdown); 447 var openDelay = $dropdown.is(':visible') ? 0 : this.openDelay; 448 449 // keep this drop open 450 clearTimeout($dropdown.data('openTimeout')); 451 clearTimeout($dropdown.data('closeTimeout')); 452 453 this.positionDropdown($dropdown); 454 455 function startOpening() { 456 if (!$navItem.data('over') && !$dropdown.data('over')) return; 457 nav.loadDropdown($navItem, $dropdown); 458 nav.closeItems($navItem.siblings()); 459 $itemAndDropdown.addClass(OPENING_CLASS); 460 $dropdown.data('openTimeout', setTimeout(finishOpening, NEXT_TICK)); 461 } 462 463 function finishOpening() { 464 if ($navItem.hasClass(OPENING_CLASS)) { 465 $itemAndDropdown 466 .addClass(OPEN_CLASS) 467 .removeClass(OPENING_CLASS); 468 } 469 } 470 471 $dropdown.data('openTimeout', setTimeout(startOpening, openDelay)); 472 }, 473 474 keepDropdownOpen: function($dropdown) { 475 if (!$dropdown) return 476 $dropdown.data('over', true); 477 clearTimeout($dropdown.data('closeTimeout')); 478 var $parentItem = $dropdown.data('navItem'); 479 if ($parentItem) this.keepDropdownOpen($parentItem.data('parentDropdown')); 480 }, 481 482 allowDropdownToClose: function($dropdown) { 483 if (!$dropdown) return 484 $dropdown.data('over', false); 485 $dropdown.data('closeTimeout', setTimeout(this.closeDropdown.bind(this, $dropdown, true), this.closeDelay)); 486 var $parentItem = $dropdown.data('navItem'); 487 if ($parentItem) this.allowDropdownToClose($parentItem.data('parentDropdown')); 488 }, 489 490 closeItems: function($items) { 491 var nav = this; 492 $items.each(function(i, item) { 493 nav.closeDropdown($(item).data('dropdown')); 494 }); 495 }, 496 497 closeDropdown: function($dropdown, bubble) { 498 if (!$dropdown) return 499 var $navItem = $dropdown.data('navItem'); 500 if ($navItem.data('over') || $dropdown.data('over')) return 501 $navItem.removeClass(OPEN_CLASSES); 502 this.closeItems($dropdown.removeClass(OPEN_CLASSES).children()); 503 if (bubble) this.closeDropdown($navItem.data('parentDropdown')); 504 }, 505 506 positionDropdown: function($dropdown) { 507 // start by resetiing the dropdown state 508 $dropdown 509 .css('maxHeight', '') 510 .removeClass(DROPDOWN_ALIGN_CLASSES); 511 512 var $navItem = $dropdown.data('navItem'); 513 var $parentDropdown = $navItem.data('parentDropdown'); 514 var renderBelow = $dropdown.data('nav-level') < 3; 515 var navOffset = this.$el.offset(); 516 var navItemsOffset = this.$navItems.offset(); 517 var itemOffset = $navItem.offset(); 518 var windowHeight = $(window).height(); 519 var windowScroll = $(window).scrollTop(); 520 var maxX = this.$navItems.outerWidth() + navItemsOffset.left - navOffset.left; 521 var navHeight = this.$navItems.outerHeight(); 522 var navItemWidth = $navItem.outerWidth(); 523 var width = navItemWidth > DROPDOWN_WIDTH ? navItemWidth : DROPDOWN_WIDTH; 524 var height = $dropdown.outerHeight(); 525 var minTop = navHeight; 526 var fixedBottom = navOffset.top + navHeight - windowScroll; 527 var maxHeight = windowHeight - fixedBottom - DROPDOWN_MARGIN_BOTTOM; 528 529 // allow second level menues to shift down 530 if (!renderBelow) minTop += DROPDOWN_MARGIN_TOP; 531 532 var maxBottom = minTop + maxHeight; 533 var left; 534 var top; 535 var alignmentClass = DROPDOWN_ALIGN_LEFT; 536 537 // first level drop downs 538 if (renderBelow) { 539 top = minTop; 540 left = itemOffset.left - navOffset.left; 541 542 if (left + width > maxX) { 543 alignmentClass = DROPDOWN_ALIGN_RIGHT; 544 left = left - width + navItemWidth; 545 } 546 } 547 548 // second level dropdowns 549 else { 550 var widthOffset = $parentDropdown ? $parentDropdown.outerWidth() : navItemWidth; 551 top = Math.max(itemOffset.top - navOffset.top, minTop); 552 left = itemOffset.left - navOffset.left + widthOffset; 553 554 if (left + width > maxX) { 555 alignmentClass = DROPDOWN_ALIGN_RIGHT; 556 left = left - width - navItemWidth; 557 } 558 } 559 560 // Shift menu up if is scrolls and there is space to do so 561 if (top + height > maxBottom) { 562 top = Math.max(maxBottom - height, minTop); 563 } 564 565 // update dropdown state 566 $dropdown 567 .addClass(alignmentClass) 568 .css({ 569 top: top, 570 left: left, 571 maxHeight: maxHeight, 572 width: width, 573 overflow: 'auto' 574 }); 575 }, 576 577 showNextLevel: function(level) { 578 var checkDepth = 'maxDepth' in this.opts && level; 579 return checkDepth && level <= this.opts.maxDepth || false; 580 }, 581 582 loadDropdown: function($navItem, $dropdown) { 583 if ($dropdown.data('loadRequested') || !$dropdown.hasClass('theme-nav-dropdown') || !$dropdown.hasClass('loading')) return; 584 if (this.dropdownLoadCoolingDown($dropdown)) return; 585 586 var nav = this; 587 var navUrl = $navItem.data('nav-url'); 588 var level = $dropdown.data('nav-level'); 589 590 if (!navUrl) return; 591 592 $dropdown.data('loadRequested', true); 593 594 function renderItems(navData) { 595 $dropdown.removeData('loadRetryAt'); 596 nav.renderNavItems($dropdown, { 597 items: navData.children, 598 level: level 599 }); 600 var items = $dropdown.children(); 601 items.data('parentItem', $navItem); 602 items.data('parentDropdown', $dropdown); 603 // add scroll binding to hide submenus 604 $dropdown.on('scroll', nav.closeItems.bind(nav, $dropdown.children())); 605 nav.positionDropdown($dropdown); 606 } 607 608 function handleLoadError(xhr) { 609 $dropdown.removeData('loadRequested'); 610 $dropdown.data('loadRetryAt', nav.nextDropdownLoadRetryAt(xhr)); 611 } 612 613 nav.getNavData(navUrl).then(renderItems, handleLoadError); 614 }, 615 616 dropdownLoadCoolingDown: function($dropdown) { 617 var retryAt = $dropdown.data('loadRetryAt'); 618 if (!retryAt) return false;
619 if (retryAt > new Date().getTime()) return true; 620 $dropdown.removeData('loadRetryAt'); 621 return false; 622 }, 623 624 nextDropdownLoadRetryAt: function(xhr) { 625 var retryAfter = xhr && xhr.getResponseHeader && parseInt(xhr.getResponseHeader('Retry-After'), 10); 626 var delay = retryAfter ? retryAfter * 1000 : xhr && xhr.status === 429 ? RATE_LIMIT_RETRY_DELAY : LOAD_ERROR_RETRY_DELAY; 627 return new Date().getTime() + delay; 628 }, 629 630 renderDropdown: function($navItem) { 631 var $dropdown = $navItem.data('dropdown'); 632 if ($dropdown) return $dropdown; 633 634 var level = ($navItem.parent().data('nav-level') || 1) + 1 635 636 $dropdown = $('<ul class="loading theme-nav-dropdown" data-nav-level="' + level + '" />'); 637 $dropdown.width(DROPDOWN_WIDTH); 638 $dropdown.scrollGuard(); // prevents overflow scrolling 639 $navItem.data('dropdown', $dropdown); 640 $dropdown.data('navItem', $navItem); 641 642 return $dropdown.appendTo(this.$el); 643 }, 644 645 renderNavItems: function($container, opts) { 646 var nav = this; 647 $container.removeClass('loading'); 648 649 if (opts.title) { 650 $container.append('<li class="theme-nav-title"><a href="' + opts.titleLink + '">' + opts.title + '</a></li>'); 651 } 652 653 opts.items.forEach(function(navItem){ 654 var classes = [].concat(navItem.css_classes||[]); 655 var $navItem = $('<li class="theme-nav-item" />'); 656 657 if (navItem.id === nav.currentId) classes.push('selected'); 658 if (navItem.display_status) classes.push(navItem.display_status); 659 660 if (nav.showNextLevel(opts.level)) { 661 if (navItem.has_child) classes.push('hasChild'); 662 if (navItem.nav_url) $navItem.attr('data-nav-url', navItem.nav_url); 663 } 664
665 $navItem.addClass(classes.join(' ')); 666 667 var $link = $('<a class="theme-nav-link" />') 668 .appendTo($navItem) 669 .text(navItem.name) 670 .attr({ 671 href: navItem.url, 672 title: navItem.title, 673 target: navItem.target || '_self' 674 }); 675 676 if (navItem.display_status === 'paywalled' && nav.paywallImage) { 677 $link.prepend(nav.paywallImage + ' '); 678 } 679 680 $container.append($navItem); 681 }) 682 }, 683 684 checkOverflowAfterLoad: function() { 685 var overflowWidths = this.overflowWidths || []; 686 var firstItemWidth = this.$navItems.children().first().outerWidth(); 687 this.resetOverflow(); 688 }, 689 690 resetOverflow: function() { 691 delete this.overflowWidths; 692 this.$moreButton.detach(); 693 this.$navItems.append(this.$moreMenu.children()); 694 this.handleOverflow(); 695 }, 696 697 handleOverflow: function() { 698 var navItems = this.$navItems; 699 var moreMenu = this.$moreMenu; 700 var moreButton = this.$moreButton; 701 var visibleItems = navItems.children().not(moreButton); 702 var oldExtraItems = moreMenu.children(); 703 var allItems = visibleItems.add(oldExtraItems); 704 705 var overflowWidths = this.getOverflowWidths(); 706 var moreButtonWidth = this.moreButtonWidth; 707 var maxWidth = this.$navItems.width(); 708 var totalWidth = 0; 709 var hasExtraItems = false; 710 var index = 0; 711 712 // fit as many items in as possible 713 while (index < overflowWidths.length) { 714 var itemWidth = overflowWidths[index]; 715 if (totalWidth + itemWidth > maxWidth) { 716 hasExtraItems = true; 717 break; 718 } 719 totalWidth += itemWidth; 720 index++; 721 } 722 723 // backtrack if more button won't fit 724 while (hasExtraItems && totalWidth + moreButtonWidth > maxWidth) { 725 index--; 726 totalWidth -= overflowWidths[index]; 727 } 728 729 var newExtraItems = hasExtraItems && allItems.slice(index).not(oldExtraItems); 730 var newVisibleItems = hasExtraItems && oldExtraItems.not(newExtraItems); 731 732 function changeNavLevel(delta) { 733 return function(i, navItem) { 734 navItem = $(navItem); 735 navItem.data('nav-level', delta + navItem.data('nav-level')); 736 } 737 } 738 739 function setParents(parentItem, parentDropdown) { 740 return function(i, navItem) { 741 navItem = $(navItem); 742 navItem.data('parentItem', parentItem); 743 navItem.data('parentDropdown', parentDropdown); 744 } 745 } 746 747 if (newVisibleItems) { 748 newVisibleItems.each(changeNavLevel(-1)) 749 moreButton.before(newVisibleItems); 750 newVisibleItems.each(setParents(null, null)) 751 } 752 753 if (newExtraItems) { 754 newExtraItems.each(changeNavLevel(1)) 755 moreMenu.prepend(newExtraItems); 756 newExtraItems.each(setParents(moreButton, moreMenu)) 757 } 758 759 if (!hasExtraItems) moreButton.detach(); 760 }, 761 762 getOverflowWidths: function() { 763 if (this.overflowWidths) return this.overflowWidths; 764 765 var overflowWidths = this.overflowWidths = []; 766 var navItems = this.$navItems.children(); 767 var moreButton = this.$moreButton.appendTo(this.$navItems); 768 this.moreButtonWidth = Math.ceil(realOuterWidth(moreButton.get(0))); 769 770 navItems.each(function(i, item) { 771 overflowWidths[i] = Math.ceil(realOuterWidth(item)); 772 }); 773 774 return overflowWidths; 775 } 776 777 }); 778 779})(jQuery); 780;(function($) { 781 var MEGA_NAV_SELECTOR = '.mega-nav, .mega_nav'; 782 783 function installMegaNavPatch() { 784 if (!window.NGIN || !NGIN.Modules || !NGIN.Modules.NavService) return false; 785 786 var NS = NGIN.Modules.NavService; 787 if (NS.__sharedMegaNavPatchInstalled__) return true; 788 789 function patchMethods(methods) { 790 if (!methods || methods.__sharedMegaNavMethodsPatched__) return methods; 791 792 var patched = $.extend({}, methods); 793 var originalBindEvents = patched.bindEvents; 794 var originalCloseDropdown = patched.closeDropdown; 795 var originalPositionDropdown = patched.positionDropdown; 796 var originalRenderDropdown = patched.renderDropdown; 797 var originalRenderNavItems = patched.renderNavItems; 798 var originalResize = patched.resize; 799 800 patched.bindEvents = function() { 801 originalBindEvents.call(this); 802 this.$el 803 .on('mouseenter', '.mega-nav-dropdown', this.dropdownOver.bind(this)) 804 .on('mouseleave', '.mega-nav-dropdown', this.dropdownOut.bind(this)); 805 }; 806 807 patched.renderDropdown = function($navItem) { 808 var $dropdown = $navItem.data('dropdown'); 809 if ($dropdown) return $dropdown; 810 811 if (!$navItem.data('parentItem') && isMegaNavItem($navItem)) { 812 return this.renderMegaDropdown($navItem); 813 } 814 815 return originalRenderDropdown.call(this, $navItem); 816 }; 817 818 patched.positionDropdown = function($dropdown) { 819 if ($dropdown && $dropdown.hasClass('mega-nav-dropdown')) { 820 return this.positionMegaDropdown($dropdown); 821 } 822 823 return originalPositionDropdown.call(this, $dropdown); 824 }; 825 826 patched.closeDropdown = function($dropdown, bubble) { 827 var result = originalCloseDropdown.call(this, $dropdown, bubble); 828 829 if ($dropdown && $dropdown.hasClass('mega-nav-dropdown') && !isDropdownOpen($dropdown)) { 830 resetCollapsedItems($dropdown); 831 } 832 833 return result; 834 }; 835 836 patched.resize = function() { 837 if (originalResize) originalResize.call(this); 838 839 this.$el.find('.mega-nav-dropdown').each(function(i, dropdown) { 840 var $dropdown = $(dropdown); 841 if (isDropdownOpen($dropdown)) this.positionDropdown($dropdown); 842 }.bind(this)); 843 }; 844 845 patched.renderMegaDropdown = function($navItem) { 846 var nav = this; 847 var $dropdown = $('<div class="mega-nav-dropdown" data-nav-level="2" />').scrollGuard(); 848 var megaChildren = $navItem.data('mega-nav-children'); 849 var maxItems = parseInt($navItem.data('mega-nav-max-items'), 10) || 5; 850 851 $navItem.data('dropdown', $dropdown); 852 $dropdown 853 .data('navItem', $navItem) 854 .appendTo(this.$el); 855 856 if (megaChildren) { 857 this.renderMegaColumns($dropdown, megaChildren, maxItems); 858 this.positionDropdown($dropdown); 859 } else { 860 $dropdown.addClass('loading'); 861 nav.getNavData($navItem.data('nav-url')).then(function(navData) { 862 $dropdown.removeClass('loading'); 863 nav.renderMegaColumns($dropdown, navData.children, maxItems); 864 nav.positionDropdown($dropdown); 865 }); 866 } 867 868 return $dropdown; 869 }; 870 871 patched.renderMegaColumns = function($dropdown, children, maxItems) { 872 var maxVisible = maxItems || 5; 873 var generalItems = []; 874 var $columnsWrap = $('<div class="mega-nav-columns-wrap" />'); 875 876 applyDropdownStyles(this, $dropdown); 877 878 $.each(children || [], function(i, child) { 879 if (!canAccess(child)) return; 880 881 var childNodes = $.grep(child.children || [], canAccess); 882 if (!childNodes.length) { 883 generalItems.push(child); 884 return; 885 } 886 887 var $column = $('<div class="mega-nav-column" />') 888 .append(buildMegaLink('mega-nav-column-header', child)); 889 890 appendMegaItems($column, childNodes, maxVisible); 891 $columnsWrap.append($column); 892 }); 893 894 setMegaColumnCount($columnsWrap, maxMegaColumnCount($columnsWrap)); 895 896 if (generalItems.length) { 897 var $generalColumn = $('<div class="mega-nav-column mega-nav-column-general" />'); 898 appendMegaItems($generalColumn, generalItems, Infinity); 899 $dropdown.append($generalColumn); 900 } 901 902 $dropdown.append($columnsWrap); 903 fitGeneralColumn($dropdown, generalItems); 904 }; 905 906 patched.positionMegaDropdown = function($dropdown) {
907 var $navItem = $dropdown.data('navItem'); 908 var navHeight = this.$navItems.outerHeight(); 909 var elOffsetLeft = this.$el.offset().left; 910 var windowLeft = $(window).scrollLeft(); 911 var windowRight = windowLeft + $(window).width(); 912 var elWidth = this.$el.outerWidth(); 913 var visibleLeft = Math.max(0, windowLeft - elOffsetLeft); 914 var visibleRight = Math.min(elWidth, windowRight - elOffsetLeft); 915 var availableWidth = Math.max(0, visibleRight - visibleLeft); 916 var navBottom = this.$el.offset().top + navHeight - $(window).scrollTop(); 917 var maxHeight = $(window).height() - navBottom - 20; 918 var itemLeft = $navItem.offset().left - elOffsetLeft; 919 var itemRight = itemLeft + $navItem.outerWidth(); 920 fitMegaColumnsToWidth($dropdown, availableWidth); 921 var measuredWidth = measureMegaWidth($dropdown); 922 var dropdownWidth = Math.min(measuredWidth || availableWidth, availableWidth); 923 var maxLeft = Math.max(visibleLeft, visibleRight - dropdownWidth); 924 var left = itemLeft + dropdownWidth > visibleRight ? itemRight - dropdownWidth : itemLeft; 925 926 $dropdown.css({ 927 top: navHeight - 1, 928 width: dropdownWidth, 929 maxWidth: availableWidth, 930 maxHeight: maxHeight, 931 overflowX: 'auto', 932 overflowY: 'auto', 933 left: Math.max(visibleLeft, Math.min(left, maxLeft)), 934 transform: '' 935 }); 936 }; 937 938 patched.renderNavItems = function($container, opts) { 939 var existingCount = $container.children('.theme-nav-item').length; 940 originalRenderNavItems.call(this, $container, opts); 941 942 var items = opts.items || []; 943 $container.children('.theme-nav-item').slice(existingCount).each(function(index) { 944 var navItem = items[index]; 945 if (!navItem) return; 946 947 var $navItem = $(this); 948 if (navItem.mega_nav_children) $navItem.data('mega-nav-children', navItem.mega_nav_children); 949 if (navItem.mega_nav_max_items) $navItem.data('mega-nav-max-items', navItem.mega_nav_max_items); 950 951 if (isMegaNavItem($navItem)) { 952 $navItem.addClass('hasChild'); 953 if (navItem.nav_url) $navItem.attr('data-nav-url', navItem.nav_url); 954 } 955 }); 956 }; 957 958 patched.__sharedMegaNavMethodsPatched__ = true; 959 return patched; 960 } 961 962 function patchPrototype(Module) { 963 if (!Module || !Module.prototype || Module.prototype.__sharedMegaNavPrototypePatched__) return; 964 965 $.extend(Module.prototype, patchMethods(Module.prototype)); 966 Module.prototype.__sharedMegaNavPrototypePatched__ = true; 967 } 968 969 if (!NS.__sharedMegaNavRegisterWrapped__) { 970 var originalRegister = NS.register; 971 NS.register = function(className, methods) { 972 if (className === 'DropdownNav') methods = patchMethods(methods); 973 var result = originalRegister.call(this, className, methods); 974 if (className === 'DropdownNav') patchPrototype(this.modules && this.modules.DropdownNav); 975 return result; 976 }; 977 NS.__sharedMegaNavRegisterWrapped__ = true; 978 } 979 980 patchPrototype(NS.modules && NS.modules.DropdownNav); 981 NS.__sharedMegaNavPatchInstalled__ = true; 982 return true; 983 } 984 985 function appendMegaItems($column, items, maxVisible) { 986 $.each(items, function(i, item) { 987 var $item = buildMegaLink('mega-nav-column-item', item); 988 if (items.length > maxVisible + 1 && i >= maxVisible) $item.addClass('mega-nav-hidden-item').hide(); 989 $column.append($item); 990 }); 991 992 if (items.length > maxVisible + 1) appendMoreLink($column); 993 } 994 995 function appendMoreLink($column) { 996 $('<a class="mega-nav-column-item mega-nav-more-link" href="#" />') 997 .text('More...') 998 .on('click', function(e) { 999 e.preventDefault(); 1000 $(this).siblings('.mega-nav-hidden-item').show(); 1001 $(this).hide(); 1002 }) 1003 .appendTo($column); 1004 } 1005 1006 function buildMegaLink(className, item) { 1007 var $link = $('<a class="' + className + '" />') 1008 .text(item.name) 1009 .attr({ 1010 href: item.url || item.nav_url || item.path || item.full_url || '#', 1011 title: item.title || item.name, 1012 target: item.target || '_self' 1013 }); 1014 1015 if (isDisabled(item)) $link.addClass('mega-nav-muted'); 1016 return $link; 1017 } 1018 1019 function applyDropdownStyles(nav, $dropdown) { 1020 var $source = findSourceDropdown(nav); 1021 var removeSource = !$source.length; 1022 1023 if (removeSource) { 1024 $source = $( 1025 '<div class="theme-nav-dropdown" data-nav-level="2" style="display:block;visibility:hidden;position:absolute;
1025left:-9999px;top:-9999px;">' + 1026 '<div class="theme-nav-item"><a class="theme-nav-link" href="#">Sample</a></div>' + 1027 '<div class="theme-nav-item disabled"><a class="theme-nav-link" href="#">Sample</a></div>' + 1028 '</div>' 1029 ).appendTo(nav.$el); 1030 } 1031 1032 copyStyleProperty($dropdown, $source[0], 'zIndex', 'z-index'); 1033 copyStyleVar($dropdown, $source[0], 'backgroundColor', '--mega-dropdown-bg'); 1034 copyStyleVar($dropdown, $source.find('.theme-nav-item').first()[0], 'borderBottomColor', '--mega-separator-color'); 1035 copyStyleVar($dropdown, $source.find('.theme-nav-link').first()[0], 'color', '--mega-link-color'); 1036 copyStyleVar($dropdown, $source.find('.theme-nav-link').first()[0], 'color', '--mega-link-hover-color'); 1037 copyStyleVar($dropdown, $source.find('.disabled .theme-nav-link, .disabled a').first()[0], 'color', '--mega-link-disabled-color'); 1038 1039 if (removeSource) $source.remove(); 1040 } 1041 1042 function copyStyleProperty($target, source, property, cssProperty) { 1043 if (!source) return; 1044 1045 var value = window.getComputedStyle(source)[property]; 1046 if (value && value !== 'auto') $target[0].style.setProperty(cssProperty, value); 1047 } 1048 1049 function copyStyleVar($target, source, property, cssVar) { 1050 if (!source) return; 1051 1052 var value = window.getComputedStyle(source)[property]; 1053 if (value && value !== 'transparent' && value !== 'rgba(0, 0, 0, 0)') { 1054 $target[0].style.setProperty(cssVar, value); 1055 } 1056 } 1057 1058 function canAccess(item) { 1059 if (!item) return false; 1060 if (item.hasOwnProperty('can_access')) return !!item.can_access; 1061 if (item.hasOwnProperty('can_view')) return !!item.can_view; 1062 if (item.hasOwnProperty('accessible')) return !!item.accessible; 1063 if (item.hasOwnProperty('authorized')) return !!item.authorized; 1064 return true; 1065 } 1066 1067 function fitGeneralColumn($dropdown, generalItems) { 1068 var $columnsWrap = $dropdown.find('.mega-nav-columns-wrap'); 1069 if (!generalItems.length || !$columnsWrap.children().length) return; 1070 1071 var wasHidden = !$dropdown.is(':visible'); 1072 if (wasHidden) $dropdown.css({ display: 'flex', visibility: 'hidden', opacity: 0 }); 1073 1074 var columnsHeight = $columnsWrap.outerHeight(); 1075 var $items = $dropdown.find('.mega-nav-column-general .mega-nav-column-item'); 1076 var totalHeight = 0; 1077 var visibleCount = 0; 1078 1079 $items.each(function() { 1080 totalHeight += $(this).outerHeight(true); 1081 if (totalHeight <= columnsHeight) visibleCount++; 1082 else return false; 1083 }); 1084 1085 if (visibleCount < generalItems.length && generalItems.length - visibleCount > 1) { 1086 visibleCount = Math.max(visibleCount - 1, 3); 1087 $items.each(function(i) { 1088 if (i >= visibleCount) $(this).addClass('mega-nav-hidden-item').hide(); 1089 }); 1090 appendMoreLink($dropdown.find('.mega-nav-column-general')); 1091 } 1092 1093 if (wasHidden) $dropdown.css({ display: '', visibility: '', opacity: '' }); 1094 } 1095 1096 function findSourceDropdown(nav) { 1097 return nav.$el.find('.theme-nav-dropdown:not(.mega-nav-dropdown)').filter(function() { 1098 return $(this).find('.theme-nav-item').not(MEGA_NAV_SELECTOR).length > 0; 1099 }).first(); 1100 } 1101 1102 function isDisabled(item) { 1103 if (!item) return false; 1104 if (item.disabled === true || item.is_disabled === true) return true; 1105 1106 var status = ((item.display_status || '') + '').toLowerCase(); 1107 return !!status && status !== 'enabled' && status !== 'public' && status !== 'paywalled'; 1108 } 1109 1110 function isMegaNavItem($navItem) { 1111 return $navItem.is(MEGA_NAV_SELECTOR); 1112 } 1113 1114 function isDropdownOpen($dropdown) { 1115 return $dropdown.hasClass('dropdown-open') || $dropdown.hasClass('dropdown-opening'); 1116 } 1117 1118 function fitMegaColumnsToWidth($dropdown, availableWidth) { 1119 var $columnsWrap = $dropdown.find('.mega-nav-columns-wrap'); 1120 var maxCols = maxMegaColumnCount($columnsWrap); 1121 var minCols = maxCols > 1 ? 2 : 1; 1122 var cols; 1123 1124 if (!$columnsWrap.length || !availableWidth) return maxCols; 1125 1126 for (cols = maxCols; cols > minCols; cols--) { 1127 setMegaColumnCount($columnsWrap, cols); 1128 if (measureMegaWidth($dropdown) <= availableWidth) return cols; 1129 } 1130 1131 setMegaColumnCount($columnsWrap, minCols); 1132 return minCols; 1133 } 1134 1135 function maxMegaColumnCount($columnsWrap) { 1136 if (!$columnsWrap.length) return 1; 1137 1138 return Math.max(1, Math.min(4, $columnsWrap.children().length)); 1139 } 1140 1141 function setMegaColumnCount($columnsWrap, columnCount) { 1142 if ($columnsWrap.length) { 1143 $columnsWrap[0].style.setProperty('--mega-nav-cols-per-row', columnCount); 1144 } 1145 } 1146 1147 function measureMegaWidth($dropdown) { 1148 var wasHidden = !$dropdown.is(':visible'); 1149 var el = $dropdown[0]; 1150 var display = el.style.display; 1151 var maxWidth = el.style.maxWidth; 1152 var opacity = el.style.opacity; 1153 var visibility = el.style.visibility; 1154 var widthStyle = el.style.width; 1155 1156 $dropdown.css({ width: '', maxWidth: '' }); 1157 if (wasHidden) $dropdown.css({ display: 'flex', visibility: 'hidden', opacity: 0 }); 1158 1159 var width = $dropdown.outerWidth() || 0; 1160 1161 el.style.display = display; 1162 el.style.maxWidth = maxWidth; 1163 el.style.opacity = opacity; 1164 el.style.visibility = visibility; 1165 el.style.width = widthStyle; 1166 1167 return width; 1168 } 1169 1170 function resetCollapsedItems($dropdown) { 1171 $dropdown.find('.mega-nav-hidden-item').hide(); 1172 $dropdown.find('.mega-nav-more-link').show(); 1173 } 1174 1175 function watchProperty(obj, key, onSet) { 1176 if (!obj) return false;
1177 1178 var desc = Object.getOwnPropertyDescriptor(obj, key); 1179 if (desc && !desc.configurable) return false; 1180 1181 var value = obj[key]; 1182 Object.defineProperty(obj, key, { 1183 configurable: true, 1184 enumerable: desc ? desc.enumerable : true, 1185 get: function() { 1186 return value; 1187 }, 1188 set: function(next) { 1189 value = next; 1190 onSet(next); 1191 } 1192 }); 1193 1194 if (value !== undefined) onSet(value); 1195 return true; 1196 } 1197 1198 function watchModules(modules) { 1199 if (!modules) return; 1200 1201 if (installMegaNavPatch()) return; 1202 watchProperty(modules, 'NavService', installMegaNavPatch); 1203 } 1204 1205 function watchNavServiceBootstrap() { 1206 if (installMegaNavPatch()) return; 1207 1208 watchProperty(window, 'NGIN', function(ngin) { 1209 if (!ngin) return; 1210 1211 if (installMegaNavPatch()) return; 1212 watchModules(ngin.Modules); 1213 watchProperty(ngin, 'Modules', watchModules); 1214 }); 1215 1216 if (window.NGIN) { 1217 watchModules(window.NGIN.Modules); 1218 watchProperty(window.NGIN, 'Modules', watchModules); 1219 } 1220 1221 $(window).one('load', installMegaNavPatch); 1222 $(document).one('DOMContentLoaded', installMegaNavPatch); 1223 } 1224 1225 watchNavServiceBootstrap(); 1226})(jQuery); 1227;(function($) { 1228 1229 var DEFAULT_DURATION = 300; 1230 1231 NGIN.Modules.NavService.register('SlideNav', { 1232 1233 initialize: function(opts) { 1234 // Allow custom collapsed classes as well as the required one (for css integration) 1235 this.collapsedClass = 'slideNavCollapsed ' + (this.collapsedClass || ''); 1236 this.duration = opts.duration || DEFAULT_DURATION; 1237 this.collapseWidth = opts.collapseWidth || 0; 1238 this.listCount = 0; 1239 this.currentNavData = opts.data; 1240 this.$html = $('html'); 1241 this.$nav = $('<div class="theme-nav theme-nav-style-slide" />'); 1242 this.$toggle = $('<span class="theme-slide-nav-toggle" />').appendTo(this.$nav); 1243 this.$title = $('<div class="theme-nav-title">').appendTo(this.$nav); 1244 this.$menus = $('<div class="theme-nav-menus">').appendTo(this.$nav).scrollGuard(); 1245 this.$overlay = $('<div class="theme-slide-nav-overlay" />').appendTo(this.$nav); 1246 1247 this.appendContent(); 1248 this.bindEvents(); 1249 1250 this.$el.trigger('SlideNavInitialize', { navElement: this.$nav }); 1251 }, 1252 1253 // Injects any pre-rendered content into the nav element in the correct location 1254 appendContent: function() { 1255 var content = this.$el.children().detach(); 1256 if (content.length) $('<div class="theme-slide-nav-content" />') 1257 .append(content) 1258 .insertBefore(this.$title); 1259 }, 1260 1261 bindEvents: function() { 1262 this.$nav.on('hover', '[data-nav-url]', this.preload.bind(this)); 1263 this.$menus.on('click', '[data-nav-url]', this.loadChildNav.bind(this)); 1264 this.$title.on('click', '[data-nav-url]', this.loadParentNav.bind(this)); 1265 this.$toggle.add(this.$overlay).on('click', this.toggleMenu.bind(this)); 1266 1267 // unbind mclose -- Removes old ngin JS menu close handler 1268 $(document).unbind('click', mclose()); 1269 }, 1270 1271 toggleMenu: function(e) { 1272 var open = !this.$html.hasClass('slide-nav-open'); 1273 this.$html.toggleClass('slide-nav-open', open); 1274 this.$el.trigger(open ? 'SlideNavOpen' : 'SlideNavClose', { navElement: this.$nav }); 1275 this.scrollToTop(); 1276 }, 1277 1278 scrollToTop: function() { 1279 // Doing this in an interval to fix an issue where iOS would NOT end up at 1280 // the top when closing the menu. Maybe something to do with CSS animation? 1281 var scrollInterval = setInterval(function(){ 1282 window.scrollTo(0, 0); 1283 }, 20); 1284 setTimeout(function(){ 1285 clearInterval(scrollInterval); 1286 }, 300); 1287 }, 1288 1289 resize: function() { 1290 this.$html.not('.slideNavCollapsed').removeClass('slide-nav-open'); 1291 }, 1292 1293 renderParentContent: function(data) { 1294 var topLevel = data.parent_id === null && data.node_type === null; 1295 1296 if (topLevel) { 1297 this.$title.html( 1298 '<span class="theme-nav-title-text">Main Menu</span>' 1299 ); 1300 } else { 1301 this.$title.html( 1302 '<span class="theme-nav-back" data-nav-url="' + data.nav_url + '"></span>' + 1303 '<a href="' + data.url + '" class="theme-nav-title-text">' + data.name + '</a>' 1304 ); 1305 } 1306 }, 1307 1308 render: function(navData, opts) { 1309 if (!this.$nav.parent(this.$el).length) this.$el.append(this.$nav); 1310 var navDir = 'up'; 1311 1312 // navDir bound as first arg when rendering after navigation 1313 if (typeof navData === 'string') { 1314 navDir = navData; 1315 navData = opts; 1316 } 1317 1318 var renderParent = navDir !== 'down'; 1319 var newMenu = $('<ul class="theme-nav-menu" />'); 1320 var listData = renderParent ? navData.siblings : navData.children; 1321 var parentData = renderParent ? navData.parent : navData; 1322 1323 // Build Lists 1324 for (var i = 0; i < listData.length; i++) { 1325 var navItem = $('<li class="theme-nav-item"/>') 1326 var data = listData[i]; 1327 var linkContent = data.name; 1328 1329 if (data.display_status == "paywalled") linkContent = this.paywallImage + ' ' + linkContent 1330 1331 var $link = $('<a class="theme-nav-link"/>') 1332 .html(linkContent) 1333 .attr({ 1334 href: data.url, 1335 title: data.title, 1336 id: data.id, 1337 target: data.target || '_self' 1338 }); 1339 var forwardArrow = ''; 1340 1341 if (data.display_status) navItem.addClass(data.display_status); 1342 if (data.id === this.currentId) navItem.addClass('selected'); 1343 if (data.has_child) { 1344 navItem.addClass('hasChild'); 1345 forwardArrow = $('<span class="theme-nav-forward ' + data.id + '"></span>'); 1346 if (data.nav_url) forwardArrow.attr('data-nav-url', data.nav_url); 1347 } 1348 1349 navItem 1350 .append($link, forwardArrow) 1351 .appendTo(newMenu) 1352 } 1353 1354 // render/preload parent data if needed (do this after preloading 1355 // children since child pages are more likely to be needed first). 1356 this.renderParentContent(parentData); 1357 1358 // Place List Content 1359 this.$menus.append(newMenu); 1360 1361 // Sets the first menu as current (will animate to next menu if needed) 1362 this.$menus.children().first(); 1363 1364 // Animate if we have rendered more than one list 1365 if (this.listCount++) { 1366 if (renderParent) this.transitionToParent(); 1367 else this.transitionToChildren(); 1368 } 1369 }, 1370 1371 transitionToParent: function() { 1372 var menus = this.$menus.children(); 1373 var newMenu = menus.last().addClass('theme-nav-parent-in'); 1374 var oldMenu = menus.first().addClass('theme-nav-children-out'); 1375 newMenu.insertBefore(oldMenu); 1376 setTimeout(function() { 1377 newMenu.removeClass('theme-nav-parent-in'); 1378 }, 50); 1379 setTimeout(function() { 1380 oldMenu.remove(); 1381 }, this.duration); 1382 }, 1383 1384 transitionToChildren: function() { 1385 var menus = this.$menus.children(); 1386 var newMenu = menus.last().addClass('theme-nav-children-in'); 1387 var oldMenu = menus.first().addClass('theme-nav-parent-out'); 1388 setTimeout(function() { 1389 newMenu.removeClass('theme-nav-children-in'); 1390 }, 50); 1391 setTimeout(function() { 1392 oldMenu.remove(); 1393 }, this.duration); 1394 }, 1395 1396 loadParentNav: function(e) { 1397 this.getNavData($(e.currentTarget).attr('data-nav-url')) 1398 .then(this.render.bind(this, 'up')); 1399 }, 1400 1401 loadChildNav: function(e) { 1402 this.getNavData($(e.currentTarget).attr('data-nav-url')) 1403 .then(this.render.bind(this, 'down')); 1404 } 1405 1406 }); 1407 1408})(jQuery); 1409;(function($){ 1410 1411 // Requires teh $.fn.scrollGuard module. 1412 1413 var COMPACT_CLASS = 'compact-style'; 1414 1415 var NNB = NGIN.NetworkNavBar = function(selector, config) { 1416 this.dropDownStyle = config.displayStyle === 'drop_down_menu' 1417 this.$el = $(selector); 1418 this.el = this.$el[0]; 1419 this.$title = this.$el.find('.theme-network-bar-title'); 1420 this.$sites = this.$el.find('.theme-network-bar-sites').scrollGuard(); 1421 this.$body = $('body'); 1422 this.$doc = $(document); 1423 this.bindEvents(); 1424 } 1425 1426 $.extend(NNB.prototype, { 1427 1428 bindEvents: function() { 1429 $(window).on('resize', this.resize.bind(this)); 1430 setTimeout(this.resize.bind(this), 0); 1431 }, 1432 1433 renderDropDown: function() { 1434 this.$el.addClass(COMPACT_CLASS); 1435 this.$sites.detach(); 1436 this.tip = NGIN.Modules.Tip(this.$sites, { 1437 style: 'theme-network-bar-dropdown', 1438 width: 'target', 1439 target: this.$title, 1440 target_anchor: 'bl', 1441 tip_anchor: 'tl', 1442 show_on: ['.theme-network-bar-title', 'click'], 1443 hide_on: [['.theme-network-bar-title', 'click'], [document, 'click'], [window, 'scroll']], 1444 }); 1445 }, 1446 1447 renderIconList: function() { 1448 this.$sites.detach(); 1449 if (this.tip) this.tip.destroy(); 1450 this.$sites.insertAfter(this.$title).show(); 1451 this.$el.removeClass(COMPACT_CLASS); 1452 }, 1453 1454 resize: function() { 1455 this.renderIconList(); 1456 var dropDown = this.dropDownStyle || this.el.scrollWidth > this.el.offsetWidth; 1457 if (dropDown) this.renderDropDown(); 1458 } 1459 1460 }); 1461 1462})(jQuery); 1463;(function($){ 1464 1465 var EXPANDED_CLASS = 'network-nav-expanded'; 1466 1467 var NNM = NGIN.NetworkNavMenu = function(config) { 1468 this.$title = $(config.title); 1469 this.$sites = $(config.sites); 1470 this.$mobileNav = this.$title.closest('.theme-mobile-nav'); 1471 1472 this.$sites.find('.theme-nav-menus').scrollGuard(); 1473 this.$sites.detach(); 1474 1475 this.bindEvents(); 1476 } 1477 1478 $.extend(NNM.prototype, { 1479 1480 bindEvents: function() { 1481 var self = this; 1482 this.$title.on('click', '.theme-nav-back', this.transitionToNetwork.bind(this)); 1483 this.$sites.on('click', '.theme-nav-forward', this.transitionToSite.bind(this)); 1484 this.$mobileNav.on('SlideNavInitialize', this.moveSites.bind(this)); 1485 this.$mobileNav.on('SlideNavClose', (function() { 1486 var timer; 1487 return function() { 1488 clearTimeout(timer); 1489 timer = setTimeout(function() { self.transitionToSite(); }, 200); 1490 }; 1491 })()); 1492 }, 1493 1494 // do this only after the slide nav inits since this gets initialized before the slide nav module 1495 moveSites: function(event, eventData) { 1496 this.$sites.appendTo(eventData.navElement); 1497 }, 1498 1499 transitionToSite: function() { 1500 this.$mobileNav.removeClass(EXPANDED_CLASS); 1501 }, 1502 1503 transitionToNetwork: function() { 1504 this.$mobileNav.addClass(EXPANDED_CLASS); 1505 } 1506 1507 }); 1508 1509})(jQuery); 1510;(function($) { 1511 1512 var $html = $('html'); 1513 var htmlClass = 'page-manager-visible'; 1514 1515 var PM = NGIN.Modules.PageManager = function(opts) { 1516 this.config = PM.CONFIG || {} 1517 this.opts = opts || {}; 1518 this.$pageManager = $('.sn-pageManager'); 1519 this.$pageManagerToggle = this.$pageManager.find('.sn-togglePages'); 1520 this.$pageManagerContent = $('.sn-structure.sn-sidebarItem'); 1521 this.$tabs = $('.sn-nav.sn-nav-tabs li'); 1522 this.$tabContent = $('.sn-tabContainer'); 1523 this.$pageLinks = $('.sn-pages li a').not('.cmsNavButton a'); 1524 this.$editModeDisabled = $('.sn-edit-mode-disabled'); 1525 this.$editModeDisabledTip = this.$editModeDisabled.find('.sn-tooltip'); 1526 this.bindEvents(); 1527 } 1528 1529 PM.config = function(config) { 1530 PM.CONFIG = config; 1531 } 1532 1533 // Use jQuery UI sortable interaction to handle page manager's fixed position. 1534 // Use the sortable callbacks for backward compatability. 1535 PM.sortable = function(elementId, pageId) { 1536 var $el = $('#' + elementId); 1537 var childSelector = '.drag'; 1538 var url = '/page/order/' + pageId; 1539 var csrfToken = $('meta[name="csrf-token"]').attr('content'); 1540 1541 function updateOrder(event, ui) { 1542 var serializeRule = /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/; 1543 var params = { authenticity_token: csrfToken }; 1544 1545 params[elementId + '[]'] = $el.children(childSelector).map(function() { 1546 return (this.id.match(serializeRule) || [])[1]; 1547 }).get(); 1548 1549 $.ajax({ 1550 url: url, 1551 type: 'POST', 1552 data: params,
1553 dataType: 'script' 1554 }); 1555 } 1556 1557 $el.sortable({ 1558 axis: 'y', 1559 items: childSelector, 1560 update: updateOrder 1561 }); 1562 } 1563 1564 PM.prototype.bindEvents = function() { 1565 this.$pageManagerToggle 1566 .on('click', this.toggle.bind(this)); 1567 this.$editModeDisabled 1568 .on('mouseover', this.showEditModeDisabledTip.bind(this)) 1569 .on('mouseout', this.hideEditModeDisabledTip.bind(this)); 1570 this.$tabs 1571 .on('click', this.showTab.bind(this)); 1572 1573 // prevent click of links in pages & navigation tab 1574 this.$pageLinks.each(function() { 1575 $(this).replaceWith($('<p>' + this.innerHTML + '</p>')); 1576 }); 1577 1578 // decide to show or not based on window size 1579 if (this.config.minWidth) { 1580 this.resize() 1581 $(window).on('resize', this.resize.bind(this)); 1582 } 1583 } 1584 1585 PM.prototype.toggle = function() { 1586 var content = this.$pageManagerContent; 1587 var activate = !content.hasClass('active'); 1588 content.toggleClass('active', activate); 1589 if (activate) this.resetTabs(); 1590 } 1591 1592 PM.prototype.resize = function() { 1593 var visible = $html.hasClass(htmlClass); 1594 var hide = window.innerWidth < this.config.minWidth; 1595 if (hide && visible) $html.removeClass(htmlClass); 1596 else if (!hide && !visible) $html.addClass(htmlClass); 1597 } 1598 1599 PM.prototype.showTab = function(e) { 1600 this.$tabs.removeClass('active'); 1601 this.$tabContent.hide(); 1602 $(e.currentTarget) 1603 .addClass('active') 1604 .find('.sn-tabContainer').show(); 1605 } 1606 1607 PM.prototype.resetTabs = function() { 1608 this.$tabs.removeClass('active').first().addClass('active'); 1609 this.$tabContent.hide().first().show(); 1610 } 1611 1612 PM.prototype.showEditModeDisabledTip = function() { 1613 this.$editModeDisabledTip.stop().fadeIn(); 1614 } 1615 1616 PM.prototype.hideEditModeDisabledTip = function() { 1617 var tip = this.$editModeDisabledTip 1618 tip.stop().fadeOut(200, function() { 1619 tip.css({ opacity : "1" }); 1620 }); 1621 } 1622 1623})(jQuery); 1624;(function($) { 1625 1626 NGIN.Modules.GamesSlider = { 1627 init: function() { 1628 return new GamesSlider(); 1629 } 1630 }; 1631 1632 function GamesSlider() { 1633 this.$slider = $('.games-slider'); 1634 this.$selected = this.$slider.find('.selected'); 1635 this.scrollToSelected(); 1636 } 1637 1638 // Set scrollLeft of slider so that selected game is visible 1639 GamesSlider.prototype.scrollToSelected = function() { 1640 if (this.$selected.length) { 1641 var scrollLeft = this.$slider.scrollLeft(); 1642 1643 var left = Math.round( 1644 this.$selected.offset().left 1645 - this.$slider.offset().left 1646 ); 1647 var leftWidth = left + this.$selected.outerWidth(); 1648 1649 // Selected game is off to left 1650 if (left < 0) { 1651 scrollLeft += left; 1652 } 1653 // Selected game is off to right 1654 else if (leftWidth > this.$slider.outerWidth()) { 1655 scrollLeft += leftWidth - this.$slider.outerWidth(); 1656 } 1657 1658 if (scrollLeft) this.$slider.scrollLeft(scrollLeft); 1659 } 1660 } 1661 1662})(jQuery); 1663;(function($){ 1664 1665 // SHARED VARIABLES 1666 1667 var searchModal; 1668 var searchTerm; 1669 var loadSearchData; 1670 1671 // Kyes that don't change the search value are ignored and include: 1672 // shift, ctrl, alt/opt, cmd, win/cmd, left, up, right, down 1673 var ignoredKeys = [16, 17, 18, 91, 93, 37, 38, 39, 40]; 1674 1675 // Some events run on click or certain key press. This check 1676 // will pass if the event was not a key event, or if the key event 1677 // matches the given key code. 1678 function allowKeys(e, code) { 1679 if (e.type.indexOf('key') === -1) return true; 1680 return e.which === code; 1681 } 1682 1683 function ignoredKeyEvent(e) { 1684 return e && e.type.indexOf('key') !== -1 && ignoredKeys.indexOf(e.which) !== -1; 1685 } 1686 1687 // PAGE SEARCH BOX (one or more per page) 1688 1689 var PSB = NGIN.PageSearchBox = function(selector, config) { 1690 this.$el = $(selector); 1691 this.$input = this.$el.find(config.inputElement); 1692 this.$submit = this.$el.find(config.submitElement); 1693 this.$clear = this.$el.find(config.clearElement).hide(); 1694 this.bindEvents(); 1695 } 1696 1697 $.extend(PSB.prototype, { 1698 1699 bindEvents: function() { 1700 var self = this; 1701 this.$input 1702 .on('focus', this.load.bind(this)) 1703 .on('keyup', this.update.bind(this)) 1704 .on('keydown', this.submit.bind(this)); 1705 this.$submit 1706 .on('click', function(event) { 1707 self.load(); 1708 self.submit(event); 1709 }); 1710 this.$clear 1711 .on('click', this.clear.bind(this)); 1712 }, 1713 1714 val: function(query) { 1715 var setting = !!arguments.length; 1716 if (setting) { 1717 this.$clear.toggle(!!query); 1718 return this.$input.val(query); 1719 } else { 1720 return this.$input.val(); 1721 } 1722 }, 1723 1724 update: function(e) { 1725 if (searchModal && !ignoredKeyEvent(e)) { 1726 var val = this.$input.val(); 1727 var empty = !val.trim(); 1728 this.$clear.toggle(!empty); 1729 searchModal.update(val); 1730 } 1731 }, 1732 1733 submit: function(e) { 1734 if (!searchModal || !allowKeys(e, 13)) return 1735 1736 this.update(); 1737 searchModal.show(); 1738 e.preventDefault(); 1739 }, 1740 1741 clear: function(e) { 1742 e.preventDefault(); 1743 this.$input.val(''); 1744 this.update(); 1745 }, 1746 1747 load: function() { 1748 if (!loadSearchData) { 1749 loadSearchData = $.ajax('/navigation/page_search') 1750 } 1751 } 1752 1753 }); 1754 1755 // PAGE SEARCH MODAL (only one per page) 1756 1757 var PSM = NGIN.PageSearchModal = function(selector, config) { 1758 if (searchModal) return searchModal;
1759 1760 searchModal = this; 1761 1762 this.$el = $(selector).appendTo('body'); 1763 this.$results = this.$el.find(config.resultsElement).scrollGuard(); 1764 this.$status = this.$el.find(config.statusElement); 1765 this.$spinner = this.$el.find(config.spinnerElement); 1766 this.$close = this.$el.find(config.closeElement); 1767 1768 this.bindEvents(config); 1769 1770 this.searchBox = new PSB(this.$el, { 1771 inputElement: config.inputElement, 1772 clearElement: config.clearElement 1773 }); 1774 } 1775 1776 $.extend(PSM.prototype, { 1777 1778 sortBy: 'page_title', 1779 1780 resultTmpl: function(item) { 1781 return '<div class="theme-search-result" data-url="/page/nav_links_redirect/' + item.page_node_id + '">' + 1782 '<div class="theme-search-result-info">' + 1783 '<span class="theme-search-result-primary">' + item.page_title + '</span>' + 1784 '<span class="theme-search-result-secondary">' + item.parent_button_title + '</span>' + 1785 '</div>' + 1786 '</div>'; 1787 }, 1788 1789 bindEvents: function(config) { 1790 this.$el.on('click', this.clickToHide.bind(this)); 1791 this.$close.on('click', this.clickToHide.bind(this)); 1792 this.$el.on('click', '[data-url]', this.navigate.bind(this)); 1793 $(document).on('keydown', this.hide.bind(this)); 1794 }, 1795 1796 show: function() { 1797 this.visible = true; 1798 this.render(); 1799 this.$el.fadeIn(); 1800 this.searchBox.$input.focus(); 1801 }, 1802 1803 clickToHide: function(e) { 1804 if (e.target === e.currentTarget) this.hide(e); 1805 }, 1806 1807 navigate: function(e) { 1808 var url = $(e.currentTarget).data('url'); 1809 if (url) $.get(url); 1810 }, 1811 1812 hide: function(e) { 1813 if (!allowKeys(e, 27)) return; 1814 this.visible = false; 1815 this.$el.fadeOut(); 1816 }, 1817 1818 update: function(query) { 1819 this.query = query; 1820 this.searchBox.val(query); 1821 if (this.visible) this.render(); 1822 }, 1823 1824 filteredResults: function(data) { 1825 var query = this.query && this.query.trim(); 1826 if (!query) return data; 1827 1828 var sortBy = this.sortBy; 1829 var queryRx = new RegExp(query, 'i'); 1830 1831 return data.filter(function(item) { 1832 return queryRx.test(item[sortBy]); 1833 }); 1834 }, 1835 1836 setStatus: function(matches, total) { 1837 var statusText = matches ? 'Showing ' + matches + ' of ' + total : 'No results.'; 1838 this.$status.text(statusText); 1839 }, 1840 1841 render: function() { 1842 var search = this; 1843 if (search.waitingToRender) return; 1844 1845 search.$results.empty().append(search.$spinner); 1846 search.waitingToRender = true; 1847 1848 loadSearchData.then(function(data) { 1849 var results = data.result || []; 1850 var filteredResults = search.filteredResults(results); 1851 var markup = filteredResults.map(search.resultTmpl).join(''); 1852 1853 search.waitingToRender = false;
1854 search.setStatus(filteredResults.length, results.length); 1855 search.$results 1856 .empty() 1857 .append(this.$status) 1858 .append(markup); 1859 }) 1860 } 1861 1862 }); 1863 1864})(jQuery); 1865;(function($){ 1866 1867 var searchModal 1868 1869 // SEARCH BOX 1870 1871 var GSB = NGIN.GoogleSearchBox = function(selector, config) { 1872 this.$el = $(selector); 1873 this.$input = this.$el.find(config.inputElement); 1874 this.$submit = this.$el.find(config.submitElement); 1875 this.$clear = this.$el.find(config.clearElement).hide(); 1876 this.bindEvents(); 1877 } 1878 1879 $.extend(GSB.prototype, { 1880 1881 bindEvents: function() { 1882 this.$input 1883 .on('keydown', this.submitIfEnter.bind(this)) 1884 .on('keyup', this.update.bind(this)); 1885 this.$submit.on('click', this.submit.bind(this)); 1886 this.$clear.on('click', this.clear.bind(this)); 1887 }, 1888 1889 update: function() { 1890 var val = this.$input.val().trim(); 1891 this.$clear.toggle(!!val); 1892 }, 1893 1894 submit: function(e) { 1895 if (!searchModal) return 1896 var api = searchModal.api(); 1897 var query = this.$input.val().trim(); 1898 1899 e.preventDefault(); 1900 e.stopPropagation(); 1901 1902 if (api && query) { 1903 api.execute(query); 1904 this.showModal(); 1905 } 1906 }, 1907 1908 clear: function(e) { 1909 e.preventDefault(); 1910 this.$input.val(''); 1911 }, 1912 1913 submitIfEnter: function(e) { 1914 if (e.which == 13) this.submit(e); 1915 }, 1916 1917 showModal: function() { 1918 if (searchModal) searchModal.show(); 1919 }, 1920 1921 hideModal: function() { 1922 if (searchModal) searchModal.hide(); 1923 } 1924 1925 }); 1926 1927 // SEARCH MODAL 1928 1929 var GSM = NGIN.GoogleSearchModal = function(selector, config) { 1930 if (searchModal) return searchModal; 1931 searchModal = this; 1932 this.$el = $(selector); 1933 this.$close = this.$el.find(config.closeElement); 1934 this.bindEvents(); 1935 } 1936 1937 $.extend(GSM.prototype, { 1938 1939 api: function() { 1940 return google.search.cse.element.getElement('GoogleSearch'); 1941 }, 1942 1943 bindEvents: function() { 1944 this.$close.on('click', this.hide.bind(this)); 1945 this.$el.on('click', this.clickToHide.bind(this)); 1946 }, 1947 1948 show: function() { 1949 this.$el.fadeIn(); 1950 }, 1951 1952 hide: function() { 1953 var api = searchModal.api(); 1954 if (api) api.clearAllResults(); 1955 this.$el.fadeOut(); 1956 }, 1957 1958 1959 clickToHide: function(e) { 1960 if (e.target === e.currentTarget) this.hide(e); 1961 } 1962 1963 }); 1964 1965})(jQuery); 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975
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.