feat: getting the hang of htmx

This commit is contained in:
Jef Roosens 2024-12-29 13:28:49 +01:00
parent f96bf4d193
commit 4c84c944d4
No known key found for this signature in database
GPG key ID: 21FD3D77D56BAF49
12 changed files with 153 additions and 46 deletions

View file

@ -1,23 +1,41 @@
mod plants;
use axum::{
extract::State,
http::{header::VARY, HeaderMap, HeaderValue},
response::Html,
routing::{get, post},
Form, Router,
routing::get,
Router,
};
use tera::Context;
use tower_http::set_header::SetResponseHeaderLayer;
use crate::db;
const HX_REQUEST_HEADER: &str = "HX-Request";
pub fn is_htmx_req(headers: &HeaderMap) -> bool {
headers.get(HX_REQUEST_HEADER).is_some()
}
pub fn app(ctx: crate::Context) -> axum::Router {
let mut router = Router::new()
.route("/", get(get_index))
.route("/plants", post(post_plants));
.with_state(ctx.clone())
.nest("/plants", plants::app(ctx.clone()));
for (name, content) in crate::STATIC_FILES {
router = router.route(&format!("/static/{}", name), get(content))
}
router.with_state(ctx)
// Routes return either partial or full pages depending on whether the request is done using
// HTMX or just as a plain HTTP request. Adding the Vary header ensures caches don't mix
// partial and full responses.
// https://htmx.org/docs/#caching
router.layer(SetResponseHeaderLayer::appending(
VARY,
HeaderValue::from_static(HX_REQUEST_HEADER),
))
}
async fn get_index(State(ctx): State<crate::Context>) -> Html<String> {
@ -30,17 +48,3 @@ async fn get_index(State(ctx): State<crate::Context>) -> Html<String> {
context.insert("plants", &plants);
Html(ctx.tera.render("index.html", &context).unwrap())
}
async fn post_plants(
State(ctx): State<crate::Context>,
Form(plant): Form<db::NewPlant>,
) -> Html<String> {
let plant = tokio::task::spawn_blocking(move || db::insert_plant(&ctx.pool, &plant))
.await
.unwrap()
.unwrap();
let mut context = Context::new();
context.insert("plant", &plant);
Html(ctx.tera.render("plant_li.html", &context).unwrap())
}

55
src/server/plants.rs Normal file
View file

@ -0,0 +1,55 @@
use axum::{
extract::{Path, State},
http::HeaderMap,
response::Html,
routing::{get, post},
Form, Router,
};
use tera::Context;
use crate::db;
use super::is_htmx_req;
pub fn app(ctx: crate::Context) -> axum::Router {
Router::new()
.route("/:id", get(get_plant_page))
.route("/", post(post_plant))
.with_state(ctx)
}
async fn get_plant_page(
State(ctx): State<crate::Context>,
headers: HeaderMap,
Path(plant_id): Path<u32>,
) -> Html<String> {
let plant = tokio::task::spawn_blocking(move || db::get_plant(&ctx.pool, plant_id))
.await
.unwrap()
.unwrap();
let mut context = Context::new();
context.insert("plant", &plant);
let tmpl = if is_htmx_req(&headers) {
"partials/plant_info.html"
} else {
"plant_page.html"
};
Html(ctx.tera.render(tmpl, &context).unwrap())
}
async fn post_plant(
State(ctx): State<crate::Context>,
Form(plant): Form<db::NewPlant>,
) -> Html<String> {
let plant = tokio::task::spawn_blocking(move || db::insert_plant(&ctx.pool, &plant))
.await
.unwrap()
.unwrap();
let mut context = Context::new();
context.insert("plant", &plant);
Html(ctx.tera.render("partials/plant_li.html", &context).unwrap())
}