Re-read element attributes at click time, not from closed-over bind values
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m39s
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 3m39s
All click handlers (bind-event, bindBoostLink, bindClientRouteClick) now re-read href/verb-info from the DOM element when the click fires, instead of using values captured at bind time. This ensures correct behavior when DOM is replaced or attributes are morphed after binding. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@
|
|||||||
// =========================================================================
|
// =========================================================================
|
||||||
|
|
||||||
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
|
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
|
||||||
var SX_VERSION = "2026-03-07T01:41:53Z";
|
var SX_VERSION = "2026-03-07T02:03:55Z";
|
||||||
|
|
||||||
function isNil(x) { return x === NIL || x === null || x === undefined; }
|
function isNil(x) { return x === NIL || x === null || x === undefined; }
|
||||||
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
||||||
@@ -1984,13 +1984,13 @@ return postSwap(target); });
|
|||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
return (isSxTruthy(shouldFire) ? ((isSxTruthy(sxOr((eventName == "submit"), (isSxTruthy((eventName == "click")) && domHasAttr(el, "href")))) ? preventDefault_(e) : NIL), (function() {
|
return (isSxTruthy(shouldFire) ? ((isSxTruthy(sxOr((eventName == "submit"), (isSxTruthy((eventName == "click")) && domHasAttr(el, "href")))) ? preventDefault_(e) : NIL), (function() {
|
||||||
var isGetLink = (isSxTruthy((eventName == "click")) && isSxTruthy((get(verbInfo, "method") == "GET")) && isSxTruthy(domHasAttr(el, "href")) && !isSxTruthy(get(mods, "delay")));
|
var liveInfo = sxOr(getVerbInfo(el), verbInfo);
|
||||||
|
var isGetLink = (isSxTruthy((eventName == "click")) && isSxTruthy((get(liveInfo, "method") == "GET")) && isSxTruthy(domHasAttr(el, "href")) && !isSxTruthy(get(mods, "delay")));
|
||||||
var clientRouted = false;
|
var clientRouted = false;
|
||||||
if (isSxTruthy(isGetLink)) {
|
if (isSxTruthy(isGetLink)) {
|
||||||
logInfo((String("sx:route trying ") + String(get(verbInfo, "url"))));
|
clientRouted = tryClientRoute(urlPathname(get(liveInfo, "url")), domGetAttr(el, "sx-target"));
|
||||||
clientRouted = tryClientRoute(urlPathname(get(verbInfo, "url")), domGetAttr(el, "sx-target"));
|
|
||||||
}
|
}
|
||||||
return (isSxTruthy(clientRouted) ? (browserPushState(get(verbInfo, "url")), browserScrollTo(0, 0)) : ((isSxTruthy(isGetLink) ? logInfo((String("sx:route server fetch ") + String(get(verbInfo, "url")))) : NIL), (isSxTruthy(get(mods, "delay")) ? (clearTimeout_(timer), (timer = setTimeout_(function() { return executeRequest(el, verbInfo, NIL); }, get(mods, "delay")))) : executeRequest(el, verbInfo, NIL))));
|
return (isSxTruthy(clientRouted) ? (browserPushState(get(liveInfo, "url")), browserScrollTo(0, 0)) : ((isSxTruthy(isGetLink) ? logInfo((String("sx:route server fetch ") + String(get(liveInfo, "url")))) : NIL), (isSxTruthy(get(mods, "delay")) ? (clearTimeout_(timer), (timer = setTimeout_(function() { return executeRequest(el, NIL, NIL); }, get(mods, "delay")))) : executeRequest(el, NIL, NIL))));
|
||||||
})()) : NIL);
|
})()) : NIL);
|
||||||
})(); }, (isSxTruthy(get(mods, "once")) ? {["once"]: true} : NIL)) : NIL);
|
})(); }, (isSxTruthy(get(mods, "once")) ? {["once"]: true} : NIL)) : NIL);
|
||||||
})(); };
|
})(); };
|
||||||
@@ -3449,11 +3449,13 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
|
|
||||||
// --- Boost bindings ---
|
// --- Boost bindings ---
|
||||||
|
|
||||||
function bindBoostLink(el, href) {
|
function bindBoostLink(el, _href) {
|
||||||
el.addEventListener("click", function(e) {
|
el.addEventListener("click", function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
executeRequest(el, { method: "GET", url: href }).then(function() {
|
// Re-read href from element at click time (not closed-over value)
|
||||||
try { history.pushState({ sxUrl: href, scrollY: window.scrollY }, "", href); } catch (err) {}
|
var liveHref = el.getAttribute("href") || _href;
|
||||||
|
executeRequest(el, { method: "GET", url: liveHref }).then(function() {
|
||||||
|
try { history.pushState({ sxUrl: liveHref, scrollY: window.scrollY }, "", liveHref); } catch (err) {}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -3469,10 +3471,12 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
|
|
||||||
// --- Client-side route bindings ---
|
// --- Client-side route bindings ---
|
||||||
|
|
||||||
function bindClientRouteClick(link, href, fallbackFn) {
|
function bindClientRouteClick(link, _href, fallbackFn) {
|
||||||
link.addEventListener("click", function(e) {
|
link.addEventListener("click", function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var pathname = urlPathname(href);
|
// Re-read href from element at click time (not closed-over value)
|
||||||
|
var liveHref = link.getAttribute("href") || _href;
|
||||||
|
var pathname = urlPathname(liveHref);
|
||||||
// Find target selector: sx-boost ancestor, explicit sx-target, or #main-panel
|
// Find target selector: sx-boost ancestor, explicit sx-target, or #main-panel
|
||||||
var boostEl = link.closest("[sx-boost]");
|
var boostEl = link.closest("[sx-boost]");
|
||||||
var targetSel = boostEl ? boostEl.getAttribute("sx-boost") : null;
|
var targetSel = boostEl ? boostEl.getAttribute("sx-boost") : null;
|
||||||
@@ -3480,12 +3484,12 @@ callExpr.push(dictGet(kwargs, k)); } }
|
|||||||
targetSel = link.getAttribute("sx-target") || "#main-panel";
|
targetSel = link.getAttribute("sx-target") || "#main-panel";
|
||||||
}
|
}
|
||||||
if (tryClientRoute(pathname, targetSel)) {
|
if (tryClientRoute(pathname, targetSel)) {
|
||||||
try { history.pushState({ sxUrl: href, scrollY: window.scrollY }, "", href); } catch (err) {}
|
try { history.pushState({ sxUrl: liveHref, scrollY: window.scrollY }, "", liveHref); } catch (err) {}
|
||||||
if (typeof window !== "undefined") window.scrollTo(0, 0);
|
if (typeof window !== "undefined") window.scrollTo(0, 0);
|
||||||
} else {
|
} else {
|
||||||
logInfo("sx:route server " + pathname);
|
logInfo("sx:route server " + pathname);
|
||||||
executeRequest(link, { method: "GET", url: href }).then(function() {
|
executeRequest(link, { method: "GET", url: liveHref }).then(function() {
|
||||||
try { history.pushState({ sxUrl: href, scrollY: window.scrollY }, "", href); } catch (err) {}
|
try { history.pushState({ sxUrl: liveHref, scrollY: window.scrollY }, "", liveHref); } catch (err) {}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2720,11 +2720,13 @@ PLATFORM_ORCHESTRATION_JS = """
|
|||||||
|
|
||||||
// --- Boost bindings ---
|
// --- Boost bindings ---
|
||||||
|
|
||||||
function bindBoostLink(el, href) {
|
function bindBoostLink(el, _href) {
|
||||||
el.addEventListener("click", function(e) {
|
el.addEventListener("click", function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
executeRequest(el, { method: "GET", url: href }).then(function() {
|
// Re-read href from element at click time (not closed-over value)
|
||||||
try { history.pushState({ sxUrl: href, scrollY: window.scrollY }, "", href); } catch (err) {}
|
var liveHref = el.getAttribute("href") || _href;
|
||||||
|
executeRequest(el, { method: "GET", url: liveHref }).then(function() {
|
||||||
|
try { history.pushState({ sxUrl: liveHref, scrollY: window.scrollY }, "", liveHref); } catch (err) {}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -2740,10 +2742,12 @@ PLATFORM_ORCHESTRATION_JS = """
|
|||||||
|
|
||||||
// --- Client-side route bindings ---
|
// --- Client-side route bindings ---
|
||||||
|
|
||||||
function bindClientRouteClick(link, href, fallbackFn) {
|
function bindClientRouteClick(link, _href, fallbackFn) {
|
||||||
link.addEventListener("click", function(e) {
|
link.addEventListener("click", function(e) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var pathname = urlPathname(href);
|
// Re-read href from element at click time (not closed-over value)
|
||||||
|
var liveHref = link.getAttribute("href") || _href;
|
||||||
|
var pathname = urlPathname(liveHref);
|
||||||
// Find target selector: sx-boost ancestor, explicit sx-target, or #main-panel
|
// Find target selector: sx-boost ancestor, explicit sx-target, or #main-panel
|
||||||
var boostEl = link.closest("[sx-boost]");
|
var boostEl = link.closest("[sx-boost]");
|
||||||
var targetSel = boostEl ? boostEl.getAttribute("sx-boost") : null;
|
var targetSel = boostEl ? boostEl.getAttribute("sx-boost") : null;
|
||||||
@@ -2751,12 +2755,12 @@ PLATFORM_ORCHESTRATION_JS = """
|
|||||||
targetSel = link.getAttribute("sx-target") || "#main-panel";
|
targetSel = link.getAttribute("sx-target") || "#main-panel";
|
||||||
}
|
}
|
||||||
if (tryClientRoute(pathname, targetSel)) {
|
if (tryClientRoute(pathname, targetSel)) {
|
||||||
try { history.pushState({ sxUrl: href, scrollY: window.scrollY }, "", href); } catch (err) {}
|
try { history.pushState({ sxUrl: liveHref, scrollY: window.scrollY }, "", liveHref); } catch (err) {}
|
||||||
if (typeof window !== "undefined") window.scrollTo(0, 0);
|
if (typeof window !== "undefined") window.scrollTo(0, 0);
|
||||||
} else {
|
} else {
|
||||||
logInfo("sx:route server " + pathname);
|
logInfo("sx:route server " + pathname);
|
||||||
executeRequest(link, { method: "GET", url: href }).then(function() {
|
executeRequest(link, { method: "GET", url: liveHref }).then(function() {
|
||||||
try { history.pushState({ sxUrl: href, scrollY: window.scrollY }, "", href); } catch (err) {}
|
try { history.pushState({ sxUrl: liveHref, scrollY: window.scrollY }, "", liveHref); } catch (err) {}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -388,33 +388,33 @@
|
|||||||
(dom-has-attr? el "href")))
|
(dom-has-attr? el "href")))
|
||||||
(prevent-default e))
|
(prevent-default e))
|
||||||
|
|
||||||
;; For GET clicks on links, try client-side routing first
|
;; Re-read verb info from element at click time (not closed-over)
|
||||||
(let ((is-get-link (and (= event-name "click")
|
(let ((live-info (or (get-verb-info el) verbInfo))
|
||||||
(= (get verbInfo "method") "GET")
|
(is-get-link (and (= event-name "click")
|
||||||
|
(= (get live-info "method") "GET")
|
||||||
(dom-has-attr? el "href")
|
(dom-has-attr? el "href")
|
||||||
(not (get mods "delay"))))
|
(not (get mods "delay"))))
|
||||||
(client-routed false))
|
(client-routed false))
|
||||||
(when is-get-link
|
(when is-get-link
|
||||||
(log-info (str "sx:route trying " (get verbInfo "url")))
|
|
||||||
(set! client-routed
|
(set! client-routed
|
||||||
(try-client-route
|
(try-client-route
|
||||||
(url-pathname (get verbInfo "url"))
|
(url-pathname (get live-info "url"))
|
||||||
(dom-get-attr el "sx-target"))))
|
(dom-get-attr el "sx-target"))))
|
||||||
(if client-routed
|
(if client-routed
|
||||||
(do
|
(do
|
||||||
(browser-push-state (get verbInfo "url"))
|
(browser-push-state (get live-info "url"))
|
||||||
(browser-scroll-to 0 0))
|
(browser-scroll-to 0 0))
|
||||||
(do
|
(do
|
||||||
(when is-get-link
|
(when is-get-link
|
||||||
(log-info (str "sx:route server fetch " (get verbInfo "url"))))
|
(log-info (str "sx:route server fetch " (get live-info "url"))))
|
||||||
(if (get mods "delay")
|
(if (get mods "delay")
|
||||||
(do
|
(do
|
||||||
(clear-timeout timer)
|
(clear-timeout timer)
|
||||||
(set! timer
|
(set! timer
|
||||||
(set-timeout
|
(set-timeout
|
||||||
(fn () (execute-request el verbInfo nil))
|
(fn () (execute-request el nil nil))
|
||||||
(get mods "delay"))))
|
(get mods "delay"))))
|
||||||
(execute-request el verbInfo nil))))))))
|
(execute-request el nil nil))))))))
|
||||||
(if (get mods "once") (dict "once" true) nil))))))
|
(if (get mods "once") (dict "once" true) nil))))))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user