account page: can now reorder entries under shops sections by drag and drop

This commit is contained in:
2026-01-26 13:51:40 -07:00
parent a2d8993e0e
commit a50c0ef18d
14 changed files with 682 additions and 88 deletions
+172
View File
@@ -0,0 +1,172 @@
(function(){
/**
* @type {object}
*/
const htmx = window.htmx;
htmx.defineExtension("drag", {
init: (node) => {
// ideally it should only start listening to elements with hx-ext="drag"
// I assume there is a better way I can bind than this?
document.addEventListener("dragstart", DragStart);
document.addEventListener("dragover", DragOver);
document.addEventListener("dragleave", DragLeave);
document.addEventListener("dragend", DragEnd);
document.addEventListener("drop", Drop);
}
});
/**
* the most recent element a drag event started on
* @type {Element|null}
*/
let dragging = null;
/**
* Get the parent element which matches the selector
* @param {EventTarget|null} target
* @param {string} selector
* @returns {Element | null}
*/
function GetTarget(target, selector) {
if (target == null) return null;
if (!(target instanceof Element)) return null;
target = target.closest(`[${selector}],[data-${selector}]`);
if (target == null) return null;
if (!(target instanceof Element)) return null;
return target;
}
/**
* Get the attribute allowing `data-` fallback
* @param {Element | null} target
* @param {string} name
* @returns {string | null}
*/
function GetAttribute(target, name) {
if (target === null) return null;
return target.getAttribute(name) || target.getAttribute("data-"+name);
}
/**
* @param {DragEvent} ev
*/
function DragStart(ev) {
if (!ev.dataTransfer) return;
const target = GetTarget(ev.target, "hx-drag");
if (!target) return null;
const data = GetAttribute(target, "hx-drag");
if (data === null) return;
target.classList.add("hx-drag");
dragging = target;
}
/**
* @param {DragEvent} ev
*/
function DragOver(ev) {
if (!dragging) return;
const target = GetTarget(ev.target, "hx-drop");
if (!target) return;
ev.preventDefault();
target.classList.add("hx-drag-over");
}
/**
* @param {DragEvent} ev
*/
function DragLeave(ev) {
if (!dragging) return;
const target = GetTarget(ev.target, "hx-drop");
if (!target) return;
target.classList.remove("hx-drag-over");
}
/**
* @param {DragEvent} ev
*/
function DragEnd() {
if (!dragging) return;
dragging.classList.remove("hx-drag");
}
/**
* @param {DragEvent} ev
*/
async function Drop(ev) {
if (!dragging) return;
const drag = dragging;
const drop = GetTarget(ev.target, "hx-drop");
if (!drop) return;
drop.classList.add("hx-drop");
drop.classList.remove("hx-drag-over");
const dragAttr = GetAttribute(drag, "hx-drag");
const dropAttr = GetAttribute(drop, "hx-drop");
console.log('dragAttr:', dragAttr);
console.log('dropAttr:', dropAttr);
const dragVals = JSON.parse(GetAttribute(drag, "hx-drag") || "{}");
const dropVals = JSON.parse(GetAttribute(drop, "hx-drop") || "{}");
const precedence = GetAttribute(drag, "hx-drag-sync") || GetAttribute(drag, "hx-drop-sync");
const dragFirst = precedence === "drag";
const sync = !!precedence;
drop.classList.add("htmx-request");
drag.classList.add("htmx-request");
const queue = dragFirst ? [drag, drop] : [drop, drag];
for (const elm of queue) {
const promise = RunDragDrop(elm, elm === drag, dragVals, dropVals);
if (sync) await promise;
}
drag.classList.remove("hx-drag");
drop.classList.remove("hx-drop");
dragging = null;
}
/**
*
* @param {Element} source
* @param {boolean} isDrag
* @param {object} dragVals
* @param {object} dropVals
* @returns
*/
async function RunDragDrop(source, isDrag, dragVals, dropVals) {
if (!document.body.contains(source)) return;
let method, action, values;
if (isDrag) {
action = GetAttribute(source, "hx-drag-action");
method = GetAttribute(source, "hx-drag-method") || "PUT";
values = Object.assign({}, dropVals, dragVals);
} else {
action = GetAttribute(source, "hx-drop-action");
method = GetAttribute(source, "hx-drop-method") || "PUT";
values = Object.assign({}, dragVals, dropVals);
}
console.log('action:', action)
if (action === null) return;
try { // don't let one failure cascade to the other ajax
await htmx.ajax(method, action, { source, values });
} catch (e) {
return;
}
}
})()