feat: implement a simple loader

Play with htmx events
This commit is contained in:
2023-05-11 00:03:36 +02:00
parent 4489a8a8e5
commit efef1d074e
4 changed files with 209 additions and 4 deletions

View File

@@ -1,7 +1,7 @@
use std::io;
use std::{io, time::Duration};
use actix_files as fs;
use actix_web::{get, middleware, App, HttpRequest, HttpServer, Responder};
use actix_web::{get, middleware, App, HttpRequest, HttpServer, Responder, rt::time::sleep};
use actix_web_lab::respond::Html;
use askama::Template;
@@ -44,9 +44,11 @@ async fn users(req: HttpRequest) -> actix_web::Result<impl Responder> {
email: "joe.doe@foo.baz".into(),
},
]);
match req.headers().get("hx-request") {
Some(_) => {
// Render the hypermedia fragment.
sleep(Duration::from_millis(1000)).await;
Ok(Html(Fragment { users }.render().expect("Valid template")))
}
None => {

View File

@@ -1,3 +1,89 @@
h1 {
color: blue;
}
[data-loader] {
position: fixed;
top: 0;
left: 0;
z-index: 1000;
height: .2em;
padding: 0;
margin: 0;
background-color: blue;
transition: all 500ms;
}
body {
display: grid;
grid-template-columns: 15em 1fr;
grid-template-rows: 3em 1fr 3em;
grid-template-areas:
"nav header"
"nav main"
"nav footer";
margin: 0;
height: 100vh;
}
@media (max-width: 1080px) {
body {
grid-template-columns: 1fr;
grid-template-rows: 3em 1fr 3em;
grid-template-areas:
"header"
"main"
"footer";
}
nav {
position: fixed;
left: 0;
top: 0;
bottom: 0;
width: 15em;
}
}
body > * {
padding: 1em;
}
hgroup * {
margin: 0;
}
nav {
grid-area: nav;
background-color: lightgray;
}
header {
grid-area: header;
background-color: orange;
display: flex;
justify-content: space-evenly;
align-items: center;
}
main {
grid-area: main;
background-color: darkgray;
}
footer {
grid-area: footer;
background-color: grey;
display: flex;
justify-content: flex-start;
align-items: center;
}

98
static/js/loader.js Normal file
View File

@@ -0,0 +1,98 @@
/**
* A loading bar that hooks into htmx events to automatically indicate the
* progression of pending requests.
*
* ```html
* <div data-loader></div>
* ```
* Only the first loader will be used.
*/
(function () {
var MAX_INCREMENT = 20;
var MIN_INCREMENT = 3;
var ADVANCE_UNTIL = 90;
var pending = 0;
var increment = MAX_INCREMENT;
var timeout = null;
var interval = null;
function getLoader() {
return document.querySelector("[data-loader]");
}
function clamp(x, a, b) {
return Math.max(a, Math.min(x, b));
}
function progress(loader) {
loader.hidden = false;
var current = parseInt(loader.dataset.loader);
if (current < ADVANCE_UNTIL) {
current = Math.min(current + increment, ADVANCE_UNTIL);
loader.dataset.loader = current;
loader.style.right = 100 - current + "%";
increment = clamp(Math.floor(increment * 0.65), MIN_INCREMENT, MAX_INCREMENT);
}
}
function start() {
pending++;
if (pending > 1) {
return;
}
clearTimeout(timeout);
clearInterval(interval);
increment = MAX_INCREMENT;
var loader = getLoader();
if (!loader) {
return;
}
// Give the request a reasonable time to complete before triggering the
// loader.
timeout = setTimeout(function () {
loader.dataset.loader = 0;
loader.style.right = "100%";
loader.hidden = false;
// Increment the loader state until it reaches the threshold.
// Trickery and lies!
interval = setInterval(function () {
progress(loader)
}, 100);
}, 250);
}
function finish() {
pending--;
if (pending <= 0) {
clearTimeout(timeout);
clearInterval(interval);
var loader = getLoader();
if (!loader) {
return;
}
loader.style.right = 0;
timeout = setTimeout(function () {
loader.hidden = true;
timeout = setTimeout(function () {
loader.dataset.loader = 0;
loader.style.right = "100%";
}, 500);
}, 500);
return;
}
}
addEventListener("htmx:beforeRequest", start);
addEventListener("htmx:afterRequest", finish);
})();

View File

@@ -7,15 +7,34 @@
<link rel="stylesheet" href="/static/css/style.css" />
<script src="/static/js/htmx.min.js"></script>
<script src="/static/js/script.js"></script>
<script src="/static/js/script.js" defer></script>
<script src="/static/js/loader.js" defer></script>
<title>{% block title %}Hello htmx!{% endblock %}</title>
{% block head %}{% endblock %}
</head>
<body>
<div data-loader hidden></div>
<nav data-nav>
<hgroup>
<h2>htmx</h2>
<p>Very cool</p>
</hgroup>
<ol>
<li><a href="/">Home</a></li>
<li><a href="/user">User</a></li>
</ol>
</nav>
<header>
<div>Item 1</div>
<div>Item 2</div>
<div>Item 3</div>
</header>
<main id="content">
{% block content %}{% endblock %}
</main>
</body>
<footer>
<small>* Not a pretty site...</small>
</footer>
</html>