feat: add basic htmx example

This commit is contained in:
Alexander Navarro 2025-02-26 04:16:11 -03:00
parent 17bf419074
commit ffca6175c0
6 changed files with 80 additions and 25 deletions

32
Cargo.lock generated
View file

@ -117,6 +117,23 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "axum-htmx"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d16a4be621f96b959fc829e4cbf02fd79ffb8427525002af31a9e2979599fbb7"
dependencies = [
"axum-core",
"futures",
"futures-core",
"http",
"pin-project-lite",
"serde",
"serde_json",
"tokio",
"tower",
]
[[package]] [[package]]
name = "axum-macros" name = "axum-macros"
version = "0.5.0" version = "0.5.0"
@ -250,6 +267,7 @@ name = "compendium"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"axum", "axum",
"axum-htmx",
"axum-sqlx-tx", "axum-sqlx-tx",
"chrono", "chrono",
"minijinja", "minijinja",
@ -481,6 +499,20 @@ dependencies = [
"libc", "libc",
] ]
[[package]]
name = "futures"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876"
dependencies = [
"futures-channel",
"futures-core",
"futures-io",
"futures-sink",
"futures-task",
"futures-util",
]
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.31" version = "0.3.31"

View file

@ -5,6 +5,7 @@ edition = "2021"
[dependencies] [dependencies]
axum = { version = "0.8.1", features = ["macros"] } axum = { version = "0.8.1", features = ["macros"] }
axum-htmx = { version = "0.7.0", features = ["auto-vary", "serde", "guards"] }
axum-sqlx-tx = "0.10.0" axum-sqlx-tx = "0.10.0"
chrono = { version = "0.4.39", features = ["serde"] } chrono = { version = "0.4.39", features = ["serde"] }
minijinja = { version = "2.7.0", features = ["loader"] } minijinja = { version = "2.7.0", features = ["loader"] }

View file

@ -1,10 +1,11 @@
<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="@mini-strap/core" /> <link rel="stylesheet" href="@mini-strap/core" />
<script type="module" src="htmx.org"></script>
<link rel="stylesheet" href="../css/style.css" /> <link rel="stylesheet" href="../css/style.css" />
<script type="module" src="htmx.org"></script>
<title> <title>
{% block title %}Axum web service!{% endblock %} {% block title %}Axum web service!{% endblock %}
</title> </title>

View file

@ -1,26 +1,34 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block content %} {% block content %}
<table>
<thead> <div class="msp-hstack msp-justify-content-end msp-mb-3">
<tr> <button class="msp-btn-sm" hx-get="/" hx-target="#example-table">Refresh</button>
<th class="msp-text-center">Database</th> </div>
<th class="msp-text-center">Version</th>
<th class="msp-text-center">Database Name</th>
<th class="msp-text-center">Current User</th> {% block htmx %}
<th class="msp-text-center">Current Timestamp</th> <table id="example-table">
</tr> <thead>
</thead> <tr>
<tbody> <th class="msp-text-center">Database</th>
{% for row in rows %} <th class="msp-text-center">Version</th>
<tr> <th class="msp-text-center">Database Name</th>
<th>{{ row.database }}</th> <th class="msp-text-center">Current User</th>
<th>{{ row.version }}</th> <th class="msp-text-center">Current Timestamp</th>
<th>{{ row.current_database }}</th> </tr>
<th>{{ row.current_user }}</th> </thead>
<th>{{ row.current_timestamp }}</th> <tbody>
</tr> {% for row in rows %}
{% endfor %} <tr>
</tbody> <th>{{ row.database }}</th>
</table> <th>{{ row.version }}</th>
<th>{{ row.current_database }}</th>
<th>{{ row.current_user }}</th>
<th>{{ row.current_timestamp }}</th>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock htmx %}
{% endblock content %} {% endblock content %}

View file

@ -1,6 +1,7 @@
#![allow(unused)] #![allow(unused)]
#![allow(dead_code)] #![allow(dead_code)]
use axum_htmx::AutoVaryLayer;
use compendium::{router, AppState, Error, Link, Result, Tx}; use compendium::{router, AppState, Error, Link, Result, Tx};
use minijinja::{Environment, Value}; use minijinja::{Environment, Value};
use sqlx::postgres::PgPoolOptions; use sqlx::postgres::PgPoolOptions;
@ -78,6 +79,7 @@ async fn main() -> Result<()> {
let app = router::new() let app = router::new()
.layer(TraceLayer::new_for_http().on_request(())) .layer(TraceLayer::new_for_http().on_request(()))
.layer(tx_layer) .layer(tx_layer)
.layer(AutoVaryLayer)
.with_state(AppState::new(tmpl_env, tx_state)); .with_state(AppState::new(tmpl_env, tx_state));
// Add hot reload only on dev mode // Add hot reload only on dev mode

View file

@ -1,4 +1,5 @@
use axum::{extract::State, response::Html, routing::get, Router}; use axum::{extract::State, response::Html, routing::get, Router};
use axum_htmx::HxRequest;
use chrono::Utc; use chrono::Utc;
use minijinja::context; use minijinja::context;
use serde::Serialize; use serde::Serialize;
@ -22,7 +23,11 @@ struct ExampleRow {
pub current_timestamp: chrono::DateTime<Utc>, pub current_timestamp: chrono::DateTime<Utc>,
} }
async fn handler_home(State(state): State<AppState>, mut tx: Tx) -> ResultTemplate { async fn handler_home(
State(state): State<AppState>,
HxRequest(hx_request): HxRequest,
mut tx: Tx,
) -> ResultTemplate {
let template = state.tmpl_env.get_template("index.html")?; let template = state.tmpl_env.get_template("index.html")?;
let rows = sqlx::query_as::<_, ExampleRow>("select 'Postgres' as database, setting as version, current_database(), current_user, current_timestamp from pg_settings where name = 'server_version'") let rows = sqlx::query_as::<_, ExampleRow>("select 'Postgres' as database, setting as version, current_database(), current_user, current_timestamp from pg_settings where name = 'server_version'")
@ -31,7 +36,13 @@ async fn handler_home(State(state): State<AppState>, mut tx: Tx) -> ResultTempla
println!("{:?}", rows); println!("{:?}", rows);
let content = template.render(context!(rows => rows))?; let content = if hx_request {
template
.eval_to_state(context!(rows => rows))?
.render_block("htmx")?
} else {
template.render(context!(rows => rows))?
};
Ok(Html(content)) Ok(Html(content))
} }