https://www.housingwire.com/_static/??-eJyVj8sOAiEMRX9IrMZXXBj/wq2…/a3tltWPf7+ab7Wy7WizXs+4B9s+YGQ==
1document.addEventListener("DOMContentLoaded", () => { 2 /** 3 * Add referral url as a parameter whenever a link to the subscribe page is clicked 4 */ 5 document.addEventListener("click", function (event) { 6 const link = event.target.closest("a[href]"); 7 if (!link) return; 8 9 const clickedUrl = new URL(link.href, window.location.origin); 10 const targetUrl = new URL(howMixpanel.subscribe_page_url); 11 12 // Normalize hostnames by removing "www." prefix if present 13 const normalizeHostname = (hostname) => 14 hostname.startsWith("www.") ? hostname.slice(4) : hostname; 15 16 // Normalize pathname by removing trailing slash if present (except root "/") 17 const normalizePathname = (pathname) => 18 pathname !== "/" && pathname.endsWith("/") 19 ? pathname.slice(0, -1) 20 : pathname; 21 22 const clickedHost = normalizeHostname(clickedUrl.hostname); 23 const targetHost = normalizeHostname(targetUrl.hostname); 24 25 const clickedPath = normalizePathname(clickedUrl.pathname); 26 const targetPath = normalizePathname(targetUrl.pathname); 27 28 // Check if hostnames and paths match (ignoring protocol and www, trailing slash) 29 if (clickedHost === targetHost && clickedPath === targetPath) { 30 event.preventDefault(); 31 32 clickedUrl.searchParams.set("ref", window.location.href); 33 34 window.location.href = clickedUrl.toString(); 35 } 36 }); 37 38 /** 39 * If no referral url is set on the subscribe page, add direct, a search engine name, or undefined 40 */ 41 (function () { 42 const currentUrl = window.location.href; 43 const targetUrl = howMixpanel.subscribe_page_url; 44 45 if (currentUrl === targetUrl) { 46 const url = new URL(currentUrl); 47 if (!url.searchParams.has("ref")) { 48 let ref = "direct"; 49 50 if (document.referrer) { 51 try { 52 const referrerUrl = new URL(document.referrer); 53 if (referrerUrl.hostname !== window.location.hostname) { 54 ref = referrerUrl.hostname; 55 } 56 } catch { 57 ref = "undefined"; 58 } 59 } 60 61 url.searchParams.set("ref", ref); 62 63 window.history.replaceState({}, "", url.toString()); 64 } 65 } 66 })(); 67 68 function waitForMixpanel(callback) { 69 if ( 70 typeof mixpanel !== "undefined" && 71 mixpanel.track && 72 window.orchestrator && 73 window.orchestrator.dataFetched 74 ) { 75 callback(); 76 } else { 77 setTimeout(() => waitForMixpanel(callback), 100); 78 } 79 } 80 81 async function trackEventsSegmented() { 82 const baseProps = { 83 page: window.location.pathname, 84 user_type: window.orchestrator.userType, 85 user_id: window.orchestrator.userId, 86 user_name: window.orchestrator.userName, 87 user_email: window.orchestrator.userEmail, 88 user_role: window.orchestrator.userRole, 89 }; 90 91 //console.log("trackEvents called with baseProps:", baseProps); 92 93 mixpanel.track("page_view_segmented", baseProps); 94 //console.log("Sent page_view_segmented event"); 95 96 if (typeof howMixpanel !== "undefined" && howMixpanel.article_title) { 97 //console.log("Sending Article Read event for:", howMixpanel.article_title); 98 mixpanel.track("Article Read", { 99 ...baseProps, 100 "Article Name": howMixpanel.article_title, 101 }); 102 } else { 103 //console.log("No article title found, skipping Article Read event"); 104 } 105 } 106 107 waitForMixpanel(() => { 108 trackEventsSegmented(); 109 document.addEventListener("click", function (e) { 110 const trackableElement = e.target.closest('[data-mixpanel="true"]'); 111 112 if (trackableElement) { 113 try { 114 const location = 115 trackableElement.getAttribute("data-mixpanel-location") || 116 "Unknown"; 117 const sublocation = 118 trackableElement.getAttribute("data-mixpanel-sublocation") || 119 "Unknown"; 120 const label = 121 trackableElement.getAttribute("data-mixpanel-label") || "Unknown"; 122 const page = window.location.pathname; 123 124 mixpanel.track(`${location} - ${label} Clicked`, { 125 page: page, 126 button_location: sublocation, 127 user_type: window.orchestrator.userType, 128 }); 129 } catch (error) { 130 console.error("Mixpanel tracking error:", error); 131 } 132 } 133 }); 134 }); 135}); 136; 137// MR Infinite Table Script (vanilla JS) 138// Assumptions (per your "proceed with defaults"): 139// - API pagination params: limit & offset 140// - Server-side sorting via `category` query param 141// - Other filters/search are applied client-side to fetched results 142// - Table container IDs/classes provided by you are used as-is 143// - Page size = 20 144// 145// Wire this script into your page (e.g., put in a <script> tag after your HTML). 146(() => { 147 // Config 148 const MR_SETTINGS = window.MR_Settings || {}; 149 const API_BASE = MR_SETTINGS.api_base || "/wp-json/rt-lo/v1/ranked"; 150 const YEAR = MR_SETTINGS.year || "2025"; 151 const AD_IMAGES = how_mortgageRankings.ad_images || null; // expected shape: { small: '...', medium: '...', large: '...' } 152 const AD_URL = how_mortgageRankings.ad_url || null; 153 const category = 154 window.how_mortgageRankings && 155 "undefined" !== typeof how_mortgageRankings.category 156 ? how_mortgageRankings.category 157 : ""; 158 const type = how_mortgageRankings.type; 159 const COLSPAN = 160 "undefined" !== typeof category && "yoy_volume" === category ? 8 : 7; 161 let PAGE_SIZE = MR_SETTINGS.page_size || 20; 162 163 // category will be read into `category` (see below). Scroll threshold can also be overridden. 164 const SCROLL_THRESHOLD_PX = MR_SETTINGS.scroll_threshold || 300; 165 166 // DOM selectors 167 const tableBody = document.querySelector("#MR_TableBody"); 168 const sortTabsContainer = null; 169 170 // Defer these so we can resolve them at init() (after DOM ready) 171 let searchInput = null; 172 let stateFilter = null; 173 let brokerFilter = null; 174 let minVolumeInput = null; 175 let minUnitsInput = null; 176 let employerSearchInput = null; 177 178 const loaders = document.querySelectorAll(".mr_loader"); 179 const mrTitle = document.querySelector("#MR_title"); 180 181 const TABLE_ROW_TEMPLATE = { 182 className: "mr-no-results", 183 184 // change this if the Year over Year Growth category needs a different c
184olspan 185 186 htmlNoResults() { 187 return `<td colspan="${COLSPAN}" style="text-align:center; padding: 6rem 1rem!important; color:#666;">No results</td>`; 188 }, 189 htmlLoading() { 190 return `<td colspan="${COLSPAN}" style="text-align:center; padding: 6rem 1rem!important; color:#666;"><span class="mr-intable-loading">Loading...</span></td>`; 191 }, 192 }; 193 194 // Try server-side search. Set to false to use client-side name filtering. 195 const SERVER_SIDE_SEARCH = true; 196 197 // Parameter name to send to the API for name search. Change to 'q' or 'search' if your API uses that. 198 const SERVER_SEARCH_PARAM = "name"; 199 let lastServerSearch = ""; 200 201 const SERVER_EMPLOYER_SEARCH_PARAM = "employer"; 202 let lastServerEmployerSearch = ""; 203 204 if (!tableBody) { 205 console.error( 206 "Required DOM elements (#MR_TableBody or #MR_sortTabs) not found.", 207 ); 208 return; 209 } 210 211 // State 212 let offset = 0; 213 let isLoading = false; 214 let exhausted = false; // no more pages 215 let fetchedItems = []; // all items fetched so far (kept in server order) 216 let renderRequestId = 0; // for debounce invalidation 217 let infiniteObserver = null; 218 219 // Utility formatters 220 const currencyFmt = new Intl.NumberFormat("en-US", { 221 style: "currency", 222 currency: "USD", 223 maximumFractionDigits: 0, 224 }); 225 const numberFmt = new Intl.NumberFormat("en-US"); 226 227 // Helpers to show/hide loader elements 228 function showInitialLoader() { 229 // clear table and show the in-table loading row 230 tableBody.innerHTML = ""; 231 const tr = document.createElement("tr"); 232 tr.className = TABLE_ROW_TEMPLATE.className; 233 tr.innerHTML = TABLE_ROW_TEMPLATE.htmlLoading(); 234 tableBody.appendChild(tr); 235 } 236 function hideInitialLoader() { 237 const existing = tableBody.querySelector( 238 "tr." + TABLE_ROW_TEMPLATE.className, 239 ); 240 if (existing) { 241 existing.remove(); 242 } 243 } 244 245 // Replace showAppendLoader / hideAppendLoader with this minimal implementation 246 // (ensures the loader is inserted after the <table> element, never inside the <tbody>) 247 248 // REPLACE your current showAppendLoader() and hideAppendLoader() with this more robust version. 249 // This ensures the append loader is inserted after the nearest wrapper (.mr-table-wrap) or the table element, 250 // and removes any in-table loader first so it doesn't look like it's inside the table. 251 252 function showAppendLoader() { 253 // don't add another if it already exists 254 if (document.getElementById("MR_append_loader")) { 255 return; 256 } 257 258 // ensure any in-table loader is removed so there's no visual overlap 259 if ("function" === typeof hideInitialLoader) { 260 hideInitialLoader(); 261 } 262 263 // Prefer the wrapper if present 264 const tableEl = tableBody.closest("table"); 265 let insertAfterEl = null; 266 267 if (tableEl) { 268 // look for a wrapper you might be using, fallback to the table element 269 const wrap = tableEl.closest(".mr-table-wrap") || tableEl.parentElement; 270 insertAfterEl = wrap || tableEl; 271 } else { 272 // fallback to tableBody parent 273 insertAfterEl = tableBody.parentElement || document.body; 274 } 275 276 const wrapper = document.createElement("div"); 277 wrapper.id = "MR_append_loader"; 278 wrapper.className = "mr-append-loader"; 279 wrapper.setAttribute("role", "status"); 280 wrapper.setAttribute("aria-live", "polite"); 281 wrapper.style.textAlign = "center"; 282 wrapper.style.padding = "12px 0"; 283 wrapper.innerHTML = 284 '<div class="mr-intable-loading" aria-hidden="true"><span class="mr-intable-loading-label" style="color: #666">Loading...</span></div>'; 285 286 // Insert after the chosen element 287 if (insertAfterEl && insertAfterEl.parentNode) { 288 if (insertAfterEl.nextSibling) { 289 insertAfterEl.parentNode.insertBefore( 290 wrapper, 291 insertAfterEl.nextSibling, 292 ); 293 } else { 294 insertAfterEl.parentNode.appendChild(wrapper); 295 } 296 } else { 297 document.body.appendChild(wrapper); 298 } 299 } 300 301 function hideAppendLoader() { 302 const el = document.getElementById("MR_append_loader"); 303 if (el) { 304 el.remove(); 305 } 306 } 307 308 // Build fetch URL with params 309 // Replace your existing buildUrl function with this version that uses the `name` query param for server-side search. 310 function buildUrl(params = {}) { 311 // Create a URL object that works if API_BASE is absolute or relative 312 let url; 313 try { 314 url = new URL(API_BASE); // works if API_BASE is absolute (https://...) 315 } catch (err) { 316 // fallback: treat API_BASE as relative to the current origin 317 url = new URL(API_BASE, window.location.origin); 318 } 319 320 // Ensure year & category are set (these override any existing query values) 321 url.searchParams.set("year", YEAR); 322 url.searchParams.set("category", category); 323 324 // Add provided params (limit, offset, etc.)
325 Object.keys(params).forEach((k) => { 326 url.searchParams.set(k, params[k]); 327 }); 328 329 // If server-side search is enabled and there's a search term, include it as `name` 330 if (SERVER_SIDE_SEARCH) { 331 if (searchInput) { 332 const q = (searchInput.value || "").trim(); 333 if (q) { 334 url.searchParams.set(SERVER_SEARCH_PARAM || "name", q); 335 } else { 336 url.searchParams.delete(SERVER_SEARCH_PARAM || "name"); 337 } 338 } 339 340 if (employerSearchInput) { 341 const employerQ = (employerSearchInput.value || "").trim(); 342 if (employerQ) { 343 url.searchParams.set( 344 SERVER_EMPLOYER_SEARCH_PARAM || "employer", 345 employerQ, 346 ); 347 } else { 348 url.searchParams.delete(SERVER_EMPLOYER_SEARCH_PARAM || "employer"); 349 } 350 } 351 } 352 353 // Add min_units (read current input value at call time). fallback if no ID. 354 (function addMinUnits() { 355 const group = 356 document.querySelector(".flex.flex-wrap.items-end") || document; 357 const fallbackUnits = group 358 ? Array.from(group.querySelectorAll('input[type="number"]'))[1] || null 359 : null; 360 const unitsEl = 361 document.getElementById("MR_minUnits") || 362 minUnitsInput || 363 fallbackUnits; 364 if (unitsEl) { 365 const u = (unitsEl.value || "").toString().trim(); 366 if ("" !== u && !Number.isNaN(Number(u))) { 367 url.searchParams.set("min_units", u); 368 } else { 369 url.searchParams.delete("min_units"); 370 } 371 } else { 372 url.searchParams.delete("min_units"); 373 } 374 })(); 375 376 // Add state filter when present (read live) 377 if (stateFilter) { 378 const sv = (stateFilter.value || "").toString().trim(); 379 if (sv && "All" !== sv) { 380 url.searchParams.set("state", sv); 381 } else { 382 url.searchParams.delete("state"); 383 } 384 } 385 386 return url.toString(); 387 } 388 389 // Replace your existing fetchPage function with this version. 390 // This updates #MR_number on every successful fetch when `json.count` is present. 391 // Helper: are we allowed to fetch beyond the first page? 392 // Helper: are we allowed to fetch beyond the first page? 393 function allowedToFetchMore() { 394 // Restriction only applies when type is 'segmented'. 395 // If not segmented, allow fetching more for all users. 396 if ("segmented" !== type) { 397 return true; 398 } 399 400 // When segmented, only 'subscriber' or 'registered_user' may fetch past the first page 401 return ( 402 "subscriber" === window.orchestrator.userType || 403 "registered_user" === window.orchestrator.userType 404 ); 405 } 406 407 async function fetchPage() { 408 // Prevent concurrent fetches or when we've already exhausted results 409 if (isLoading || exhausted) { 410 return; 411 } 412 413 // If user is not allowed to fetch more and we've already fetched the first page, stop here 414 if (!allowedToFetchMore() && offset >= PAGE_SIZE) { 415 exhausted = true; 416 417 // ensure any append loader is removed 418 hideAppendLoader(); 419 return; 420 } 421 422 isLoading = true; 423 if (0 === offset) { 424 showInitialLoader(); 425 } else { 426 showAppendLoader(); 427 } 428 429 try { 430 if ( 431 "segmented" == type && 432 !( 433 "subscriber" === window.orchestrator.userType || 434 "registered_user" === window.orchestrator.userType 435 ) 436 ) { 437 PAGE_SIZE = 10; 438 } 439 const url = buildUrl({ limit: PAGE_SIZE, offset }); 440 const resp = await fetch(url); 441 if (!resp.ok) { 442 console.error("API fetch error", resp.status, resp.statusText); 443 exhausted = true; 444 return; 445 } 446 const json = await resp.json(); 447 448 // Update the MR_number element whenever the response includes a count 449 if (json && "undefined" !== typeof json.count) { 450 const countEl = document.getElementById("MR_number"); 451 if (countEl) { 452 const totalCount = Number(json.count || 0); 453 countEl.textContent = numberFmt.format(totalCount); 454 } 455 } 456 457 const page = Array.isArray(json.data) ? json.data : []; 458 459 // Push into fetchedItems 460 fetchedItems = fetchedItems.concat(page); 461 462 // If the returned page is smaller than PAGE_SIZE, or we've fetched all according to server count, we've reached the end 463 if ( 464 page.length < PAGE_SIZE || 465 fetchedItems.length >= (json.count || Infinity) 466 ) { 467 exhausted = true; 468 } 469 470 // Advance offset for the next fetch (if permitted) 471 offset += PAGE_SIZE; 472 473 // Repopulate filter dropdowns (based on seen data) 474 populateStateFilterOptions(); 475 476 // Re-render visible rows using current filters/search 477 renderFilteredRowsDebounced(); 478 } catch (err) { 479 console.error("Fetch exception", err); 480 exhausted = true; 481 } finally { 482 isLoading = false;
483 hideInitialLoader(); 484 hideAppendLoader(); 485 486 // If we've reached the end, stop observing / remove sentinel 487 if (exhausted) { 488 try { 489 teardownInfiniteScroll(); 490 } catch (e) { 491 // non-fatal 492 } 493 } 494 } 495 } 496 497 // Rendering a single row (returns tr element) 498 // Replace your existing createRow function with this version. 499 // Handles the normal columns and a special layout when category === 'yoy_volume'. 500 // Assumptions: 501 // - previous total is available as submission.prev_total_volume (or submission.previous_total_volume). 502 // - percent change is available as submission.volume_change_pct or submission.yoy_volume (the code tries both). 503 // - If the percent value is a decimal (e.g. 0.125) it will be multiplied by 100 for display. 504 // - Adjust the field names if your API uses different keys. 505 function createRow(item) { 506 const sub = item.submission || {}; 507 const off = item.originator || item.officer || {}; 508 const rank = null != item.rank ? item.rank : ""; 509 const name = off.loan_originator_name || ""; 510 const employer = off.lo_employer; 511 const state = off.branch_state || ""; 512 const units = Number(sub.total_count || 0); 513 514 // Determine totalVolume based on explicit category matches 515 let totalVolume = 0; 516 if ("undefined" !== typeof category && "fha_volume" === category) { 517 totalVolume = Number(sub.fha_volume || 0); 518 } else if ("undefined" !== typeof category && "refi_volume" === category) { 519 totalVolume = Number(sub.refi_volume || 0); 520 } else if ( 521 "undefined" !== typeof category && 522 "purchase_volume" === category 523 ) { 524 totalVolume = Number(sub.purchase_volume || 0); 525 } else if ("undefined" !== typeof category && "va_volume" === category) { 526 totalVolume = Number(sub.va_volume || 0); 527 } else if ("undefined" !== typeof category && "usda_volume" === category) { 528 totalVolume = Number(sub.usda_volume || 0); 529 } else if ("undefined" !== typeof category && "nonqm_volume" === category) { 530 totalVolume = Number(sub.nonqm_volume || 0); 531 } else if ( 532 "undefined" !== typeof category && 533 "equity_volume" === category 534 ) { 535 totalVolume = Number(sub.equity_volume || 0); 536 } else if ( 537 "undefined" !== typeof category && 538 "brokered_volume" === category 539 ) { 540 totalVolume = Number(sub.brokered_volume || 0); 541 } else { 542 // default fallback 543 totalVolume = Number(sub.total_volume || 0); 544 } 545 546 const avgLoan = 0 < units ? Math.round(totalVolume / units) : 0; 547 const tr = document.createElement("tr"); 548 549 // If this category shows year-over-year columns, render the alternate columns 550 if ("undefined" !== typeof category && "yoy_volume" === category) { 551 // Try a couple of plausible previous-total field names 552 const prevTotalRaw = 553 "undefined" !== typeof sub.prev_total_volume 554 ? sub.prev_total_volume 555 : "undefined" !== typeof sub.previous_total_volume 556 ? sub.previous_total_volume 557 : 0; 558 const prevTotal = Number(prevTotalRaw || 0); 559 560 const delta = totalVolume - prevTotal; 561 562 // Try plausible percent fields 563 let pctRaw = 564 "undefined" !== typeof sub.volume_change_pct 565 ? sub.volume_change_pct 566 : "undefined" !== typeof sub.yoy_volume 567 ? sub.yoy_volume 568 : 0; 569 pctRaw = Number(pctRaw || 0); 570 571 // If API returns decimal (0.125) convert to percent; if it returns >1 assume it's already percent. 572 /* 573 const pctForDisplay = ! isNaN( pctRaw ) ? 574 ( 1 >= Math.abs( pctRaw ) ? pctRaw * 100 : pctRaw ) : 575 0; */ 576 577 const pctForDisplay = pctRaw * 100; 578 579 // Format percent with one decimal 580 const pctDisplay = `${new Intl.NumberFormat("en-US", { minimumFractionDigits: 1, maximumFractionDigits: 1 }).format(pctForDisplay)}%`; 581 582 tr.innerHTML = ` 583 <td class="mr-col-number">${escapeHtml(rank)}</td> 584 <td class="mr-col-name">${escapeHtml(name)}</td> 585 <td class="mr-col-employer">${escapeHtml(employer)}</td> 586 <td class="mr-col-state">${escapeHtml(state)}</td> 587 <td class="mr-col-prev-total">${escapeHtml(currencyFmt.format(prevTotal))}</td> 588 <td class="mr-col-total-volume">${escapeHtml(currencyFmt.format(totalVolume))}</td> 589 <td class="mr-col-delta-volume">${escapeHtml(currencyFmt.format(delta))}</td> 590 <td class="mr-col-volume-pct">${escapeHtml(pctDisplay)}</td> 591 `.trim(); 592 593 return tr; 594 } 595 596 // Default row layout 597 tr.innerHTML = ` 598 <td class="mr-col-number">${escapeHtml(rank)}</td> 599 <td class="mr-col-name">${escapeHtml(name)}</td> 600 <td class="mr-col-employer">${escapeHtml(employer)}</td> 601 <td class="mr-col-state">${escapeHtml(state)}</td> 602 <td class="mr-col-total-volume">${escapeHtml(currencyFmt.format(totalVolume))}</td> 603 <td class="mr-col-units">${escapeHtml(numberFmt.format(units))}</td> 604 <td class="mr-col-avg-loan">${escapeHtml(currencyFmt.format(avgLoan))}</td> 605 `.trim(); 606 607 return tr; 608 } 609 610 function createAdRow() { 611 const tr = document.createElement("tr"); 612 tr.className = "mr-ad-row relative"; 613 614 const td = document.createElement("td"); 615 td.setAttribute("colspan", COLSPAN); 616 td.style.backgroundColor = "#EEECEA"; 617 618 // Use your tailwind padding class inside the cell 619 const inner = document.createElement("div"); 620 inner.className = 621 "mr-ad-row-inner p-4 text-center flex items-center justify-center"; 622 inner.style.backgroundColor = "#EEECEA"; 623 624 // Build responsive image markup if AD_IMAGES provided, otherwise fallback placeholder 625 if (AD_IMAGES && (AD_IMAGES.small || AD_IMAGES.medium || AD_IMAGES.large)) { 626 // Replace the inner.innerHTML assignment block with this (uses MR_SETTINGS.ad_url as AD_URL) 627 // Replace the inner.innerHTML assignment with this to output one <img> per size inside the <a>. 628 // Default Tailwind visibility classes are provided but you can replace them as you prefer. 629 const adHref = AD_URL || "#";
630 const imgParts = []; 631 632 // small: visible on smallest breakpoints (example classes: visible on sm and below) 633 if (AD_IMAGES.small) { 634 imgParts.push( 635 `<img src="${AD_IMAGES.small}" alt="Ad small" class="ad-img-small block important:md:hidden w-full h-auto" style="max-width: 300px" loading="lazy" />`, 636 ); 637 } 638 639 // medium: visible on medium breakpoints (example classes) 640 if (AD_IMAGES.medium) { 641 imgParts.push( 642 `<img src="${AD_IMAGES.medium}" alt="Ad medium" class="ad-img-medium important:hidden important:md:block w-full h-auto" style="max-width: 720px" loading="lazy" />`, 643 ); 644 } 645 646 // large: visible on large breakpoints (example classes) 647 if (AD_IMAGES.large) { 648 //imgParts.push( `<img src="${AD_IMAGES.large}" alt="Ad large" class="ad-img-large hidden lg:block w-full h-auto" loading="lazy" style="max-width:720px;" />` ); 649 } 650 651 inner.innerHTML = `<a href="${adHref}" class="inline-block" target="_blank" rel="noopener noreferrer">${imgParts.join("")}</a>`; 652 } else { 653 // fallback -- empty box the editor can style or replace later 654 inner.innerHTML = '<div class="w-full h-24 bg-gray-100"></div>'; 655 } 656 657 td.appendChild(inner); 658 tr.appendChild(td); 659 return tr; 660 } 661 662 // Simple HTML escape for injected content 663 function escapeHtml(str) { 664 if (null === str || str === undefined) { 665 return ""; 666 } 667 return String(str) 668 .replace(/&/g, "&") 669 .replace(/</g, "<") 670 .replace(/>/g, ">"); 671 } 672 673 // Filtering logic (client-side) 674 function applyFilters(items) { 675 const q = ((searchInput && searchInput.value) || "").trim().toLowerCase(); 676 const stateVal = (stateFilter && stateFilter.value) || "All"; 677 const brokerVal = (brokerFilter && brokerFilter.value) || "All"; 678 const minVol = Number((minVolumeInput && minVolumeInput.value) || 0); 679 const minUnits = Number((minUnitsInput && minUnitsInput.value) || 0); 680 const employerQ = ((employerSearchInput && employerSearchInput.value) || "") 681 .trim() 682 .toLowerCase(); 683 684 return items.filter((item) => { 685 // Prefer originator but fall back to officer for compatibility 686 const off = item.originator || item.officer || {}; 687 const sub = item.submission || {}; 688 689 // Search by LO name (loan_originator_name) â only if server-side search is disabled 690 if (!SERVER_SIDE_SEARCH) { 691 if (q) { 692 const name = (off.loan_originator_name || "").toLowerCase(); 693 if (!name.includes(q)) { 694 return false; 695 } 696 } 697 698 if (employerQ) { 699 const employer = (off.lo_employer || "").toLowerCase(); 700 if (!employer.includes(employerQ)) { 701 return false; 702 } 703 } 704 } 705 706 // State filter 707 if (stateVal && "All" !== stateVal) { 708 if ((off.branch_state || "") !== stateVal) { 709 return false; 710 } 711 } 712 713 // Broker/banker filter (map company_institution_type to Broker/Banker/Other) 714 if (brokerVal && "All" !== brokerVal) { 715 const cit = (off.company_institution_type || "").toLowerCase(); 716 const inferred = cit.includes("broker") 717 ? "Broker" 718 : cit.includes("bank") || cit.includes("mortgage") 719 ? "Banker" 720 : "Other"; 721 if (inferred !== brokerVal) { 722 return false; 723 } 724 } 725 726 // Min volume (use submission.total_volume) 727 const totalVolume = Number(sub.total_volume || sub.total || 0); 728 if (minVol && totalVolume < minVol) { 729 return false; 730 } 731 732 // Min units (use submission.total_count) 733 const units = Number(sub.total_count || sub.units || 0); 734 if (minUnits && units < minUnits) { 735 return false; 736 } 737 738 return true; 739 }); 740 } 741 742 // Render filtered rows (clear table and append) 743 // Replace the existing renderFilteredRows() with this version 744 function renderFilteredRows() { 745 renderRequestId += 1; 746 const thisRenderId = renderRequestId; 747 748 const filtered = applyFilters(fetchedItems); 749 750 // Clear table 751 tableBody.innerHTML = ""; 752 753 const fragment = document.createDocumentFragment(); 754 755 if (!filtered || 0 === filtered.length) { 756 // If there are no filtered rows, show a single "No results" row. 757 const tr = document.createElement("tr"); 758 tr.className = TABLE_ROW_TEMPLATE.className; 759 tr.innerHTML = TABLE_ROW_TEMPLATE.htmlNoResults(); 760 fragment.appendChild(tr); 761 } else { 762 // Loop with index so we can insert the ad after the 10th row (after index 9) 763 for (let i = 0; i < filtered.length; i++) { 764 const item = filtered[i]; 765 const tr = createRow(item); 766 fragment.appendChild(tr); 767 768 // After the 10th row (i === 9) and only when there are at least 10 rows total, 769 // insert the ad row as the 11th row. Only insert once. 770 if (9 === i && 10 <= filtered.length) { 771 const adTr = createAdRow(); 772 fragment.appendChild(adTr); 773 } 774 } 775 } 776 777 tableBody.appendChild(fragment); 778 779 // If another render was requested meanwhile, nothing else to do (next render will replace) 780 if (thisRenderId !== renderRequestId) { 781 return; 782 } 783 } 784 785 // Debounced render to avoid rapid DOM thrash 786 let renderDebounceTimer = null; 787 function renderFilteredRowsDebounced(delay = 200) { 788 if (renderDebounceTimer) { 789 clearTimeout(renderDebounceTimer); 790 } 791 renderDebounceTimer = setTimeout(() => { 792 renderFilteredRows(); 793 renderDebounceTimer = null; 794 }, delay); 795 } 796 797 // Populate state filter options based on fetchedItems seen so far 798 function populateStateFilterOptions() { 799 if (!stateFilter) { 800 return; 801 } 802 803 // Gather unique states from fetchedItems, checking originator then officer 804 const states = new Set();
805 fetchedItems.forEach((it) => { 806 const s = 807 (it.originator && it.originator.branch_state) || 808 (it.officer && it.officer.branch_state) || 809 ""; 810 if (s) { 811 states.add(s); 812 } 813 }); 814 815 // Convert to sorted array 816 const stateList = Array.from(states).sort(); 817 818 // Save current selection (may be 'All' or a two-letter code) 819 const current = stateFilter.value || "All"; 820 821 // Preserve a current non-All selection even if it didn't appear in the newly gathered list 822 if (current && "All" !== current && -1 === stateList.indexOf(current)) { 823 stateList.unshift(current); 824 } 825 826 // Rebuild options: 'All' first 827 stateFilter.innerHTML = ""; 828 const allOpt = document.createElement("option"); 829 allOpt.value = "All"; 830 allOpt.textContent = "All"; 831 stateFilter.appendChild(allOpt); 832 833 stateList.forEach((s) => { 834 const opt = document.createElement("option"); 835 opt.value = s; 836 opt.textContent = s; 837 stateFilter.appendChild(opt); 838 }); 839 840 // restore selection if still present, otherwise default to 'All' 841 if ([...stateFilter.options].some((o) => o.value === current)) { 842 stateFilter.value = current; 843 } else { 844 stateFilter.value = "All"; 845 } 846 } 847 848 // Populate broker filter with All / Broker / Banker / Other (static) 849 function populateBrokerFilter() { 850 if (!brokerFilter) { 851 return; 852 } 853 const opts = ["All", "Broker", "Banker", "Other"]; 854 brokerFilter.innerHTML = ""; 855 opts.forEach((val) => { 856 const o = document.createElement("option"); 857 o.value = val; 858 o.textContent = val; 859 brokerFilter.appendChild(o); 860 }); 861 } 862 863 // Debounce helper 864 function debounce(fn, wait) { 865 let t = null; 866 return function (...args) { 867 if (t) { 868 clearTimeout(t); 869 } 870 t = setTimeout(() => fn.apply(this, args), wait); 871 }; 872 } 873 874 function setupFilters() { 875 searchInput = document.querySelector("#MR_Search") || null; 876 stateFilter = document.querySelector("#MR_stateFilter") || null; 877 brokerFilter = document.querySelector("#MR_brokerFilter") || null; 878 employerSearchInput = document.querySelector("#MR_Search_Employer") || null; 879 880 // Resolve numeric inputs robustly (prefer IDs, fall back to number inputs inside the filters area) 881 const filterGroup = 882 document.querySelector(".flex.flex-wrap.items-end") || document; 883 const numberInputs = Array.from( 884 filterGroup.querySelectorAll('input[type="number"]'), 885 ); 886 minVolumeInput = 887 document.getElementById("MR_minVolume") || numberInputs[0] || null; 888 minUnitsInput = 889 document.getElementById("MR_minUnits") || 890 numberInputs[1] || 891 numberInputs[0] || 892 null; 893 894 // replace existing searchInput handler with this 895 if (searchInput) { 896 // keep last value to avoid unnecessary fetches 897 let lastSearchVal = (lastServerSearch || "").toString(); 898 899 const debouncedSearch = debounce(() => { 900 const q = (searchInput.value || "").trim(); 901 if (q !== lastSearchVal) { 902 lastSearchVal = q; 903 lastServerSearch = q; 904 offset = 0; 905 exhausted = false; 906 fetchedItems = []; 907 tableBody.innerHTML = ""; 908 fetchPage(); 909 } 910 }, 1000); // 1 second debounce 911
912 // only input/change â no focus/click handlers 913 searchInput.addEventListener("input", debouncedSearch); 914 searchInput.addEventListener("change", () => { 915 const q = (searchInput.value || "").trim(); 916 if (q !== lastSearchVal) { 917 lastSearchVal = q; 918 lastServerSearch = q; 919 offset = 0; 920 exhausted = false; 921 fetchedItems = []; 922 tableBody.innerHTML = ""; 923 fetchPage(); 924 } 925 }); 926 } 927 928 if (employerSearchInput) { 929 let lastEmployerSearchVal = (lastServerEmployerSearch || "").toString(); 930 931 const debouncedEmployerSearch = debounce(() => { 932 const q = (employerSearchInput.value || "").trim(); 933 if (q !== lastEmployerSearchVal) { 934 lastEmployerSearchVal = q; 935 lastServerEmployerSearch = q; 936 offset = 0; 937 exhausted = false; 938 fetchedItems = []; 939 tableBody.innerHTML = ""; 940 fetchPage(); 941 } 942 }, 1000); 943 944 employerSearchInput.addEventListener("input", debouncedEmployerSearch); 945 employerSearchInput.addEventListener("change", () => { 946 const q = (employerSearchInput.value || "").trim(); 947 if (q !== lastEmployerSearchVal) { 948 lastEmployerSearchVal = q; 949 lastServerEmployerSearch = q; 950 offset = 0; 951 exhausted = false; 952 fetchedItems = []; 953 tableBody.innerHTML = ""; 954 fetchPage(); 955 } 956 }); 957 } 958 959 if (stateFilter) { 960 stateFilter.addEventListener("change", () => { 961 // reset paging and fetched cache, then request first page from server 962 offset = 0; 963 exhausted = false; 964 if (Array.isArray(fetchedItems)) { 965 fetchedItems.length = 0; 966 } 967 if (tableBody) { 968 tableBody.innerHTML = ""; 969 } 970 fetchPage(); 971 }); 972 } 973 if (brokerFilter) { 974 brokerFilter.addEventListener("change", () => { 975 renderFilteredRowsDebounced(); 976 }); 977 } 978 if (minVolumeInput) { 979 minVolumeInput.addEventListener( 980 "input", 981 debounce(() => { 982 renderFilteredRowsDebounced(); 983 }, 300), 984 ); 985 } 986 if (minUnitsInput) { 987 let lastMinUnitsVal = (minUnitsInput.value || "").toString(); 988 989 const debouncedMinUnits = debounce(() => { 990 const v = (minUnitsInput.value || "").trim(); 991 if (v !== lastMinUnitsVal) { 992 lastMinUnitsVal = v; 993 offset = 0; 994 exhausted = false; 995 fetchedItems = []; 996 tableBody.innerHTML = ""; 997 fetchPage(); 998 } 999 }, 1000); // 1 second debounce 1000 1001 minUnitsInput.addEventListener("input", debouncedMinUnits); 1002 minUnitsInput.addEventListener("change", () => { 1003 const v = (minUnitsInput.value || "").trim(); 1004 if (v !== lastMinUnitsVal) { 1005 lastMinUnitsVal = v; 1006 offset = 0; 1007 exhausted = false; 1008 fetchedItems = []; 1009 tableBody.innerHTML = ""; 1010 fetchPage(); 1011 } 1012 }); 1013 } 1014 } 1015 1016 // --- START: integrate mr-state dropdown (insert at end of setupFilters(), before the closing brace) --- 1017 (function initMrStateDropdown() { 1018 const HIDDEN_CLASS = "tw-hidden"; 1019 const trigger = document.getElementById("mr-state-trigger"); 1020 const list = document.getElementById("mr-state-list"); 1021 if (!trigger || !list) { 1022 return; 1023 } 1024 1025 // Find or create stateFilter so buildUrl()/fetchPage() will see the value 1026 if (!stateFilter) { 1027 const container = 1028 document.querySelector(".flex.flex-wrap.items-end") || document.body; 1029 const hiddenSelect = document.createElement("select"); 1030 hiddenSelect.id = "MR_stateFilter"; 1031 hiddenSelect.style.display = "none"; 1032 hiddenSelect.value = "All"; 1033 container.appendChild(hiddenSelect); 1034 stateFilter = hiddenSelect; 1035 } 1036 1037 const valueSpan = trigger.querySelector(".mr-state-value") || null; 1038 1039 // Toggle dropdown visibility on trigger click 1040 trigger.addEventListener("click", (ev) => { 1041 ev.stopPropagation(); 1042 const showing = !list.classList.contains(HIDDEN_CLASS); 1043 if (showing) { 1044 list.classList.add(HIDDEN_CLASS); 1045 trigger.setAttribute("aria-expanded", "false"); 1046 } else { 1047 list.classList.remove(HIDDEN_CLASS); 1048 trigger.setAttribute("aria-expanded", "true"); 1049 1050 // focus first interactive item 1051 const firstBtn = list.querySelector("button[data-value]"); 1052 if (firstBtn) { 1053 firstBtn.focus(); 1054 } 1055 } 1056 }); 1057 1058 // Close when clicking outside 1059 document.addEventListener("click", (ev) => { 1060 if (!list.classList.contains(HIDDEN_CLASS)) { 1061 if (!trigger.contains(ev.target) && !list.contains(ev.target)) { 1062 list.classList.add(HIDDEN_CLASS); 1063 trigger.setAttribute("aria-expanded", "false"); 1064 } 1065 } 1066 }); 1067 1068 // Keyboard support: ArrowDown opens, Escape closes 1069 trigger.addEventListener("keydown", (ev) => { 1070 if ("ArrowDown" === ev.key) { 1071 ev.preventDefault(); 1072 list.classList.remove(HIDDEN_CLASS); 1073 trigger.setAttribute("aria-expanded", "true"); 1074 const firstBtn = list.querySelector("button[data-value]"); 1075 if (firstBtn) { 1076 firstBtn.focus(); 1077 } 1078 } 1079 }); 1080 document.addEventListener("keydown", (ev) => { 1081 if ("Escape" === ev.key && !list.classList.contains(HIDDEN_CLASS)) { 1082 list.classList.add(HIDDEN_CLASS); 1083 trigger.setAttribute("aria-expanded", "false"); 1084 trigger.focus(); 1085 } 1086 }); 1087 1088 // Handle selection clicks 1089 list.addEventListener("click", (ev) => { 1090 const btn = ev.target.closest("button[data-value]"); 1091 if (!btn) { 1092 return; 1093 } 1094 ev.stopPropagation(); 1095 1096 const value = btn.getAttribute("data-value"); 1097 const text = (btn.textContent || "").trim(); 1098 1099 // Update visible label 1100 if ("all" === value || !value) { 1101 if (valueSpan) { 1102 valueSpan.textContent = "All States"; 1103 } 1104 } else { 1105 if (valueSpan) { 1106 valueSpan.textContent = text; 1107 } 1108 } 1109 1110 // Update stateFilter so buildUrl() picks it up 1111 try { 1112 if (stateFilter) { 1113 if ("all" === value || !value) { 1114 stateFilter.value = "All"; 1115 } else { 1116 // ensure option exists (if using a hidden select with no options) 1117 if (![...stateFilter.options].some((o) => o.value === value)) { 1118 const opt = document.createElement("option"); 1119 opt.value = value; 1120 opt.textContent = text; 1121 stateFilter.appendChild(opt); 1122 } 1123 stateFilter.value = value; 1124 } 1125 1126 // trigger change event in case anything listens to it 1127 stateFilter.dispatchEvent(new Event("change", { bubbles: true })); 1128 } 1129 } catch (e) { 1130 // non-fatal 1131 console.warn("mr-state: could not update stateFilter", e); 1132 } 1133 1134 // hide dropdown 1135 list.classList.add(HIDDEN_CLASS); 1136 trigger.setAttribute("aria-expanded", "false"); 1137 1138 // reset paging and fetch (server-driven count) â use existing fetchPage() 1139 try { 1140 offset = 0; 1141 exhausted = false;
1142 if (Array.isArray(fetchedItems)) { 1143 fetchedItems.length = 0; 1144 } 1145 if (tableBody) { 1146 tableBody.innerHTML = ""; 1147 } 1148 if ("function" === typeof fetchPage) { 1149 fetchPage(); 1150 } else { 1151 // fallback: attempt to call renderFilteredRowsDebounced if fetchPage is not present 1152 if ("function" === typeof renderFilteredRowsDebounced) { 1153 renderFilteredRowsDebounced(); 1154 } 1155 } 1156 } catch (err) { 1157 console.error("mr-state: fetch trigger failed", err); 1158 } 1159 }); 1160 })(); 1161 1162 // --- END: integrate mr-state dropdown --- 1163 1164 // Infinite scroll handler 1165 function onScroll() { 1166 if (isLoading || exhausted) { 1167 return; 1168 } 1169 const scrolledToBottom = 1170 window.innerHeight + window.scrollY >= 1171 document.documentElement.scrollHeight - SCROLL_THRESHOLD_PX; 1172 if (!scrolledToBottom) { 1173 return; 1174 } 1175 1176 fetchPage(); 1177 } 1178 1179 function setupInfiniteScroll() { 1180 // create or locate a small sentinel element placed after the table/wrapper 1181 const tableEl = tableBody.closest("table") || tableBody; 1182 const wrap = tableEl 1183 ? tableEl.closest(".mr-table-wrap") || tableEl.parentElement 1184 : document.body; 1185 1186 let sentinel = document.getElementById("MR_scroll-sentinel"); 1187 if (!sentinel) { 1188 sentinel = document.createElement("div"); 1189 sentinel.id = "MR_scroll-sentinel"; 1190 sentinel.style.width = "1px"; 1191 sentinel.style.height = "1px"; 1192 sentinel.style.visibility = "hidden"; 1193 if (wrap && wrap.parentNode) { 1194 if (wrap.nextSibling) { 1195 wrap.parentNode.insertBefore(sentinel, wrap.nextSibling); 1196 } else { 1197 wrap.parentNode.appendChild(sentinel); 1198 } 1199 } else { 1200 document.body.appendChild(sentinel); 1201 } 1202 } 1203 1204 // Use IntersectionObserver when available (more reliable on mobile) 1205 if ("IntersectionObserver" in window) { 1206 // rootMargin preloads earlier; adjust value if you want earlier/later loads 1207 infiniteObserver = new IntersectionObserver( 1208 (entries) => { 1209 entries.forEach((entry) => { 1210 if (entry.isIntersecting && !isLoading && !exhausted) { 1211 // slight debounce to avoid double-invocation on some browsers 1212 setTimeout(() => { 1213 if (!isLoading && !exhausted) { 1214 fetchPage(); 1215 } 1216 }, 100); 1217 } 1218 }); 1219 }, 1220 { root: null, rootMargin: "200px 0px", threshold: 0 }, 1221 ); 1222 1223 infiniteObserver.observe(sentinel); 1224 } else { 1225 // Fallback: attach scroll listener (keep existing onScroll) 1226 window.addEventListener("scroll", onScroll, { passive: true }); 1227 } 1228 } 1229 1230 function teardownInfiniteScroll() { 1231 const sentinel = document.getElementById("MR_scroll-sentinel"); 1232 if (infiniteObserver) { 1233 infiniteObserver.disconnect(); 1234 infiniteObserver = null; 1235 } else { 1236 window.removeEventListener("scroll", onScroll, { passive: true }); 1237 } 1238 if (sentinel && sentinel.parentNode) { 1239 sentinel.parentNode.removeChild(sentinel); 1240 } 1241 } 1242 1243 // Initialization 1244 function init() { 1245 // Populate static broker options 1246 populateBrokerFilter(); 1247 1248 // Setup filters 1249 setupFilters(); 1250 1251 // If there's no category provided, do not call the API â show "No results" in-table 1252 if (!category) { 1253 fetchedItems = []; 1254 1255 // renderFilteredRows will produce the single "No results" row when fetchedItems is empty 1256 renderFilteredRows(); 1257 return; 1258 } 1259 1260 // Otherwise fetch the first page and enable infinite scroll 1261 fetchPage(); 1262 setupInfiniteScroll(); 1263 } 1264 1265 // Start when DOM is ready 1266 if ("loading" === document.readyState) { 1267 window.addEventListener("DOMContentLoaded", init); 1268 } else { 1269 init(); 1270 } 1271})(); 1272 1273document.addEventListener("DOMContentLoaded", () => { 1274 const toggle = document.getElementById("MR_toggle_sidebar"); 1275 const sidebar = document.getElementById("MR_sidebar_inner"); 1276 if (!toggle || !sidebar) { 1277 return; 1278 } 1279 1280 const svg = toggle.querySelector("svg"); 1281 1282 toggle.addEventListener("click", (e) => { 1283 e.preventDefault(); 1284 sidebar.classList.toggle("tw-hidden"); 1285 if (svg) { 1286 svg.classList.toggle("rotate-180"); 1287 } 1288 }); 1289}); 1290; 1291!function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}
1291),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=11)}({11:function(e,t,n){"use strict";n.r(t);n(12);"undefined"!=typeof tp&&(tp=window.tp||[],tp.push(["init",function(){if(!0===tp.pianoId.isUserValid()){var e=tp.pianoId.getUser();!function(e,t){if(null!==document.getElementById("comments")){var n=document.getElementById("author"),r=document.getElementById("email");n.value=e,r.value=t}}((e.firstName?e.firstName:"")+" "+(e.lastName?e.lastName:""),e.email)}}]))},12:function(e,t,n){}});; 1292!function(e){var t={};function i(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,i),o.l=!0,o.exports}i.m=e,i.c=t,i.d=function(e,t,n){i.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},i.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},i.t=function(e,t){if(1&t&&(e=i(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(i.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)i.d(n,o,function(t){return e[t]}.bind(null,o));return n},i.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return i.d(t,"a",t),t},i.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},i.p="",i(i.s=26)}({26:function(e,t,i){"use strict";i.r(t),{init:function(){this.window=$(window),this.videoWrap=$(".responsive-embed.widescreen"),this.video=$(".container__vid"),this.player=$(".jwplayer"),this.videoHeight=this.video.innerHeight(),this.bindEvents()},bindEvents:function(){var e=this;this.window.on("scroll",function(t){return e.initFloatingVideo(t)})},initFloatingVideo:function(){this.window.scrollTop()>this.videoHeight+this.videoWrap.offset().top?this.video.addClass("is-stuck"):this.video.removeClass("is-stuck")}}.init()}});; 1293(function ($) { 1294 $(document).on('fusion-builder-settings-render', function () { 1295 $('select[name="id"]').on('change', function () { 1296 let selectedTableID = $(this).val(); 1297 1298 if (selectedTableID) { 1299 $.ajax({ 1300 url: wdt_ajax_object.ajaxurl, 1301 type: 'POST', 1302 data: { 1303 action: 'get_table_type_by_id', 1304 table_id: selectedTableID 1305 }, 1306 success: function (response) { 1307 var tableType = JSON.parse(response).tableType; 1308 1309 if (tableType === 'woo_commerce') { 1310 $('select[name="table_view"]').closest('.fusion-builder-option').hide(); 1311 } else { 1312 $('select[name="table_view"]').closest('.fusion-builder-option').show(); 1313 } 1314 } 1315 }); 1316 } 1317 }); 1318 }); 1319 1320 $(document).on('change', '.wpdatatable select[name="id"]', function () { 1321 let selectedTableID = $(this).val(); 1322 1323 if (selectedTableID) { 1324 $.ajax({ 1325 url: wdt_ajax_object.ajaxurl, 1326 type: 'POST', 1327 data: { 1328 action: 'get_table_type_by_id', 1329 table_id: selectedTableID 1330 }, 1331 success: function (response) { 1332 var tableType = JSON.parse(response).tableType; 1333 1334 if (tableType === 'woo_commerce') { 1335 $('li[data-option-id="table_view"]').hide(); 1336 } else { 1337 $('li[data-option-id="table_view"]').hide(); 1338 } 1339 } 1340 }); 1341 } 1342 }); 1343 $(document).on('change', 'ul[data-element="wpdatatable"] input#id', function () { 1344 let selectedTableID = $(this).val(); 1345 1346 if (selectedTableID) { 1347 $.ajax({ 1348 url: wdt_ajax_object.ajaxurl, 1349 type: 'POST', 1350 data: { 1351 action: 'get_table_type_by_id', 1352 table_id: selectedTableID 1353 }, 1354 success: function (response) { 1355 var tableType = JSON.parse(response).tableType; 1356 1357 if (tableType === 'woo_commerce') { 1358 $('li[data-option-id="table_view"]').hide(); 1359 } else { 1360 $('li[data-option-id="table_view"]').hide(); 1361 } 1362 } 1363 }); 1364 } 1365 }); 1366})(jQuery);; 1367$( document ).ready( function () { 1368 var $temp = $( '<input>' ); 1369 var $url = $( location ).attr( 'href' ); 1370 1371 $( '.sd-content' ).append( '<div class="link-icon-text">Link Copied</div>' ); 1372 1373 $( document ).on( 'click', '.share-link_copy a', function( event ) { 1374 event.preventDefault(); 1375 1376 $( 'body' ).append( $temp ); 1377 $temp.val( $url ).select(); 1378 document.execCommand( 'copy' ); 1379 $temp.remove(); 1380 1381 $( '.link-icon-text' ).show(); 1382 1383 setTimeout( function() { 1384 $( '.link-icon-text' ).hide(); 1385 }, 3000 ); 1386 } ); 1387} ); 1388;
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.