Overview: Add a small client-side script that reads a tags query param (e.g. ?tags=Outlets,Showroom) and checks the matching input[type="checkbox"][name="tagsvalue"] filters in your store-locator form, triggers the search, shows a persistent pill UI, and resiliently re-applies tags if other scripts briefly uncheck them — while respecting real user interaction.
When to use
Use this on Shopify themes that include a store-locator form which exposes tag filters as checkboxes such as:
<input type="checkbox" name="tagsvalue" value="Shoes">
It’s useful when you want deep links (or marketing links) to pre-filter store results by tag.
Features (what the script does)
Parses
tags(alsotag) fromwindow.location.searchorlocation.hash.Matches tag names to checkboxes by:
exact
valuelabel text, nearest
<li>or container texttolerant fuzzy rules (case-insensitive, plural/singular, punctuation-insensitive, substring)
Aggressively tries to toggle the checkbox in ways frameworks accept:
clicks label, clicks checkbox, sets
.checked, dispatchesinput/change/blurevents
Retries and watches for late-injected markup (MutationObserver + interval).
After success, triggers the form Search button (or
form.submit()).Shows a persistent, dismissable pill summarizing applied and unmatched tags.
Monitors and re-applies if other scripts uncheck the boxes — but:
respects a user’s manual uncheck (won’t fight user),
limits reapply attempts per tag to avoid infinite loops,
stops automatically after a safe timeout.
Exposes
window.__store_locator_apply_tags()to re-run from the console.
Prerequisites & notes
Place the script after your store-locator form markup (best: include it just before
</body>intheme.liquid, or add as an asset and include it in the layout).The script assumes:
your form has
id="bh-sl-user-location"(default); changeFORM_IDif differentyour tag checkboxes are
input[type="checkbox"][name="tagsvalue"](default)the Search button is
id="bh-sl-submit"(default)
If your theme uses a different structure or custom checkbox UI (pseudo-elements, hidden inputs or external components), you may need to adjust the selectors or add a small adapter to click the visible UI element.
Installation (Shopify Admin)
Login to Shopify Admin > Online Store > Themes.
On your active theme, click Actions > Edit code.
Under Assets click Add a new asset → choose Create a blank file → name it
store-locator-checkbox-adapter.js.Paste the script (see below) into that asset and Save.
Open
layout/theme.liquid(or your storefront layout) and before</body>add:<script src="{{ 'store-locator-checkbox-adapter.js' | asset_url }}" defer></script>deferis okay; if you prefer to ensure it runs after everything, omitdeferand include it as the last script before</body>.
Save theme changes.
Visit your store-locator page and test.
Configuration (quick)
Open the JS asset and adjust these constants near the top if needed:
const FORM_ID = 'bh-sl-user-location';
const CHECKBOX_SELECTOR = 'input[type="checkbox"][name="tagsvalue"]';
const SEARCH_BUTTON_ID = 'bh-sl-submit';
const OBSERVER_TIMEOUT = 15000; // initial retry window (ms)
const RETRY_INTERVAL = 350; // retry interval (ms)
const MONITOR_DURATION = 20000; // how long to monitor after success
const MAX_REAPPLY_PER_TAG = 5; // avoid infinite reapply
If your selectors differ, update FORM_ID, CHECKBOX_SELECTOR, and SEARCH_BUTTON_ID.
Usage / Testing
Open a store-locator page with tags in the URL. Examples:
https://yourstore.com/pages/store-locator?tags=Outletshttps://yourstore.com/pages/store-locator?tags=Outlets,Showroomhttps://yourstore.com/pages/store-locator#/?tags=Outlets
Watch the console. The adapter logs attempts and results:
[sl-adapter] attempt 1/xx[sl-adapter] tags applied: …Reapply messages if other scripts uncheck the box.
Verify that:
The checkbox gets checked,
The Search button is clicked and results update,
The persistent pill appears at top-right with applied/unmatched tags.
You can manually re-run from the console:
__store_locator_apply_tags();
If a user manually unchecks a box, the adapter stops reapplying for that tag.
Troubleshooting & tips
1. Check script load order
If the script runs before the store-locator is inserted, it will retry and observe for up to OBSERVER_TIMEOUT. Make sure the script is included late (before </body>).
2. Checkbox never gets checked
Some themes replace checkboxes with custom components. Inspect the checkbox DOM (
right-click → Inspect → Copy → OuterHTML) to see whether the visible control is a sibling or a pseudo-element. If so, you may need to click the visible wrapper (e.g..custom-checkbox) instead — updatecheckCheckbox()to click that element.
3. External scripts undoing changes
Third-party plugins (Google Places or a store-locator plugin) sometimes run later and reset filters. The adapter monitors and re-applies, but if that plugin errors (see
Cannot read properties of null), fix the plugin or increaseMONITOR_DURATION/MAX_REAPPLY_PER_TAG. You can also add a smallsetTimeoutbefore triggering the search if needed.
4. Infinite loops
v4 includes caps (
MAX_REAPPLY_PER_TAG) and other guards. If you still see repeats, reduceMONITOR_DURATIONor lowerMAX_REAPPLY_PER_TAG. Also review console logs for the reason.
5. Accessibility
The adapter dispatches
inputandchangeevents. It also setsaria-checkedfor parity. It respects user actions (ifevent.isTrusted === true).
6. Persisting pill dismissal
If you want the pill dismissal to persist across pages, add
localStorageflag on dismiss and check it at startup inshowPill().
Full script (drop-in)
Place the following code in store-locator-checkbox-adapter.js and include the asset in your theme as described above.
<script>
/*!
* store-locator-checkbox-adapter-v4.js
* Purpose: Aggressively check <input type="checkbox" name="tagsvalue" value="..."> checkboxes,
* then monitor and re-apply them if other scripts uncheck them (but respect real user unchecks).
* This version includes robust guards to prevent infinite loops.
* Author: Rose Perl Technology
*/
(function () {
'use strict';
// Config
const FORM_ID = 'bh-sl-user-location';
const CHECKBOX_SELECTOR = 'input[type="checkbox"][name="tagsvalue"]';
const SEARCH_BUTTON_ID = 'bh-sl-submit';
const OBSERVER_TIMEOUT = 15000; // initial retry window (ms)
const RETRY_INTERVAL = 350; // retry interval (ms)
const MONITOR_DURATION = 20000; // monitor duration after success (ms)
const MAX_REAPPLY_PER_TAG = 5; // maximum reapply attempts per tag to avoid infinite loops
const ADAPTER_MARK = 'data-sl-adapter'; // marker attribute to identify adapter-applied boxes
const PILL_ID = 'store-locator-tags-pill-v4';
// --- Utilities ---
function parseTagsFromURL() {
const params = new URLSearchParams(window.location.search || '');
let raw = params.get('tags') || params.get('tag') || null;
if (!raw && window.location.hash) {
const h = window.location.hash;
const qIdx = h.indexOf('?');
if (qIdx !== -1) {
const hashQS = h.slice(qIdx + 1);
try {
const hp = new URLSearchParams(hashQS);
raw = raw || hp.get('tags') || hp.get('tag');
} catch (e) {}
}
}
if (!raw) return [];
raw = raw.replace(/\+/g, ' ');
try { raw = decodeURIComponent(raw); } catch (e) {}
return raw.split(/\s*[;,|]\s*/).map(t => t.trim()).filter(Boolean);
}
function normalize(s) {
return String(s || '').toLowerCase().replace(/\s+/g, ' ').trim();
}
function tagsMatch(normalBox, normalTag) {
if (!normalBox || !normalTag) return false;
if (normalBox === normalTag) return true;
if (normalBox.endsWith('s') && normalBox.slice(0, -1) === normalTag) return true;
if (normalTag.endsWith('s') && normalTag.slice(0, -1) === normalBox) return true;
if (normalBox.replace(/[^a-z0-9]/g, '') === normalTag.replace(/[^a-z0-9]/g, '')) return true;
if (normalBox.includes(normalTag) || normalTag.includes(normalBox)) return true;
return false;
}
function checkboxContexts(box) {
const contexts = [];
if (box.value) contexts.push(box.value);
if (box.id) {
const lab = document.querySelector(`label[for="${box.id}"]`);
if (lab && lab.innerText) contexts.push(lab.innerText);
}
const labAncestor = box.closest('label');
if (labAncestor && labAncestor.innerText) contexts.push(labAncestor.innerText);
const li = box.closest('li') || box.closest('.bh-sl-filters') || box.closest('.scasl-tag-list') || box.closest('ul') || box.closest('div');
if (li && li.innerText) contexts.push(li.innerText);
if (box.getAttribute('aria-label')) contexts.push(box.getAttribute('aria-label'));
if (box.title) contexts.push(box.title);
return Array.from(new Set(contexts)).map(normalize).filter(Boolean);
}
// Event dispatch helpers
function dispatchChangeEvents(el) {
try {
el.dispatchEvent(new Event('input', { bubbles: true }));
el.dispatchEvent(new Event('change', { bubbles: true }));
el.dispatchEvent(new Event('blur', { bubbles: true }));
el.dispatchEvent(new CustomEvent('input', { bubbles: true }));
} catch (e) {}
}
function clickElement(el) {
try {
el.focus && el.focus();
el.dispatchEvent(new MouseEvent('mousedown', { bubbles: true, cancelable: true }));
el.dispatchEvent(new MouseEvent('mouseup', { bubbles: true, cancelable: true }));
el.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
} catch (e) {}
}
// Robust checkbox toggler with adapter marking and timestamp
function checkCheckbox(box, canonicalTag) {
if (!box) return false;
if (box.disabled) return false;
// If already checked, ensure adapter mark exists and dispatch events
if (box.checked) {
markBox(box, canonicalTag);
dispatchChangeEvents(box);
return true;
}
// Try label clicks first (preferred for custom UIs)
try {
if (box.id) {
const lab = document.querySelector(`label[for="${box.id}"]`);
if (lab) {
clickElement(lab);
if (box.checked) { markBox(box, canonicalTag); dispatchChangeEvents(box); return true; }
}
}
const labAncestor = box.closest('label');
if (labAncestor) {
clickElement(labAncestor);
if (box.checked) { markBox(box, canonicalTag); dispatchChangeEvents(box); return true; }
}
} catch (e) {}
// Click the box itself and force checked + dispatch events
try {
clickElement(box);
if (!box.checked) box.checked = true;
try { box.setAttribute('aria-checked', 'true'); } catch (e) {}
markBox(box, canonicalTag);
dispatchChangeEvents(box);
return !!box.checked;
} catch (e) {
try {
box.checked = true;
try { box.setAttribute('aria-checked', 'true'); } catch (e) {}
markBox(box, canonicalTag);
dispatchChangeEvents(box);
return true;
} catch (err) {
console.warn('[sl-adapter] Failed to check checkbox', err, box);
return false;
}
}
}
// Mark the box as adapter-controlled and record tag/time
function markBox(box, canonicalTag) {
try {
box.setAttribute(ADAPTER_MARK, '1');
if (canonicalTag) box.setAttribute('data-sl-adapter-tag', canonicalTag);
box.setAttribute('data-sl-adapter-checked-at', Date.now().toString());
// clear any manual flag on a successful adapter check
try { box.removeAttribute('data-sl-manual-unchecked'); } catch (e) {}
} catch (e) {}
}
// Persistent pill UI
function showPill(applied, unmatched) {
try {
let container = document.getElementById(PILL_ID);
const pillStyles = `
position: fixed;
top: 14px;
right: 14px;
z-index: 99999;
font-family: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial;
font-size: 13px;
color: #fff;
background: rgba(0,0,0,0.6);
padding: 8px 12px;
border-radius: 10px;
box-shadow: 0 8px 20px rgba(0,0,0,0.12);
display: flex;
gap: 12px;
align-items: center;
min-width: 160px;
max-width: 520px;
word-break: break-word;
`;
const listStyles = 'display:flex; gap:8px; flex-wrap:wrap; align-items:center;';
if (!container) {
container = document.createElement('div');
container.id = PILL_ID;
container.setAttribute('role', 'status');
container.setAttribute('aria-live', 'polite');
container.style.cssText = pillStyles;
const list = document.createElement('div');
list.className = 'sl-tag-list';
list.style.cssText = listStyles;
container.appendChild(list);
const closeBtn = document.createElement('button');
closeBtn.type = 'button';
closeBtn.className = 'sl-pill-close';
closeBtn.setAttribute('aria-label', 'Dismiss tag pill');
closeBtn.title = 'Dismiss';
closeBtn.innerHTML = '✕';
closeBtn.style.cssText = `
margin-left: 8px;
background: transparent;
border: none;
color: #ffffff;
font-size: 16px;
line-height: 1;
cursor: pointer;
padding: 2px 6px;
border-radius: 6px;
`;
closeBtn.addEventListener('click', function (e) {
e.stopPropagation();
try { container.remove(); } catch (err) {}
cleanupMonitor(); // stop monitor when user dismisses
});
container.appendChild(closeBtn);
document.body.appendChild(container);
}
const list = container.querySelector('.sl-tag-list');
list.innerHTML = '';
if (applied && applied.length) {
applied.forEach(t => {
const span = document.createElement('span');
span.textContent = t;
span.style.cssText = 'background: #A4118D; padding: 4px 8px; border-radius: 999px; color:#fff; font-weight:500;';
list.appendChild(span);
});
} else {
const none = document.createElement('span');
none.textContent = 'No tags applied';
none.style.cssText = 'opacity:0.9; padding:4px 8px; border-radius:6px';
list.appendChild(none);
}
if (unmatched && unmatched.length) {
const sep = document.createElement('span');
sep.textContent = '•';
sep.style.cssText = 'opacity:0.85; margin:0 4px';
list.appendChild(sep);
unmatched.forEach(t => {
const span = document.createElement('span');
span.textContent = t;
span.style.cssText = 'background: rgba(255,80,80,0.18); padding: 4px 8px; border-radius: 999px; color:#fff; opacity:0.95;';
list.appendChild(span);
});
}
} catch (e) { console.warn('[sl-adapter] showPill error', e); }
}
// applyTagsOnce returns applied/unmatched plus matchedMap of originalTag -> [boxes]
function applyTagsOnce(tags) {
const result = { applied: [], unmatched: [], matchedMap: {} };
if (!tags || !tags.length) return result;
const form = document.getElementById(FORM_ID);
const root = form || document;
const boxes = Array.from(root.querySelectorAll(CHECKBOX_SELECTOR));
if (!boxes.length) {
result.unmatched = tags.slice();
return result;
}
const normalizedTags = tags.map(normalize);
normalizedTags.forEach((ntag, idx) => {
const original = tags[idx];
let matched = false;
result.matchedMap[original] = [];
// First pass: direct value match
for (const box of boxes) {
const val = normalize(box.value || '');
if (tagsMatch(val, ntag)) {
const ok = checkCheckbox(box, ntag);
if (ok) { matched = true; result.matchedMap[original].push(box); }
}
}
if (matched) { result.applied.push(original); return; }
// Second pass: label/parent contexts
for (const box of boxes) {
const contexts = checkboxContexts(box);
for (const ctx of contexts) {
if (tagsMatch(ctx, ntag)) {
const ok = checkCheckbox(box, ntag);
if (ok) { matched = true; result.matchedMap[original].push(box); break; }
}
}
if (matched) break;
}
if (matched) { result.applied.push(original); return; }
// Third pass: substring/fuzzy
for (const box of boxes) {
const val = normalize(box.value || '');
if (val.includes(ntag) || ntag.includes(val)) {
const ok = checkCheckbox(box, ntag);
if (ok) { matched = true; result.matchedMap[original].push(box); }
}
}
if (matched) result.applied.push(original);
else result.unmatched.push(original);
});
return result;
}
// Trigger the search action safely (small delay)
function triggerSearchAfterDelay() {
try {
const form = document.getElementById(FORM_ID);
const btn = document.getElementById(SEARCH_BUTTON_ID) || (form && form.querySelector('button[type="submit"], input[type="submit"]'));
if (btn) {
setTimeout(() => {
try { btn.click(); } catch (e) { try { form && form.submit(); } catch (err) {} }
}, 120);
} else if (form) {
setTimeout(() => { try { form.submit(); } catch (e) {} }, 120);
}
} catch (e) { console.warn('[sl-adapter] triggerSearch error', e); }
}
// --- Monitoring / reapply logic with safety guards ---
let monitorObserver = null;
let monitorInterval = null;
let reapplyCounts = {}; // { tagCanonical: number }
const manualUncheck = new Set(); // normalized contexts the user manually unchecked
let monitorActive = false;
function cleanupMonitor() {
try { if (monitorObserver) { monitorObserver.disconnect(); monitorObserver = null; } } catch (e) {}
try { if (monitorInterval) { clearInterval(monitorInterval); monitorInterval = null; } } catch (e) {}
try { document.removeEventListener('change', monitorChangeHandler, true); } catch (e) {}
reapplyCounts = {};
manualUncheck.clear();
monitorActive = false;
}
// Detect user manual change (trusted events); mark contexts to avoid reapply
function monitorChangeHandler(e) {
try {
const tgt = e.target;
if (!tgt || !tgt.matches || !tgt.matches(CHECKBOX_SELECTOR)) return;
if (e.isTrusted) {
// record normalized value and contexts as manual
const val = normalize(tgt.value || '');
manualUncheck.add(val);
const ctxs = checkboxContexts(tgt);
ctxs.forEach(c => manualUncheck.add(c));
// mark element specifically too
try { tgt.setAttribute('data-sl-manual-unchecked', '1'); } catch (err) {}
}
} catch (e) {}
}
// Monitor and reapply logic, but limited per-tag, and skip tags user manually changed
function monitorAndReapply(matchedMap, appliedTags, durationMs = MONITOR_DURATION) {
if (monitorActive) return; // already monitoring
monitorActive = true;
reapplyCounts = {};
appliedTags.forEach(t => reapplyCounts[normalize(t)] = 0);
// install change listener to detect user interactions
document.addEventListener('change', monitorChangeHandler, true);
// MutationObserver for 'checked' attribute changes
monitorObserver = new MutationObserver((records) => {
try {
for (const rec of records) {
const node = rec.target;
if (!node || !node.matches || !node.matches(CHECKBOX_SELECTOR)) continue;
// We only care when a box becomes unchecked
if (node.checked) continue;
// Was it previously adapter-checked?
if (!node.hasAttribute(ADAPTER_MARK)) continue;
const canonicalTag = normalize(node.getAttribute('data-sl-adapter-tag') || node.value || '');
// Skip if user manually unchecked
if (manualUncheck.has(canonicalTag) || node.hasAttribute('data-sl-manual-unchecked')) continue;
// Reapply limited times
if ((reapplyCounts[canonicalTag] || 0) >= MAX_REAPPLY_PER_TAG) {
console.info('[sl-adapter] max reapply reached for tag', canonicalTag);
continue;
}
// find candidate boxes for this tag and try re-checking
const candidates = Array.from(document.querySelectorAll(CHECKBOX_SELECTOR)).filter(b => {
const val = normalize(b.value || '');
return tagsMatch(val, canonicalTag) || checkboxContexts(b).some(ctx => tagsMatch(ctx, canonicalTag));
});
for (const cand of candidates) {
// skip if user manually changed this candidate
if (manualUncheck.has(normalize(cand.value || '')) || cand.hasAttribute('data-sl-manual-unchecked')) continue;
const ok = checkCheckbox(cand, canonicalTag);
if (ok) {
reapplyCounts[canonicalTag] = (reapplyCounts[canonicalTag] || 0) + 1;
console.info('[sl-adapter] Re-applied tag after external reset:', canonicalTag, 'count:', reapplyCounts[canonicalTag]);
break; // reapply to first success
}
}
}
} catch (e) { console.warn('[sl-adapter] monitor observer error', e); }
});
monitorObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['checked'] });
// Periodic reinforcement (in case attributes change without mutation records)
monitorInterval = setInterval(() => {
try {
appliedTags.forEach(t => {
const canonical = normalize(t);
if (manualUncheck.has(canonical)) return;
if ((reapplyCounts[canonical] || 0) >= MAX_REAPPLY_PER_TAG) return;
// find candidate boxes for this canonical tag
const candidates = Array.from(document.querySelectorAll(CHECKBOX_SELECTOR)).filter(b => {
const val = normalize(b.value || '');
return tagsMatch(val, canonical) || checkboxContexts(b).some(ctx => tagsMatch(ctx, canonical));
});
for (const cand of candidates) {
if (!document.contains(cand)) continue;
if (cand.checked) continue;
if (manualUncheck.has(normalize(cand.value || '')) || cand.hasAttribute('data-sl-manual-unchecked')) continue;
const ok = checkCheckbox(cand, canonical);
if (ok) {
reapplyCounts[canonical] = (reapplyCounts[canonical] || 0) + 1;
console.info('[sl-adapter] Periodically re-applied tag:', canonical, 'count:', reapplyCounts[canonical]);
break;
}
}
});
} catch (e) { console.warn('[sl-adapter] monitorInterval error', e); }
}, 900);
// Stop monitoring after durationMs
setTimeout(() => {
cleanupMonitor();
console.info('[sl-adapter] monitor expired, stopped reapply attempts.');
}, durationMs);
}
// Apply with retries and avoid re-entrant attempts
function applyWithRetries(tags) {
let attempts = 0;
const maxAttempts = Math.ceil(OBSERVER_TIMEOUT / RETRY_INTERVAL);
let isAttempting = false;
let localObserver = null;
let localInterval = null;
// cleanup local watchers
function localCleanup() {
try { if (localInterval) { clearInterval(localInterval); localInterval = null; } } catch (e) {}
try { if (localObserver) { localObserver.disconnect(); localObserver = null; } } catch (e) {}
}
function attempt() {
if (isAttempting) return false; // avoid re-entrancy
isAttempting = true;
attempts++;
console.info(`[sl-adapter] attempt ${attempts}/${maxAttempts}`);
const res = applyTagsOnce(tags);
showPill(res.applied, res.unmatched);
if (res.applied && res.applied.length) {
console.info('[sl-adapter] tags applied:', res);
triggerSearchAfterDelay();
// stop any global monitor and restart fresh for this mapping
cleanupMonitor();
monitorAndReapply(res.matchedMap, res.applied, MONITOR_DURATION);
isAttempting = false;
localCleanup();
return true;
}
isAttempting = false;
if (attempts >= maxAttempts) {
console.info('[sl-adapter] max attempts reached. Final result:', res);
localCleanup();
return false;
}
return false;
}
// initial synchronous attempt
if (attempt()) return;
// set up local observer & interval to retry attempts during initial window
localObserver = new MutationObserver(() => {
if (attempt()) {
try { localObserver.disconnect(); } catch (e) {}
}
});
localObserver.observe(document.body, { childList: true, subtree: true });
localInterval = setInterval(() => {
if (attempt()) {
try { clearInterval(localInterval); } catch (e) {}
try { localObserver && localObserver.disconnect(); } catch (e) {}
}
}, RETRY_INTERVAL);
// ensure stop after OBSERVER_TIMEOUT
setTimeout(() => {
try { clearInterval(localInterval); } catch (e) {}
try { localObserver && localObserver.disconnect(); } catch (e) {}
// final attempt for UX
const final = applyTagsOnce(tags);
showPill(final.applied, final.unmatched);
console.info('[sl-adapter] final attempt after timeout:', final);
}, OBSERVER_TIMEOUT + 100);
}
// Expose public helper
window.__store_locator_apply_tags = function() {
const tags = parseTagsFromURL();
if (!tags.length) { console.log('[sl-adapter] no tags in URL'); return; }
cleanupMonitor(); // stop any previous monitor
applyWithRetries(tags);
};
// Entrypoint
function init() {
const tags = parseTagsFromURL();
if (!tags.length) {
console.log('[sl-adapter] no tags in URL to apply.');
return;
}
cleanupMonitor();
applyWithRetries(tags);
// Re-apply on SPA navigation
window.addEventListener('popstate', function() {
const newTags = parseTagsFromURL();
applyWithRetries(newTags);
});
}
if (document.readyState === 'complete' || document.readyState === 'interactive') {
setTimeout(init, 20);
} else {
document.addEventListener('DOMContentLoaded', init);
}
})();
</script>
Troubleshooting checklist
Script didn’t run? Confirm it is included in the theme and the file URL is correct.
Checkbox never toggled? Inspect the DOM
OuterHTMLof the checkbox — if it’s a fake UI, adapt the script to click the wrapper or invoke theme JS.Search fires but results aren’t filtered? Confirm the store-locator expects checkboxes to be checked (not separate query params or JS-only filters). If it uses a JS API (e.g.,
StoreLocator.search({ tags: [...] })), call that function directly from the script.Other scripts uncheck repeatedly? Increase
MONITOR_DURATION, but also inspect which plugin is resetting — solving at the source (plugin order) is preferred.Console errors from Google Places plugin? Those can break plugin logic and produce resets — fix plugin errors separately.
