b68a131bae
Port FastHTML's proc_htmx as onSwap(selector, initializeElement) in utils.js, built on htmx.onLoad: it runs an initializer once per matching element, on initial page load and inside every htmx-swapped fragment. Migrate search_select.js, range_slider.js, filter_bar.js and add_purchase.js to it, removing the hand-rolled DOMContentLoaded + htmx:afterSwap listeners and per-element guard flags. This also fixes a latent bug: both events passed the Event object as range_slider's "force" parameter, so every htmx swap force-re-initialized all sliders and stacked duplicate listeners. The collapse button's window.initRangeSliders() call was a no-op (handles are positioned in percentages, so hidden-init is safe) and is removed with the global. Add e2e/test_widgets_e2e.py covering the onSwap lifecycle (initial-load init, htmx-swap init, single-fire toggles) plus FilterSelect pills and the add-purchase type toggle. The synthetic page in test_search_select_e2e.py now loads htmx and search_select.js as a module, matching the new initialization path. https://claude.ai/code/session_01BKurBhE3Qj25p7Bfsg7EeK
47 lines
1.5 KiB
JavaScript
47 lines
1.5 KiB
JavaScript
import { getEl, disableElementsWhenTrue, onSwap } from "./utils.js";
|
|
|
|
const RELATED_PURCHASE_URL = "/tracker/purchase/related-purchase-by-game";
|
|
|
|
// The games field is now a SearchSelect widget (a <div>, not a <select>), so we
|
|
// react to its custom "search-select:change" event instead of syncing a select.
|
|
document.addEventListener("search-select:change", (event) => {
|
|
if (event.detail.name !== "games") return;
|
|
|
|
// (a) Auto-fill platform from the clicked option's data-platform.
|
|
const last = event.detail.last;
|
|
const platformId = last && last.data ? last.data.platform : "";
|
|
if (platformId) {
|
|
const platformEl = getEl("#id_platform");
|
|
if (platformEl) platformEl.value = platformId;
|
|
}
|
|
|
|
// (b) Refresh #id_related_purchase for the currently selected games.
|
|
const query = event.detail.values
|
|
.map((value) => "games=" + encodeURIComponent(value))
|
|
.join("&");
|
|
fetch(RELATED_PURCHASE_URL + "?" + query, { credentials: "same-origin" })
|
|
.then((response) => {
|
|
if (response.status === 204) return null;
|
|
return response.text();
|
|
})
|
|
.then((html) => {
|
|
if (html === null) return;
|
|
const target = getEl("#id_related_purchase");
|
|
if (target) target.outerHTML = html;
|
|
});
|
|
});
|
|
|
|
function setupElementHandlers() {
|
|
disableElementsWhenTrue("#id_type", "game", [
|
|
"#id_name",
|
|
"#id_related_purchase",
|
|
]);
|
|
}
|
|
|
|
onSwap("#id_type", (typeSelect) => {
|
|
setupElementHandlers();
|
|
typeSelect.addEventListener("change", () => {
|
|
setupElementHandlers();
|
|
});
|
|
});
|