1// Wrap all custom JS in an anonymous function that passes the alias "$" to everything so that "jQuery" doesn't have to be used 2(function ($) { 3 // Global variables 4 var megaMenuTarget = $(".menu-item-360"); // Where the mega menu is placed - remove if no mega menu 5 var enableLazyImageFade = $("body").hasClass("lazy-images-fade-in"); 6 7 // Begin JavaScript/jQuery setup 8 // The "on ready" 9 $(function () { 10 checkScroll(); 11 setupYouTube(); 12 setHeaderHeight(); 13 14 // Prepare links within the body content with the scroll-to class if they point to internal ID anchors 15 $('.content a[href^="#"]').each(function () { 16 // Add class 'scroll-to' to the link 17 $(this).addClass("scroll-to"); 18 19 // Extract the anchor target and set it as a data attribute, minus the hashtag 20 var anchorTarget = $(this).attr("href").substring(1); 21 $(this).attr("data-scroll-to-id", anchorTarget); 22 }); 23 24 // Reposition Gravity Forms labels so the HTML structure works with label CSS 25 $(".gfield").each(function () { 26 var label = $(this).find("label.gfield_label"); 27 var formControl = $(this).find(".ginput_container").children().first(); 28 label.detach().insertAfter(formControl); 29 }); 30 31 // Video gallery category filter 32 if ($("#video-category-filter").exists()) { 33 $("#video-category-filter").on("change", function () { 34 var selectedCategory = $(this).val(); 35 36 $(".videos-single").each(function () { 37 var categories = $(this).data("category").split(" "); 38 if (selectedCategory == "" || categories.includes(selectedCategory)) { 39 $(this).show(); 40 } else { 41 $(this).hide(); 42 } 43 }); 44 45 animateScroll("videos-container"); 46 }); 47 } 48 49 // Prevent empty searches on forms with the "prevent-blank-search" class 50 document.querySelectorAll(".prevent-blank-search").forEach(function (element) { 51 element.addEventListener( 52 "submit", 53 function (e) { 54 var value = $(this).find("input").val().trim(); 55 56 // Test for whitespace values 57 if (!value || !/\S/.test(value)) { 58 alert("Please enter a search term."); 59 60 e.preventDefault(); 61 } 62 }, 63 { passive: false }, 64 ); 65 }); 66 67 // Initialize mega menu (if needed) 68 if (megaMenuTarget && isDesktopScreen()) { 69 $(megaMenuTarget).addClass("static"); 70 $("#mega-menu").detach().appendTo(megaMenuTarget); 71 } 72 73 // Scroll to ID from a select dropdown. Useful on mobile 74 $(".scroll-to-select").on("change", function (e) { 75 // Get selected option then scroll to it 76 var selectedOption = $(this).find(":selected").val(); 77 animateScroll($(selectedOption).offset().top); 78 }); 79 80 // Copies content from a selected area to a target; used only on some modules 81 $(".content-selector").on("click", function () { 82 $(this).toggleClass("active"); 83 $(this).siblings().removeActive(); 84 85 $("#content-selector-target").removeClass("animated fast fadeIn"); 86 $("#content-selector-target").html($(this).html()); 87 88 setTimeout(function () { 89 $("#content-selector-target").addClass("animated fast fadeIn"); 90 }, 200); 91 }); 92 93 // Accessibility: Ensures main navigation dropdowns remain open for keyboard focus 94 $(".main-navigation-menu li, .mega-menu li") 95 .on("focusin mouseover", function () { 96 $(this).addActive(); 97 $(this).siblings().removeActive(); 98 }) 99 .on("mouseleave", function () { 100 $(this).removeActive(); 101 }); 102 103 // Blog Grid - Masonry and move pagination 104 if ($("#blog-grid").exists()) { 105 if (isDesktopScreen()) { 106 // Initializes Masonry layout; see: https://masonry.desandro.com/options.html 107 $("#blog-grid-inner").masonry({ 108 fitWidth: true, 109 gutter: 30, // Horizontal space between layout items 110 itemSelector: ".post", // Target specific class to avoid other elements being repositioned 111 }); 112 } 113 114 // Move pagination to final position, outside of grid 115 $(".blog-grid .wp-pagenavi").detach().appendTo("#blog-grid-pagination"); 116 } 117 118 // LAZY LOADING WITH LOZAD 119 // Use class "lazy-img" and attribute "data-img-src" to lazy load an <img> and "data-animation-class" to animate 120 // To randomly pick an image from a set, use attribute "data-img-srcs" like so: data-img-srcs="imageurl.
120jpg, imageurl.jpg" 121 var imageLazyLoader = lozad(".lazy-img", { 122 rootMargin: "10px 0px", 123 threshold: 0.1, 124 load: function (elementInView) { 125 if ($(elementInView).attr("data-img-srcs")) { 126 var imagesList = $(elementInView).attr("data-img-srcs").split(","); 127 var imageToLoad = imagesList[Math.floor(Math.random() * imagesList.length)]; 128 } else { 129 var imageToLoad = $(elementInView).attr("data-img-src"); 130 } 131 132 // Trigger fade-in after image loads 133 if (enableLazyImageFade) { 134 $(elementInView).one("load", function () { 135 $(this).addClass("lazy-fade-in-loaded"); 136 }); 137 138 $(elementInView).attr("src", imageToLoad); 139 140 // Fallback for cached images 141 if (elementInView.complete && elementInView.naturalHeight !== 0) { 142 $(elementInView).addClass("lazy-fade-in-loaded"); 143 } 144 } else { 145 $(elementInView).attr("src", imageToLoad); 146 } 147 148 // Optionally, add animation classes at the same time 149 if ($(elementInView).attr("data-animation-class")) { 150 var animateClass = "animated " + $(elementInView).attr("data-animation-class"); 151 $(elementInView).addClass(animateClass); 152 } 153 }, 154 }); 155 imageLazyLoader.observe(); 156 157 // Use class "lazy-bg" and attribute "data-bg-src" on any element using a CSS background image lazy load 158 // To randomly pick background image from a set, use attribute "data-bg-srcs" like so: data-bg-srcs="imageurl.jpg, imageurl.jpg" 159 var backgroundImageLazyLoader = lozad(".lazy-bg", { 160 rootMargin: "10px 0px", 161 threshold: 0.1, 162 load: function (elementInView) { 163 if ($(elementInView).attr("data-bg-srcs")) { 164 var imagesList = $(elementInView).attr("data-bg-srcs").split(","); 165 var imageToLoad = imagesList[Math.floor(Math.random() * imagesList.length)]; 166 console.log("Lazy loading a randomly-selected background image: " + imageToLoad); 167 } else { 168 var imageToLoad = $(elementInView).attr("data-bg-src"); 169 console.log("Lazy loading a background image: " + imageToLoad); 170 } 171 172 $(elementInView).css("background-image", "url(" + imageToLoad + ")"); 173 }, 174 }); 175 backgroundImageLazyLoader.observe(); 176 177 // Add an active class to an element when it enters view 178 // Optionally, add a "data-delay" attribute equal to milliseconds, e.g. 500 for 0.5 seconds 179 var activeInViewTrigger = lozad(".active-in-view", { 180 rootMargin: "10px 0px", 181 threshold: 0.1, 182 load: function (elementInView) { 183 var delayActive = 0; 184 185 if ($(elementInView).attr("data-delay")) { 186 delayActive = $(elementInView).attr("data-delay"); 187 } 188 189 setTimeout(function () { 190 $(elementInView).addActive(); 191 $(elementInView).removeClass("active-in-view"); 192 }, delayActive); 193 }, 194 }); 195 activeInViewTrigger.observe(); 196 197 // Trigger animate in view with a specific animation class added 198 // TO USE: Add class "animate-in-view" to an element plus a data attribute "data-animation-class" equal to an animation class name 199 var specificAnimateInViewTrigger = lozad(".animate-in-view", { 200 rootMargin: "10px 0px", 201 threshold: 0.1, 202 load: function (elementInView) { 203 var animToUse = $(elementInView).attr("data-animation-class"); 204 $(elementInView).addClass("animated " + animToUse); 205 }, 206 }); 207 specificAnimateInViewTrigger.observe(); 208 209 // Lazy load Google Map 210 // Requires data attribute "data-embed-map-link" to be set 211 var mapLoadInViewTrigger = lozad(".load-map-in-view", { 212 rootMargin: "10px 0px", 213 threshold: 0.1, 214 load: function (elementInView) { 215 var embedMapWidth = $(elementInView).attr("data-embed-map-width") 216 ? $(elementInView).attr("data-embed-map-width") 217 : 600; 218 var embedMapHeight = $(elementInView).attr("data-embed-map-height") 219 ? $(elementInView).attr("data-embed-map-height") 220 : 450; 221 222 // Add live <iframe> containing Google Map once the element is in view 223 $(elementInView).append( 224 '<iframe src="' + 225 $(elementInView).attr("data-embed-map-link") + 226 '" width="' + 227 embedMapWidth + 228 '" height="' + 229 embedMapHeight + 230 '" frameborder="0" style="border:0;" allowfullscreen="" aria-hidden="false" tabindex="0"></iframe>', 231 ); 232 }, 233 }); 234 mapLoadInViewTrigger.observe(); 235 236 // On page print, download images 237 window.onbeforeprint = function () { 238 $(".lazy-img").each(function () { 239 var src = $(this).attr("data-img-src"); 240 $(this).attr("src", src); 241 }); 242 243 $(".lazy-bg").each(function () { 244 var src = $(this).attr("data-bg-src");
245 $(this).css("background-image", src); 246 }); 247 }; 248 249 // END LAZY LOADING WITH LOZAD 250 }); 251 252 // Window *Load = Entire page including the DOM is loaded 253 $(window).on("load", function () { 254 prepareLinks(); 255 256 // On page load, check inputs for text to add active 257 $("input, textarea").each(function () { 258 activateInputLabel($(this)); 259 }); 260 261 // INPUT ACTIVE CLASS CONTROL 262 // On text inputs, add class when the field holds a value 263 $("input, textarea").on("input change", function () { 264 activateInputLabel($(this)); 265 }); 266 267 // Detects text inside inputs to activate or deactivate label styling 268 function activateInputLabel(thisInput) { 269 // Add validation to ensure we have a valid jQuery object 270 if (!thisInput || !thisInput.length) { 271 return; 272 } 273 274 var thisInputValue = thisInput.val(); 275 276 if (thisInputValue && thisInput.isActive() == false) { 277 thisInput.addActive(); 278 } else if (thisInputValue == "") { 279 thisInput.removeActive(); 280 } 281 } 282 283 // On dropdowns, add class when <select> is used 284 $("select").on("change", function () { 285 $(this).addActive(); 286 }); 287 288 $("select").on("focusin focusout", function () { 289 $(this).closest(".select-wrapper").toggleClass("active"); 290 }); 291 // END INPUT ACTIVE CLASS CONTROL 292 293 // ATTORNEY SEARCH HELPERS 294 // Prevent empty-value form inputs from sending data 295 $("#attorney-search-form").submit(function () { 296 $('input[value=""]:not(.active)').attr("disabled", true); 297 $('option[value=""]').attr("disabled", true); 298 }); 299 300 // If attorney search results are present, scroll to the section 301 if ($("#attorney-search-bar").exists()) { 302 animateScroll("attorney-search-bar", 300); 303 $("#attorney-search-inner").attr("tabindex", "0"); 304 $("#attorney-search-inner").focus(); 305 } 306 // END ATTORNEY SEARCH HELPERS 307 308 // TABLE OF CONTENTS 309 // Scan target for h2s and populate widget with scroll links 310 $(".toc-widget").each(function () { 311 var $widget = $(this); 312 var $list = $widget.find(".toc-list"); 313 var $select = $widget.find(".toc-select"); 314 var target = $widget.data("toc-target") || ".content"; 315 var headings = $(target).find("h2").not(".toc-widget h2"); 316 317 if (headings.length < 2) { 318 var $sidebarTOC = $widget.closest("#sidebar-toc"); 319 if ($sidebarTOC.length) { 320 $sidebarTOC.remove(); 321 } else { 322 $widget.remove(); 323 } 324 return; 325 } 326 327 headings.each(function () { 328 var thisHeadingText = $(this).text().trim(); 329 330 if (thisHeadingText === "") { 331 return; 332 } 333 334 var thisHeadingID = 335 "jump-" + 336 thisHeadingText 337 .toLowerCase() 338 .replace(/ /g, "-") 339 .replace(/[^\w-]+/g, ""); 340 341 $(this).attr("id", thisHeadingID); 342 343 var titleCaseText = thisHeadingText.replace(/\w+/g, function (word) { 344 if (word === word.toUpperCase() && word.length > 1) return word; 345 return word.charAt(0).toUpperCase() + word.substr(1).toLowerCase(); 346 }); 347 348 $list.append( 349 '<li><button class="scroll-to no-button" data-scroll-to-id="' + 350 thisHeadingID + 351 '">' + 352 titleCaseText + 353 "</button></li>", 354 ); 355 $select.append('<option value="#' + thisHeadingID + '">' + titleCaseText + "</option>"); 356 }); 357 }); 358 // END TABLE OF CONTENTS 359 360 // CONTENT TAB BUTTONS 361 // Used on attorney bios 362 $(".content-tab-activate").on("click", function () { 363 $(".content-tab-activate, .content-tab").removeActive(); 364 $("[data-content-tab='" + $(this).attr("data-content-tab") + "']").addActive(); 365 $(this).addActive(); 366 }); 367 368 // Check URL parameters for tab activation on page load 369 var tabParam = getURLParameter("tab"); 370 371 if (tabParam) { 372 // Check if a tab with this parameter exists 373 if ($("[data-content-tab='" + tabParam + "']").length) { 374 // Remove active class from all tabs and content 375 $(".content-tab-activate, .content-tab").removeActive(); 376 // Activate the requested tab and its content 377 $("[data-content-tab='" + tabParam + "']").addActive(); 378 379 // Also update the mobile select dropdown if it exists 380 if ($("#select-content-skip").length) { 381 $("#select-content-skip").val(tabParam); 382 } 383 } 384 } 385 386 $(".mobile-tab").on("click", function () { 387 animateScroll("main"); 388 }); 389 // END CONTENT TAB BUTTONS 390 391 // ACCORDION CONTROL
392 // Toggle accordion open and closed 393 if ($(".accordion-item").length) { 394 // If there are accordions on this page, set their ARIA properties 395 $(".accordion-item-title").attr("aria-expanded", "false"); 396 397 // Event listener for accordion items 398 $(".accordion-item-title").on("click", function () { 399 var parentAccordionItem = $(this).closest(".accordion-item"); 400 401 // Check data attribute values on the clicked accordion 402 var closeSiblings = $(this).data("close-siblings") !== false; 403 404 // Close sibling accordions, if the data attribute allows it 405 if (closeSiblings) { 406 parentAccordionItem.siblings().removeClass("active"); 407 parentAccordionItem 408 .siblings() 409 .find(".accordion-item-title") 410 .attr("aria-expanded", "false"); 411 } 412 413 if (!parentAccordionItem.hasClass("active")) { 414 // Open the clicked accordion 415 parentAccordionItem.addClass("active"); 416 $(this).attr("aria-expanded", "true"); 417 } else { 418 // Close the clicked accordion 419 parentAccordionItem.removeClass("active"); 420 $(this).attr("aria-expanded", "false"); 421 } 422 }); 423 } 424 425 // Open first accordion on load 426 if ($(".open-first").exists()) { 427 $(".open-first").each(function () { 428 $(this) 429 .find(".accordion-item:first-of-type .accordion-item-title") 430 .addClass("active") 431 .attr("aria-expanded", true); 432 }); 433 } 434 // END ACCORDION CONTROL 435 }); 436 437 /////////////////////////////////// 438 // Scrolls and On-Screen Reveals // 439 /////////////////////////////////// 440 441 // Gradually scrolls window to element on click of any element with the "scroll-to" class 442 $("body").on("click", ".scroll-to", function (e) { 443 e.preventDefault(); 444 var elementToScrollToID = $(this).attr("data-scroll-to-id"); 445 446 if (!elementToScrollToID) { 447 return; 448 } 449 450 animateScroll(elementToScrollToID); 451 452 if (elementToScrollToID == "main") { 453 // After scrolling completes, temporarily add tabindex and focus #main 454 $(elementToScrollToID).attr("tabindex", "-1").focus().removeAttr("tabindex"); 455 } 456 }); 457 458 // Scrolls window back to top of page 459 $(".scroll-to-top").on("click", function (e) { 460 e.preventDefault(); 461 animateScroll(0); 462 }); 463 464 // Recurring events on scroll 465 $(window).on("scroll", function () { 466 // Control frequency of scroll checks for performance 467 let scrollDebounceTimer; 468 clearTimeout(scrollDebounceTimer); 469 scrollDebounceTimer = setTimeout(checkScroll, 5); 470 }); 471 472 function checkScroll() { 473 // Add class to body when scrolled beyond top of page 474 if ($(window).scrollTop() > 0) { 475 $("body").addClass("scrolled"); 476 } else { 477 $("body").removeClass("scrolled"); 478 } 479 480 // If scrolled to bottom, activate slideout feature in footer 481 if ($("#bottom-slideout").exists()) { 482 // Detect page bottom 483 if (getScrollPercent() > 0.98) { 484 $("#bottom-slideout").addActive(); 485 } else { 486 $("#bottom-slideout").removeActive(); 487 } 488 } 489 490 // Check elements that are revealed on scroll 491 $(".scroll-reveal").each(function () { 492 // If element is on screen and not yet revealed, add classes 493 var cur = $(this); 494 if (isScrolledIntoView(cur)) { 495 if (!cur.hasClass("animated")) { 496 cur.addClass("animated fadeInUp"); 497 } 498 } 499 }); 500 } 501 502 ///////////////////// 503 // YouTube Embeds // 504 ///////////////////// 505 506 function setupYouTube() { 507 // Prepare YouTube video placeholders that turn into iframe on click 508 if ($(".youtube-player").length == 0) { 509 return; 510 } 511 512 var div; 513 var n; 514 var v = document.getElementsByClassName("youtube-player"); 515 516 for (n = 0; n < v.length; n++) { 517 div = document.createElement("div"); 518 div.setAttribute("data-id", v[n].dataset.id); 519 div.setAttribute("data-list", v[n].dataset.list); 520 div.innerHTML = 521 '<img src="https://img.youtube.com/vi/' + v[n].dataset.id + '/hqdefault.jpg" alt="">'; 522 523 // Add attributes and classes to the div to make interactive 524 $(div).addClass("youtube-player-inner").attr("tabindex", "0"); 525 $(div).addClass("youtube-player-inner").attr("aria-label", "Play video"); 526 $(div).on("click keypress", getYouTubeFrame); 527 528 // Add div to this player 529 v[n].appendChild(div); 530 } 531 } 532 533 function getYouTubeFrame() { 534 var iframe = document.createElement("iframe"); 535 var embed = "https://www.youtube.com/embed/ID?autoplay=1"; 536 537 iframe.setAttribute("src", embed.replace("ID", this.dataset.id)); 538 iframe.setAttribute("frameborder", "0"); 539 iframe.setAttribute( 540 "allow", 541 "accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share", 542 ); 543 iframe.setAttribute("referrerpolicy", "strict-origin-when-cross-origin"); 544 iframe.setAttribute("allowfullscreen", ""); 545 iframe.setAttribute("title", "YouTube video player"); 546 547 // Clear the parent container and append iframe 548 var container = this.parentNode; 549 container.innerHTML = ""; 550 container.appendChild(iframe); 551 } 552 553 //////////// 554 // Misc. // 555 //////////// 556 557 // Returns true if on homepage 558 // Requires WordPress body classes to be in effect 559 function isHomepage() { 560 if (jQuery("body.home").length) { 561 return true; 562 } 563 return false; 564 } 565 566 // Returns true no homepage class is present 567 function isSubpage() { 568 if (jQuery("body.home").length) { 569 return false; 570 } 571 return true; 572 } 573 574 // Returns true if element is scrolled into view 575 // Usage: isScrolledIntoView(element) 576 function isScrolledIntoView(elem) { 577 let docViewTop = $(window).scrollTop(); 578 let docViewBottom = docViewTop + $(window).height(); 579 let elemTop = $(elem).offset().top; 580 let elemBottom = elemTop + $(elem).outerHeight(); 581 582 return elemTop <= docViewBottom && elemBottom >= docViewTop; 583 } 584 585 // Prepare all links with appropriate attributes and classes 586 function prepareLinks() { 587 // External links: Add attributes for external links 588 $("a") 589 .filter(function () { 590 return this.hostname && this.hostname !== location.hostname; 591 }) 592 .attr("target", "_blank") 593 .attr("rel", "noopener") 594 .addClass("external-link"); 595 596 // PDF links: Open PDFs in new tab 597 $('a[href$=".pdf"]').each(function () { 598 $(this).attr("target", "_blank"); 599 }); 600
601 // Accessibility: Alert the user that a link will open in a new tab 602 $('a[target="_blank"]').each(function () { 603 // Check if the link already has an aria-label 604 var ariaLabel = $(this).attr("aria-label"); 605 606 if (ariaLabel) { 607 // If the link already has an aria-label, append the new text 608 $(this).attr("aria-label", ariaLabel + " (Opens in a new tab)"); 609 } else { 610 // If no aria-label exists, add a new one with the "Opens in a new tab" text 611 $(this).attr("aria-label", "Opens in a new tab"); 612 } 613 }); 614 615 // Identify telephone links 616 $('a[href^="tel:"]').each(function () { 617 $(this).addClass("tel-link"); 618 }); 619 } 620 621 // Recurring events on resize 622 window.addEventListener("resize", function () { 623 setHeaderHeight(); 624 }); 625 626 // Update CSS value equal to header height 627 function setHeaderHeight() { 628 const headerID = isMobileScreen() ? "header-mobile" : "header-desktop"; 629 var header = document.getElementById(headerID); 630 631 if (header) { 632 const headerHeight = header.offsetHeight; 633 document.documentElement.style.setProperty("--header-height", `${headerHeight}px`); 634 } 635 } 636 637})(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.