1// Begin Search JS 2 3var result_m = document.getElementById("mobilesearchvalidation"); 4var result_d = document.getElementById("desktopsearchvalidation"); 5var resultsBase = "/resources/search"; 6var searchErrMsg = "⚠ Please enter a search term"; 7 8$("#findmobile").on('input', function () { 9 ClearSearchValidation(result_m); 10}); 11 12$("#finddesktop").on('input', function () { 13 ClearSearchValidation(result_d); 14}); 15 16function ClearSearchValidation(el) { 17 el.classList.remove("search-error"); 18 el.removeAttribute("role"); 19 el.innerHTML = ""; 20} 21 22$("#searchboxformmobile").on('submit', function (event) { 23 event.preventDefault(); 24 var query = $("#findmobile").val(); 25 ClearSearchValidation(result_m); 26 var value = document.getElementById('findmobile').value; 27 if (value === '') { 28 result_m.classList.add("search-error"); 29 result_m.setAttribute("role", "alert"); 30 result_m.innerHTML = searchErrMsg; 31 } else { 32 window.location = resultsBase + "#stq=" + query + "&stp=1"; 33 } 34}); 35 36$("#searchboxformmodal").on('submit', function (event) { 37 event.preventDefault(); 38 var query = $("#finddesktop").val(); 39 ClearSearchValidation(result_d); 40 var value = document.getElementById('finddesktop').value; 41 if (value === '') { 42 result_d.classList.add("search-error"); 43 result_d.setAttribute("role", "alert"); 44 result_d.innerHTML = searchErrMsg; 45 } else { 46 //close the modal window 47 $('#desktopSearchModal').modal('hide'); 48 window.location = resultsBase + "#stq=" + query + "&stp=1"; 49 } 50}); 51 52window.addEventListener('resize', function () { 53 var rw = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; 54 55 if (rw >= 992) { 56 if (result_m !== undefined && result_m !== null && result_m.innerHTML.trim() !== "") { 57 ClearSearchValidation(result_m); 58 } 59 } else { 60 if (result_d !== undefined && result_d !== null && result_d.innerHTML.trim() !== "") { 61 ClearSearchValidation(result_d); 62 } 63 if ($('#desktopSearchModal').is(':visible')) { 64 $('#desktopSearchModal').modal('hide'); 65 } 66 } 67}); 68 69var searchModal = document.getElementById('desktopSearchModal'); 70 71if (searchModal != null) { 72 searchModal.addEventListener('hidden.bs.modal', function () { 73 ClearSearchValidation(result_d); 74 document.getElementById('finddesktop').value = ""; 75 }); 76} 77 78$('#desktopSearchModal').on('shown.bs.modal', function () { 79 //set focus on input in modal 80 $('#finddesktop').trigger("focus"); 81 82}) 83 84$(document).on('mouseenter', 'li.result.seeall', function () { 85 $('li.result').removeClass('active'); 86 $(this).addClass('active'); 87}); 88 89$(document).on('mouseleave', 'li.result.seeall', function () { 90 $(this).removeClass('active'); 91}); 92 93$(document).on('click', 'li.result.seeall', function () { 94 let link = $("li.result.seeall > p > a").attr("href"); 95 if (link) { 96 window.location.href = link; 97 } 98}); 99// End Search JS 100 101// Begin Modal JS 102//Prevent Keyboard Users from Focusing on Elements Outside of the Modal Dialogs While Open - .first-focusable-element/.first-focusable-element-graph and .last-focusable-element/.last-focusable-element-graph have been added to the respective elements inside the modal dialogs. 103$('.modal:not([role])').attr('role', 'dialog'); 104 105const modals = document.querySelectorAll('[role="dialog"]'); 106modals.forEach(modal => { 107 let firstFocusableElement = modal.querySelector('[class*="first-focusable-element"]'); 108 let lastFocusableElement = modal.querySelector('[class*="last-focusable-element"]'); 109 110 if (firstFocusableElement == null) { 111 firstFocusableElement = getFirstFocusableElement(modal); 112 } 113 114 if (lastFocusableElement == null) { 115 lastFocusableElement = getLastFocusableElement(modal); 116 117 if (lastFocusableElement == null && firstFocusableElement != null) { 118 lastFocusableElement = firstFocusableElement; 119 } 120 } 121 122 modal.addEventListener('keydown', function (e) { 123 if (e.target == firstFocusableElement && e.key == 'Tab' && e.shiftKey) { 124 e.preventDefault(); 125 lastFocusableElement.focus(); 126 } else if (e.target == lastFocusableElement && e.key == 'Tab' && !e.shiftKey) { 127 e.preventDefault(); 128 firstFocusableElement.focus(); 129 } 130 });
131}); 132 133function getFirstFocusableElement(container = document) { 134 const focusableElements = container.querySelectorAll( 135 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' 136 ); 137 138 if (focusableElements.length > 0) { 139 return focusableElements[0]; 140 } else { 141 return null; // No focusable elements found 142 } 143} 144 145function getLastFocusableElement(container = document) { 146 const focusableElements = container.querySelectorAll( 147 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])' 148 ); 149 150 if (focusableElements.length > 0) { 151 return focusableElements[focusableElements.length - 1]; 152 } else { 153 return null; // No focusable elements found 154 } 155} 156// End Modal JS 157 158 159// Begin Back To Top Button 160//Get the button 161let mybutton = document.getElementById("btn-back-to-top"); 162 163// When the user scrolls down 20px from the top of the document, show the button 164window.onscroll = function () { 165 scrollFunction(); 166}; 167 168function scrollFunction() { 169 if ( 170 document.body.scrollTop > 20 || 171 document.documentElement.scrollTop > 20 172 ) { 173 mybutton.style.display = "block"; 174 } else { 175 mybutton.style.display = "none"; 176 } 177} 178// When the user clicks on the button, scroll to the top of the document 179mybutton.addEventListener("click", backToTop); 180 181function backToTop() { 182 var bw = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; 183 var skip = $("#" + (bw >= 992 ? "skipbutton" : "main-logo")); 184 $(skip)[0].scrollIntoView(); 185 skip.trigger("focus"); 186} 187// End Back To Top Button 188 189 190// Begin Email Signup 191var frm = $('#subscribeForm'); 192var frm_input = document.getElementById('emailsignup'); 193var result_p = document.getElementById('emailsignup-result'); 194var icon_span = document.getElementById('email-result-icon'); 195 196frm.on('submit', function (e) { 197 //reinitialize variables if needed 198 if (frm.attr('id').indexOf('campaign') > -1) { 199 frm = $('#subscribeForm'); 200 frm_input = document.getElementById('emailsignup'); 201 result_p = document.getElementById('emailsignup-result'); 202 icon_span = document.getElementById('email-result-icon'); 203 } 204 205 e.preventDefault(); 206 clearEmailMessage(); 207 validateForm(); 208}); 209 210function clearEmailMessage() { 211 result_p.classList.remove('error'); 212 result_p.classList.remove('success'); 213 result_p.removeAttribute("role"); 214 result_p.innerHTML = ""; 215 icon_span.classList.remove('email-error-icon'); 216 icon_span.classList.remove('email-success-icon'); 217 icon_span.innerHTML = ""; 218 $("#" + frm_input.id).attr('aria-invalid', 'false').removeAttr('aria-describedby'); 219} 220 221function validateForm() { 222 var value = frm_input.value; 223 if (value === '') { 224 setEmailError('Please enter your email in the format:<br/>[email protected]'); 225 return false; 226 } 227 228 if (!validateEmail(value)) { 229 setEmailError('Please enter your email in the format:<br/>[email protected]'); 230 return false; 231 } 232 SubmitForm(value); 233} 234 235function setEmailError(msg) { 236 result_p.classList.remove('success'); 237 result_p.classList.add('error'); 238 result_p.setAttribute("role", "alert"); 239 result_p.innerHTML = DOMPurify.sanitize(msg); 240 241 icon_span.classList.remove('email-success-icon'); 242 icon_span.classList.add('email-error-icon'); 243 icon_span.innerHTML = "⚠"; 244 245 $("#" + frm_input.id).attr('aria-invalid', 'true').attr('aria-describedby', result_p.id); 246 $("#" + frm_input.id).trigger("focus"); 247} 248 249function setEmailSuccess(msg) { 250 result_p.classList.add('success'); 251 result_p.classList.remove('error'); 252 result_p.setAttribute("role", "alert"); 253 result_p.innerHTML = DOMPurify.sanitize(msg); 254 255 icon_span.classList.add('email-success-icon'); 256 icon_span.classList.remove('email-error-icon'); 257 icon_span.innerHTML = "✓"; 258 259 $("#" + frm_input.id).attr('aria-invalid', 'false').attr('aria-describedby', result_p.id); 260} 261 262function validateEmail(emailAddress) { 263 var filter = /^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/; 264 return String(emailAddress) 265 .toLowerCase() 266 .match(filter); 267} 268
269function SubmitForm() { 270 disableSubscribeBtn(); 271 var formData = frm.serialize(); 272 result_p.innerHTML = ""; 273 274 var url = "/api/subscribe/Submit"; 275 if (frm.attr('id').indexOf('campaign') > -1) { 276 url = "/api/subscribe/SubmitCampaign"; 277 } 278 279 $.ajax({ 280 url: url, 281 type: "POST", 282 contentType: "application/x-www-form-urlencoded; charset=UTF-8", 283 data: formData, 284 success: function (result) { 285 if (result.valid) { 286 frm_input.value = ""; 287 setEmailSuccess(result.message); 288 } else { 289 frm_input.focus(); 290 setEmailError(result.errors[0].message); 291 } 292 enableSubscribeBtn(); 293 }, 294 error: function (xhr) { 295 frm_input.focus(); 296 setEmailError('Email signup failed'); 297 enableSubscribeBtn(); 298 } 299 }); 300} 301 302function disableSubscribeBtn() { 303 var button = frm.find('button'); 304 button.prop("disabled", true); 305} 306 307function enableSubscribeBtn() { 308 var button = frm.find('button'); 309 button.prop("disabled", false); 310} 311// End Email Signup 312 313 314//Mobile NavBar Toggler Listeners 315 316$('#navbarSupportedContent').on('show.bs.collapse', function () { 317 // Actions to perform when the navbar is about to show (expand) 318 MobileNavLinkNameToggle('Menu'); 319}); 320 321$('#navbarSupportedContent').on('hide.bs.collapse', function () { 322 // Actions to perform when the navbar is about to hide (collapse) 323 MobileNavLinkNameToggle('Close Menu'); 324}); 325 326function MobileNavLinkNameToggle(newState) { 327 const $el = $('.navbar-toggler'); 328 if (!$el.length || !newState) return; 329 330 $el.attr('data-analytics-linkname', newState); 331} 332 333//End Mobile NavBar Toggler Functions 334 335 336//Accordion Functions 337var doAccordionScroll = true; 338 339//When Accordion Item opens 340$(document).on('shown.bs.collapse', '.collapse', function () { 341 const $collapse = $(this); 342 343 // Cache key elements 344 const $card = $collapse.closest('.accordion-item'); 345 if (!$card.length) return; 346 347 const $button = $card.find('button').first(); 348 if ($button.length) { 349 AccordionLinkNameToggle($button, '-Collapse'); 350 } 351 352 // Find other open panels within same parent 353 const parentSelector = $collapse.data('parent'); 354 const $open = parentSelector ? $(parentSelector).find('.collapse.show') : $(); 355 356 let additionalOffset = 0; 357 358 if ($open.length) { 359 const $openCard = $open.closest('.accordion-item'); 360 if ($card.prevAll().filter($openCard).length) { 361 additionalOffset = $open.outerHeight() || 0; 362 } 363 } 364 365 const id = getParameterByName("id"); 366 const windowWidth = window.innerWidth; 367 const hash = DOMPurify.sanitize(window.location.hash); 368 369 // Handle ID-based adjustment 370 if (id && this.id === id) { 371 if (windowWidth >= 992) { 372 const isSpecial = id.includes("popular") || id.includes("common"); 373 additionalOffset = isSpecial ? 100 : 185; 374 } else { 375 additionalOffset = 0; 376 } 377 378 const uri = window.location.href; 379 window.history.replaceState({}, document.title, removeURLParameter(uri, "id")); 380 } 381 382 if (!doAccordionScroll) return; 383 384 // Scroll to accordion 385 const cardOffset = $card.offset(); 386 if (cardOffset) { 387 $('html, body').animate({ 388 scrollTop: cardOffset.top - additionalOffset 389 }, 100); 390 } 391 392 // Optional: scroll to inner anchor 393 if (id && hash && $(hash).length) { 394 const isSpecial = id.includes("popular") || id.includes("common"); 395 const offset = windowWidth >= 992 396 ? (isSpecial ? 110 : 275) 397 : 75; 398 399 const targetOffset = $(hash).offset(); 400 if (targetOffset) { 401 $('html, body').animate({ 402 scrollTop: targetOffset.top - offset 403 }, 100); 404 } 405 } 406 407 doAccordionScroll = true; 408}); 409 410//When Accordion Item closes 411$(document).on('hidden.bs.collapse', '.collapse', function () { 412 const $collapse = $(this); 413 414 // Find the nearest accordion item once 415 const $card = $collapse.closest('.accordion-item'); 416 if (!$card.length) return; 417 418 // Limit search to direct toggle button(s) if possible 419 const $button = $card.find('button').first(); 420 if (!$button.length) return; 421 422 AccordionLinkNameToggle($button, '-Expand'); 423}); 424 425function AccordionLinkNameToggle($element, newState) { 426 const attr = $element.attr('data-analytics-linkname'); 427 if (!attr) return; // handles undefined, null, empty string 428 429 // Replace either -Expand or -Collapse with the new state 430 const updated = attr.replace(/-(Expand|Collapse)$/, newState); 431 432 // Only write if something actually changed 433 if (updated !== attr) { 434 $element.attr('data-analytics-linkname', updated); 435 } 436} 437 438// End Accordion Functions 439 440 441// Begin URL querystring parameter functions 442function getParameterByName(name, url = window.location.href) { 443 name = name.replace(/[\[\]]/g, '\\$&'); 444 var regex = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'), 445 results = regex.exec(url); 446 if (!results) return null; 447 if (!results[2]) return ''; 448 return decodeURIComponent(results[2].replace(/\+/g, ' ')); 449} 450 451function removeURLParameter(url, parameter) { 452 //prefer to use l.search if you have a location/link object 453 var urlparts = url.split('?'); 454 if (urlparts.length >= 2) { 455 456 var prefix = encodeURIComponent(parameter) + '='; 457 var pars = urlparts[1].split(/[&;]/g); 458 459 //reverse iteration as may be destructive 460 for (var i = pars.length; i-- > 0;) { 461 //idiom for string.startsWith 462 if (pars[i].lastIndexOf(prefix, 0) !== -1) { 463 pars.splice(i, 1); 464 } 465 } 466 467 return urlparts[0] + (pars.length > 0 ? '?' + pars.join('&') : ''); 468 } 469 return url; 470} 471// End URL querystring parameter functions 472 473 474//Allow for multiple references on page to same footnote 475//example footnote reference: <sup><a id="footnotes-ref1-1" href="#footnotes_1" aria-label="footnote 1">1</a></sup> 476//example footnote w/ back link: <li id="footnotes_1"><sup>1</sup>Footnote Content<a id="footnotes_1_back" href="#footnotes-ref1-1" class="back-to-content" aria-label="Back to content">â©</a></li> 477 478//in body footnote reference 479$(document).on('click', "a[href^='#footnotes_']", function () { 480 const $this = $(this); 481 482 // Remove existing highlight only if present 483 const $current = $(".footnoteTarget"); 484 if ($current.length) { 485 $current.removeClass("footnoteTarget"); 486 } 487 488 // Use hash and strip the # 489 const targetId = this.hash.slice(1); 490 491 const $footnote = $("#" + targetId); 492 if (!$footnote.length) return; // guard clause 493 494 // Set backlink 495 $("#" + targetId + "_back").attr("href", "#" + this.id); 496 497 // Add highlight + tabindex in one pass 498 $footnote 499 .addClass("footnoteTarget") 500 .attr("tabindex", "-1"); 501}); 502 503//back to content 504$(document).on('click', "a[href^='#footnotes-ref']", function () { 505 // Remove existing highlight only if present 506 const $current = $(".footnoteTarget"); 507 if ($current.length) { 508 $current.removeClass("footnoteTarget"); 509 } 510}); 511 512//End Allow for multiple references on page to same footnote 513 514function syncNavDropdownAccessibility(dropdownMenu, isOpen) { 515 if (!dropdownMenu) { 516 return; 517 } 518 519 var $menu = $(dropdownMenu); 520 var $links = $menu.find('a.primary-sub-nav-links.dropdown-item'); 521 522 $links.each(function () { 523 $(this).attr('tabindex', isOpen ? '0' : '-1'); 524 });
525 526 if (isOpen) { 527 $menu.removeAttr('aria-hidden'); 528 $menu.removeAttr('inert'); 529 } else { 530 $menu.attr('aria-hidden', 'true'); 531 $menu.attr('inert', ''); 532 } 533} 534 535$(function () { 536 //remove empty aria-label attributes from a tags, buttons and images 537 $('a,button,img').each(function () { 538 const $el = $(this); 539 const label = $el.attr('aria-label'); 540 541 if (label === '') { 542 $el.removeAttr('aria-label'); 543 } 544 }); 545 546 // Keep hidden dropdown links out of the interactive target set. 547 $('.dropdown-menu').each(function () { 548 syncNavDropdownAccessibility(this, false); 549 }); 550 551 $('[data-bs-toggle="dropdown"]').each(function () { 552 $(this).on('shown.bs.dropdown', function () { 553 var menu = $(this).parent().find('.dropdown-menu').first(); 554 syncNavDropdownAccessibility(menu, true); 555 }); 556 557 $(this).on('hidden.bs.dropdown', function () { 558 var menu = $(this).parent().find('.dropdown-menu').first(); 559 syncNavDropdownAccessibility(menu, false); 560 }); 561 }); 562 563 //open all accordions based on querystring 564 const openall = getParameterByName("openall"); 565 const $collapses = $('.collapse'); 566 if (openall && $collapses.length) { 567 $collapses 568 .addClass('show') 569 .each(function () { 570 const $button = $(this).closest('.accordion-item').find('button').first(); 571 if ($button.length) { 572 $button.attr('aria-expanded', 'true'); 573 AccordionLinkNameToggle($button, '-Collapse'); 574 } 575 }); 576 } 577 578 //add role to element 579 const $roles = $('.addRole'); 580 if ($roles.length) { 581 // Set role and remove the data attribute in one pass 582 $roles.attr('role', function () { 583 return this.getAttribute('data-role'); 584 }).removeAttr('data-role'); 585 586 // Remove the marker class in bulk 587 $roles.removeClass('addRole'); 588 589 // Only loop for the rare cleanup case 590 $roles.each(function () { 591 if (!this.className) { 592 this.removeAttribute('class'); 593 } 594 }); 595 } 596 597 //add presentation role to UL element if only one line item present 598 const $ultargets = $('main ul:not([role])').filter(function () { 599 return !this.hasAttribute('role') && 600 this.querySelectorAll('li').length === 1; 601 }); 602 603 if ($ultargets.length) { 604 $ultargets.attr('role', 'presentation'); 605 } 606 607 //only query the DOM once for these classes 608 var $targets = $('.hideUntil, .showUntil, .showBetween'); 609 610 //exit early if none of the classes exist on the page 611 if ($targets.length === 0) { 612 return; 613 } 614 615 getCurrentDate().done(function (data) { 616 //standardize the API string to ISO format and parse it 617 var isoDateString = data.replace(" ", "T").replace("_", ":") + getTZOffsetbyDate(data); 618 var dateNowTS = Date.parse(isoDateString) / 1000; 619 620 //helper function to convert data attributes to Unix timestamps 621 function getAttrTimestamp($el, attrName) { 622 var val = $el.attr(attrName); 623 if (!val) return null; 624 return Date.parse(val.replace(" ", "T").replace("_", ":")) / 1000; 625 } 626 627 //loop through the pre-selected elements exactly once 628 $targets.each(function () { 629 var $this = $(this); 630 631 //date-based content (elements should have data attributes with date in format: yyyy-MM-ddTHH:mm:ss-0#:00) - hides content until date, should be date with appropriate time zone offset (ex.-08:00 for PST/-07:00 for PDT) 632 if ($this.hasClass('hideUntil')) { 633 var checkTS = getAttrTimestamp($this, 'data-date'); 634 if (dateNowTS >= checkTS) { 635 $this.removeClass("hide"); 636 } else { 637 $this.remove(); 638 } 639 } 640 641 else if ($this.hasClass('showUntil')) { 642 var checkTS = getAttrTimestamp($this, 'data-date'); 643 if (dateNowTS > checkTS) { 644 $this.remove(); 645 } 646 } 647 648 else if ($this.hasClass('showBetween')) { 649 var checkStartTS = getAttrTimestamp($this, 'data-start-date'); 650 var checkEndTS = getAttrTimestamp($this, 'data-end-date'); 651 if (dateNowTS >= checkStartTS && dateNowTS <= checkEndTS) { 652 $this.removeClass("hide"); 653 } else { 654 $this.remove(); 655 } 656 } 657 }); 658 });
659}); 660 661function isProd() { 662 var host = window.location.hostname; 663 if (host.indexOf("localhost") !== -1 || host.indexOf("dev.") !== -1 || host.indexOf("qa.") !== -1 || host.indexOf("azurewebsites.net") !== -1) { 664 return false; 665 } 666 667 return true; 668} 669 670function getCurrentDate() { 671 return $.ajax({ 672 type: "GET", 673 url: "/api/internal/GetDateNow", 674 cache: false, 675 data: { today_dt: getParameterByName("today_dt"), env: getParameterByName("env") } 676 }); 677} 678 679function getTZOffset() { 680 const date = new Date(); 681 return new Intl.DateTimeFormat('en', { timeZone: 'America/Los_Angeles', timeZoneName: 'longOffset' }).format(date).match(/[\d+:-]+$/)?.[0]; 682} 683 684function getTZOffsetbyDate(dt) { 685 const date = new Date(dt); 686 return new Intl.DateTimeFormat('en', { timeZone: 'America/Los_Angeles', timeZoneName: 'longOffset' }).format(date).match(/[\d+:-]+$/)?.[0]; 687}
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.