1(function () { 2 // --------------------------- 3 // Compare bar: state helpers 4 // --------------------------- 5 const KEY = 'compareItems'; 6 const getItems = () => JSON.parse(localStorage.getItem(KEY) || '[]'); 7 const setItems = (items) => localStorage.setItem(KEY, JSON.stringify(items)); 8 9 // Elements (safe-optional) 10 const compareBar = document.getElementById('compare-bar'); 11 const compareItemsContainer = compareBar ? compareBar.querySelector('.compare-items') : null; 12 const compareCount = compareBar ? compareBar.querySelector('.compare-count') : null; 13 const compareLink = document.getElementById('compare-link'); 14 15 function rebuildCompareBar() { 16 const items = getItems(); 17 18 if (!compareBar) return; 19 20 if (items.length === 0) { 21 compareBar.classList.add('hidden'); 22 if (compareItemsContainer) compareItemsContainer.innerHTML = ''; 23 if (compareCount) compareCount.textContent = '0 items to compare'; 24 if (compareLink) compareLink.href = '/dog-food-search/dog-food-compare/'; 25 return; 26 } 27 28 compareBar.classList.remove('hidden'); 29 30 if (compareCount) { 31 compareCount.textContent = `${items.length} item${items.length > 1 ? 's' : ''} to compare`; 32 } 33 34 if (compareItemsContainer) { 35 compareItemsContainer.innerHTML = ''; 36 items.forEach(item => { 37 const el = document.createElement('div'); 38 el.className = 'compare-item'; 39 el.innerHTML = ` 40 ${item.thumb ? `<img src="${item.thumb}" alt="${item.title}">` : ''} 41 <span>${item.title.substring(0, 20)}</span> 42 <button data-id="${item.id}" class="remove-compare" aria-label="Remove from compare">x</button> 43 `; 44 compareItemsContainer.appendChild(el); 45 }); 46 } 47 48 if (compareLink) { 49 compareLink.href = 50 `/dog-food-search/dog-food-compare/?` + items.map((it, i) => `ids[${i}]=${encodeURIComponent(it.id)}`).join('&'); 51 } 52 } 53 54 function syncCheckboxesFromState() { 55 const saved = new Set(getItems().map(i => String(i.id))); 56 document.querySelectorAll('.dog-food-compare-checkbox').forEach(cb => { 57 const id = cb.dataset.postId || cb.getAttribute('data-post-id'); 58 cb.checked = saved.has(String(id)); 59 const card = cb.closest('.dogfood-card'); 60 if (card) card.classList.toggle('in-compare', cb.checked); 61 }); 62 } 63 64 function addItemFromCheckbox(cb) { 65 const postId = cb.dataset.postId || cb.getAttribute('data-post-id'); 66 let items = getItems(); 67 if (items.find(i => String(i.id) === String(postId))) return; // already in 68 69 if (items.length >= 4) { 70 alert('You can compare up to 4 items.'); 71 cb.checked = false; 72 return; 73 } 74 75 const card = cb.closest('.dogfood-card'); 76 const title = card?.dataset.title || `#${postId}`; 77 const thumb = card?.dataset.thumb || ''; 78 79 items.push({ id: String(postId), title, thumb }); 80 setItems(items); 81 card && card.classList.add('in-compare'); 82 rebuildCompareBar(); 83 } 84 85 function removeItem(id) { 86 let items = getItems().filter(i => String(i.id) !== String(id)); 87 setItems(items); 88 // Uncheck card if visible 89 const cb = document.querySelector(`.dog-food-compare-checkbox[data-post-id="${CSS.escape(String(id))}"]`); 90 if (cb) cb.checked = false; 91 const card = document.querySelector(`.dogfood-card[data-post-id="${CSS.escape(String(id))}"]`); 92 if (card) card.classList.remove('in-compare'); 93 rebuildCompareBar(); 94 } 95 96 // --------------------------- 97 // Delegated events 98 // --------------------------- 99 document.addEventListener('change', (e) => { 100 const cb = e.target.closest('.dog-food-compare-checkbox'); 101 if (!cb) return; 102 const postId = cb.dataset.postId || cb.getAttribute('data-post-id'); 103 if (cb.checked) { 104 addItemFromCheckbox(cb); 105 } else { 106 removeItem(postId); 107 } 108 }); 109 110 document.addEventListener('click', (e) => { 111 const removeBtn = e.target.closest('.remove-compare'); 112 if (removeBtn) { 113 removeItem(removeBtn.dataset.id); 114 return; 115 } 116 const clearBtn = e.target.closest('.clear-compare'); 117 if (clearBtn) { 118 setItems([]); 119 document.querySelectorAll('.dog-food-compare-checkbox').forEach(cb => (cb.checked = false)); 120 document.querySelectorAll('.dogfood-card.in-compare').forEach(c => c.classList.remove('in-compare')); 121 rebuildCompareBar(); 122 return; 123 } 124 }); 125 126 // --------------------------- 127 // FacetWP integration 128 // --------------------------- 129 130// --- Only auto-apply when the user interacts with the SEARCH facet --- 131// Adjust 'dog_food_search' in the selector if your facet name differs 132// Only auto-apply when the user interacts with the SEARCH facet. 133// Everything else still requires the "Apply Filters" button. 134(function ($) { 135 var allowAuto = false;
136 var prevVal = ''; 137 var t; 138 139 // Helpers 140 function triggerSearchRefresh() { 141 allowAuto = true; // allow auto-refresh for THIS cycle only 142 FWP.refresh(); // kick off a refresh now 143 } 144 145 // 1) Typing in the search box (debounced) 146 //$(document).on('input', '.facetwp-facet-dog_food_search .facetwp-search', function () { 147 // var val = this.value; 148 // if (val === prevVal) return; 149 // prevVal = val; 150 // clearTimeout(t); 151 // t = setTimeout(triggerSearchRefresh, 350); // debounce typing 152 //}); 153 154 // 2) Pressing Enter in the search box 155 $(document).on('keydown', '.facetwp-facet-dog_food_search .facetwp-search', function (e) { 156 if (e.key === 'Enter') { 157 e.preventDefault(); 158 clearTimeout(t); 159 triggerSearchRefresh(); 160 } 161 }); 162 163 // 3) Clicking the magnifier icon 164 $(document).on('click', '.facetwp-facet-dog_food_search .facetwp-icon', function (e) { 165 e.preventDefault(); 166 clearTimeout(t); 167 triggerSearchRefresh(); 168 }); 169 170 // Gate FacetWP's auto refresh: ON only for the search-triggered cycle 171 //$(document).on('facetwp-refresh', function () { 172 // FWP.auto_refresh = !!allowAuto; 173 // allowAuto = false; // reset so other facet clicks require Apply 174 // }); 175 176 // Apply button still works for all other facets 177 //$(document).on('click', '.facetwp-submit', function () { 178 // FWP.refresh(); 179 //}); 180 181})(jQuery); 182 183 184 185 186 // Re-sync compare UI after every FacetWP AJAX refresh 187 document.addEventListener('facetwp-loaded', function () { 188 syncCheckboxesFromState(); 189 rebuildCompareBar(); 190 }); 191 192 // Initial paint 193 document.addEventListener('DOMContentLoaded', function () { 194 syncCheckboxesFromState(); 195 rebuildCompareBar(); 196 }); 197 198 // --------------------------- 199 // Advanced Filters accordion 200 // --------------------------- 201 document.addEventListener('DOMContentLoaded', function () { 202 const toggle = document.querySelector('.accordion-toggle'); 203 const panel = document.querySelector('.accordion-panel'); 204 if (!toggle || !panel) return; 205 206 toggle.addEventListener('click', function () { 207 const expanded = this.getAttribute('aria-expanded') === 'true'; 208 this.setAttribute('aria-expanded', expanded ? 'false' : 'true'); 209 panel.classList.toggle('open'); 210 }); 211 }); 212 213 // --------------------------- 214 // Loading spinner 215 // --------------------------- 216 (function ($) { 217 $(document).on('facetwp-refresh', function () { 218 $('#facetwp-loading').show(); 219 }); 220 $(document).on('facetwp-loaded', function () { 221 $('#facetwp-loading').hide(); 222 }); 223 })(jQuery); 224})(); 225 226(function () { 227 const aside = document.getElementById('dogfood-filters'); 228 const toggle = document.querySelector('.filters-toggle'); 229 const closeBtn= document.querySelector('.filters-close'); 230 const overlay = document.querySelector('.filters-overlay'); 231 232 function openDrawer() { 233 if (!aside) return; 234 aside.classList.add('is-open'); 235 document.body.classList.add('body-no-scroll'); 236 if (overlay) overlay.hidden = false; 237 if (toggle) toggle.setAttribute('aria-expanded', 'true'); 238 aside.setAttribute('aria-hidden', 'false'); 239 } 240 241 function closeDrawer() { 242 if (!aside) return; 243 aside.classList.remove('is-open'); 244 document.body.classList.remove('body-no-scroll'); 245 if (overlay) overlay.hidden = true; 246 if (toggle) toggle.setAttribute('aria-expanded', 'false'); 247 aside.setAttribute('aria-hidden', 'true'); 248 } 249 250 toggle?.addEventListener('click', openDrawer); 251 closeBtn?.addEventListener('click', closeDrawer); 252 overlay?.addEventListener('click', closeDrawer); 253 254 // Close on ESC 255 document.addEventListener('keydown', (e) => { 256 if (e.key === 'Escape' && aside?.classList.contains('is-open')) { 257 closeDrawer(); 258 } 259 }); 260 261 // Close drawer after user applies filters 262 document.addEventListener('click', (e) => { 263 if (e.target.closest('.facetwp-submit')) { 264 closeDrawer(); 265 } 266 }); 267 268 // Optional: close after FacetWP finishes refreshing 269 document.addEventListener('facetwp-loaded', () => { 270 if (aside?.classList.contains('is-open')) closeDrawer(); 271 }); 272})(); 273 274// list view toggle 275(function () { 276 const STORAGE_KEY = 'wdj_view_mode'; // 'card' or 'list' 277 const resultsEl = document.querySelector('.dogfood-results'); 278 const gridBtn = document.querySelector('.view-btn[data-view="card"]'); 279 const listBtn = document.querySelector('.view-btn[data-view="list"]'); 280 281 if (!resultsEl || !gridBtn || !listBtn) return; 282 283 function apply(mode) { 284 // classes 285 resultsEl.classList.toggle('card-layout', mode === 'card'); 286 resultsEl.classList.toggle('list-layout', mode === 'list'); 287 // aria/active 288 gridBtn.classList.toggle('is-active', mode === 'card'); 289 gridBtn.setAttribute('aria-pressed', mode === 'card' ? 'true' : 'false'); 290 listBtn.classList.toggle('is-active', mode === 'list'); 291 listBtn.setAttribute('aria-pressed', mode === 'list' ? 'true' : 'false'); 292 } 293 294 function setMode(mode) { 295 localStorage.setItem(STORAGE_KEY, mode); 296 apply(mode); 297 } 298 299 // initial 300 const saved = localStorage.getItem(STORAGE_KEY) || 'card'; 301 apply(saved); 302 303 // clicks 304 gridBtn.addEventListener('click', () => setMode('card')); 305 listBtn.addEventListener('click', () => setMode('list')); 306 307 // Re-apply after FacetWP refresh 308 document.addEventListener('facetwp-loaded', () => apply(localStorage.getItem(STORAGE_KEY) || 'card')); 309})(); 310 311document.addEventListener("DOMContentLoaded", function() { 312 const url = new URL(window.location.href); 313 const term = url.searchParams.get('_dog_food_search'); 314 315 if (term) { 316 window.dataLayer = window.dataLayer || []; 317 dataLayer.push({ 318 event: "dog_food_search", 319 search_term: term 320 }); 321 } 322}); 323 324//Free JS Code Addition 325(function () { 326 function qs(sel, ctx) { return (ctx || document).querySelector(sel); } 327 328 var gate = qs('#df-results-gate'); // cards-area blur overlay 329 var panelWrap = qs('#df-results-gate-panel'); // viewport CTA overlay 330 if (!gate || !panelWrap) return; 331 332 var COOKIE_NAME = 'wdj_df_free'; 333 var FORM_ID = 14; 334 335 function getCookie(name) { 336 var parts = ('; ' + document.cookie).split('; ' + name + '='); 337 if (parts.length === 2) return parts.pop().split(';').shift(); 338 return ''; 339 } 340 341 function hasFreeCookie() { 342 return !!getCookie(COOKIE_NAME); 343 } 344 345 function isLoggedIn() { 346 // WP typically adds body.logged-in. This works for subscribers and any logged-in user. 347 return document.body.classList.contains('member-logged-in'); 348 } 349 350 function currentPaged() { 351 var url = new URL(window.location.href); 352 var p = parseInt(url.searchParams.get('_paged') || '1', 10); 353 return isNaN(p) ? 1 : Math.max(1, p); 354 } 355 356 function isUnknownGuest() { 357 // Free cookie OR logged in => allow pagination 358 if (hasFreeCookie()) return false;
359 if (isLoggedIn()) return false; 360 return true; 361 } 362 363 function setVisible(visible) { 364 if (visible) { 365 gate.classList.add('is-visible'); 366 panelWrap.classList.add('is-visible'); 367 gate.setAttribute('aria-hidden', 'false'); 368 panelWrap.setAttribute('aria-hidden', 'false'); 369 } else { 370 gate.classList.remove('is-visible'); 371 panelWrap.classList.remove('is-visible'); 372 gate.setAttribute('aria-hidden', 'true'); 373 panelWrap.setAttribute('aria-hidden', 'true'); 374 } 375 } 376 377 function setHard(hard) { 378 gate.classList.toggle('is-hard', !!hard); 379 panelWrap.classList.toggle('is-hard', !!hard); 380 381 // Close button hidden in hard mode 382 var closeBtn = qs('.df-results-gate__close', panelWrap); 383 if (closeBtn) closeBtn.style.display = hard ? 'none' : ''; 384 } 385 386 function showGate(hard) { 387 setVisible(true); 388 setHard(!!hard); 389 } 390 391 function hideGate(force) { 392 // force=true closes even if hard (used after cookie appears) 393 if (!force && panelWrap.classList.contains('is-hard')) return; 394 setVisible(false); 395 setHard(false); 396 } 397 398 function hideIfJustRegistered() { 399 // If GF shows confirmation and cookie exists, do not keep overlay open 400 var gfConfirm = qs('#gform_confirmation_wrapper_' + FORM_ID); 401 if (gfConfirm && hasFreeCookie()) { 402 hideGate(true); 403 } 404 } 405 406 // Close button (soft overlay only) 407 document.addEventListener('click', function (e) { 408 var btn = e.target && e.target.closest ? e.target.closest('.df-results-gate__close') : null; 409 if (!btn) return; 410 411 e.preventDefault(); 412 hideGate(false); 413 try { sessionStorage.setItem('df_gate_closed', '1'); } catch (err) {} 414 }); 415 416 // Intercept FacetWP pager clicks on page 1 for unknown guests 417 document.addEventListener('click', function (e) { 418 var a = e.target && e.target.closest ? e.target.closest('.facetwp-pager a') : null; 419 if (!a) return; 420 421 if (!isUnknownGuest()) return; 422 423 // Only block attempts to leave page 1 424 if (currentPaged() === 1) { 425 e.preventDefault(); 426 e.stopPropagation(); 427 showGate(false); 428 } 429 }, true); 430 431 document.addEventListener('click', function (e) { 432 var wrap = e.target && e.target.closest ? e.target.closest('.wdj-open-register') : null; 433 if (!wrap) return; 434 435 var link = e.target && e.target.closest ? e.target.closest('a') : null; 436 if (!link) return; 437 438 if (!isUnknownGuest()) return; 439 440 e.preventDefault(); 441 e.stopPropagation(); 442 443 showGate(false); 444 }, true); 445 446 // Halfway scroll trigger on page 1 (unknown guests only), once per session 447 var scrollTriggered = false; 448 449 function onScroll() { 450 if (scrollTriggered) return; 451 if (!isUnknownGuest()) return; 452 if (currentPaged() !== 1) return; 453 454 try { 455 if (sessionStorage.getItem('df_gate_closed') === '1') return; 456 if (sessionStorage.getItem('df_gate_shown') === '1') return; 457 } catch (err) {} 458 459 var doc = document.documentElement; 460 var scrollTop = window.pageYOffset || doc.scrollTop || 0; 461 var max = (doc.scrollHeight - window.innerHeight); 462 if (max <= 0) return; 463 464 if ((scrollTop / max) >= 0.5) { 465 scrollTriggered = true; 466 showGate(false); 467 try { sessionStorage.setItem('df_gate_shown', '1'); } catch (err) {} 468 } 469 } 470 471 window.addEventListener('scroll', onScroll, { passive: true }); 472 473 // Core enforcement: run on load, facet refresh, and back/forward navigation 474 function enforce() { 475 // If they have the cookie or are logged in, overlays should be gone no matter what 476 if (!isUnknownGuest()) { 477 hideGate(true); 478 return; 479 } 480 481 // Unknown guests: hard gate any page 2+ 482 if (currentPaged() >= 2) { 483 showGate(true); 484 return; 485 } 486 487 // Unknown guests on page 1: default hidden until scroll/click triggers 488 // (Do not auto-show) 489 hideIfJustRegistered(); 490 } 491 492 // FacetWP AJAX refresh hook 493 document.addEventListener('facetwp-loaded', function () { 494 enforce(); 495 hideIfJustRegistered(); 496 }); 497 498 // Back/forward changes 499 window.addEventListener('popstate', function () { 500 enforce(); 501 }); 502 503 // If cookie appears right after redirect/submit, hide overlay quickly 504 (function cookieWatch() { 505 var attempts = 0; 506 var t = setInterval(function () { 507 attempts++; 508 if (hasFreeCookie() || isLoggedIn()) { 509 hideGate(true); 510 clearInterval(t); 511 } 512 if (attempts >= 10) clearInterval(t); 513 }, 300); 514 })(); 515 516 // Initial run 517 enforce(); 518 hideIfJustRegistered(); 519 520})();
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.