Compare commits

..

4 commits

Author SHA1 Message Date
Daniel Bulant
afe12787c9
fix empty db state 2026-03-14 20:56:22 +01:00
Daniel Bulant
9e9514db8c
smol htmx 2026-03-14 20:56:16 +01:00
Daniel Bulant
b3625489d1
add basic requirements 2026-03-14 20:56:06 +01:00
Daniel Bulant
3bba57756b
fix paging 2026-03-14 20:55:56 +01:00
5 changed files with 110 additions and 3 deletions

View file

@ -13,6 +13,8 @@ htmx = HTMX(app)
@app.template_filter()
def format_datetime(value):
if not value:
return value
return datetime.fromtimestamp(value).isoformat()

2
a5/requirements.txt Normal file
View file

@ -0,0 +1,2 @@
flask
flask-htmx

102
a5/static/htmlx-smol.js Normal file
View file

@ -0,0 +1,102 @@
const elementTimers = new Map;
function addTimer(element, timerId) {
const timers = elementTimers.get(element) || [];
timers.push(timerId)
elementTimers.set(element, timers);
}
function removeTimerElement(element) {
const timers = elementTimers.get(element) || [];
for (const timer of timers) {
clearInterval(timer);
}
elementTimers.delete(element);
}
/**
*
* @param {Event} event
*/
function handleEvent(e) {
e.preventDefault();
handleTrigger(e.target);
}
/**
* @param {String} duration
*/
function parseDuration(duration) {
const parsed = duration.match(/(\d+(?:\.\d+)?(s|ms))/);
if (!parsed) throw new Error("Unknown duration");
const num = parseFloat(parsed[1]);
const type = parsed[2];
const length = { "s": 1000, "ms": 1 };
return num * length[type];
}
/**
* @param {Element|Document} element
*/
function registerHandlers(element) {
const elements = element.querySelectorAll("[hx-get],[hx-trigger],[hx-target]");
for (const element of elements) {
const trigger = element.getAttribute("hx-trigger");
if (trigger) {
const [key, duration, ...rest] = trigger.split(" ");
if (rest.length) throw new Error("Unknown trigger: " + rest);
const parsedDuration = parseDuration(duration);
if (key !== "every") throw new Error("Unknown trigger: " + key);
addTimer(element, setInterval(handleTrigger.bind(null, element), parsedDuration));
} else {
if ("onclick" in element) {
element.onclick = handleEvent;
}
}
}
}
/**
* @param {Element} element
*/
async function handleTrigger(element) {
const replaceUrl = element.getAttribute("hx-replace-url");
const get = element.getAttribute("hx-get");
const targetSelector = element.getAttribute("hx-target");
const targetElement = document.querySelector(targetSelector) || element;
if (replaceUrl && replaceUrl !== "false") {
const targetUrl = replaceUrl == "true" ? get : replaceUrl;
if (targetUrl) {
window.history.replaceState({}, "", targetUrl);
}
}
if (get) {
const res = await fetch(get, {
headers: {
"HX-Request": "true",
"HX-Trigger": element.id,
"HX-Target": targetSelector,
"HX-Current-URL": document.location.href
}
});
const data = await res.text();
if (!targetElement) {
console.error("Target element not found! Attempted:", targetSelector);
return;
}
const elements = [targetElement, ...targetElement.querySelectorAll("*")];
for (const element of elements) {
removeTimerElement(element);
}
targetElement.innerHTML = data;
registerHandlers(targetElement);
}
}
window.onload = () => registerHandlers(document);

View file

@ -24,6 +24,7 @@
rel="stylesheet"
>
<script src="https://cdn.jsdelivr.net/npm/htmx.org@2.0.8/dist/htmx.min.js" integrity="sha384-/TgkGk7p307TH7EXJDuUlgG3Ce1UVolAOFopFekQkkXihi5u/6OCvVKyz1W+idaz" crossorigin="anonymous"></script>
{#<script src="/static/htmlx-smol.js"></script>#}
</head>
<body>
<div class="bg-gradient"></div>

View file

@ -1,4 +1,4 @@
<div hx-get="/measurements?htmx" hx-trigger="every 2s" hx-target="main"></div>
<div hx-get="/measurements?page={{page}}&htmx" hx-trigger="every 2s" hx-target="main"></div>
<h1>Measurements</h1>
<p>Page {{page}}</p>
@ -21,10 +21,10 @@
<div>
{% if page > 1 %}
<a href="/measurements?page={{page-1}}" hx-get="/measurements?page={{page-1}}&htmx" hx-target="main" class="btn btn-link">Previous page</a>
<a href="/measurements?page={{page-1}}" hx-replace-url="/measurements?page={{page-1}}" hx-get="/measurements?page={{page-1}}&htmx" hx-target="main" class="btn btn-link">Previous page</a>
{% endif %}
{% if has_next_page %}
<a href="/measurements?page={{page+1}}" hx-get="/measurements?page={{page+1}}&htmx" hx-target="main" class="btn btn-link">Next page</a>
<a href="/measurements?page={{page+1}}" hx-replace-url="/measurements?page={{page+1}}" hx-get="/measurements?page={{page+1}}&htmx" hx-target="main" class="btn btn-link">Next page</a>
{% endif %}
<a href="/" class="btn btn-link">View statistics</a>