Add ability to update items using htmx.

This commit is contained in:
Drew Galbraith 2024-12-03 22:05:32 -08:00
parent 381910d462
commit e6ff8adb14
9 changed files with 109 additions and 16 deletions

12
Cargo.lock generated
View File

@ -125,6 +125,7 @@ checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"axum-core", "axum-core",
"axum-macros",
"bytes", "bytes",
"futures-util", "futures-util",
"http", "http",
@ -172,6 +173,17 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "axum-macros"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.90",
]
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.74" version = "0.3.74"

View File

@ -6,7 +6,7 @@ edition = "2021"
[dependencies] [dependencies]
askama = { version = "0.12.1", features = ["with-axum"] } askama = { version = "0.12.1", features = ["with-axum"] }
askama_axum = "0.4.0" askama_axum = "0.4.0"
axum = "0.7" axum = { version = "0.7.2", features = ["macros"] }
dotenvy = "0.15" dotenvy = "0.15"
http = "1.1.0" http = "1.1.0"
serde = "1.0" serde = "1.0"

View File

@ -8,7 +8,6 @@ There are 3 servers to run to enable hot reloading of the frontend
and backend. The server can then be accessed at `localhost:8000` in and backend. The server can then be accessed at `localhost:8000` in
the browser. the browser.
```bash ```bash
$ cargo watch -x run $ cargo watch -x run
$ cd frontend/ $ cd frontend/

View File

@ -4,7 +4,7 @@ mod routes;
use axum::http::{StatusCode, Uri}; use axum::http::{StatusCode, Uri};
use axum::response::IntoResponse; use axum::response::IntoResponse;
use axum::routing::{delete, get}; use axum::routing::{get, put};
use dotenvy::dotenv; use dotenvy::dotenv;
use http::{HeaderName, Method}; use http::{HeaderName, Method};
use sqlx::SqlitePool; use sqlx::SqlitePool;
@ -35,7 +35,11 @@ async fn main() {
"/tasks", "/tasks",
get(routes::tasks::list).post(routes::tasks::create), get(routes::tasks::list).post(routes::tasks::create),
) )
.route("/tasks/:task_id", delete(routes::tasks::delete)) .route(
"/tasks/:task_id",
put(routes::tasks::put).delete(routes::tasks::delete),
)
.route("/tasks/:task_id/edit", get(routes::tasks::edit))
.layer( .layer(
ServiceBuilder::new().layer( ServiceBuilder::new().layer(
CorsLayer::new() CorsLayer::new()

View File

@ -22,6 +22,18 @@ impl Task {
.await .await
} }
pub async fn delete(pool: &SqlitePool, id: i64) -> Result<SqliteQueryResult, sqlx::Error> {
sqlx::query!("DELETE FROM task WHERE id = ?1", id)
.execute(pool)
.await
}
pub async fn by_id(pool: &SqlitePool, id: i64) -> Result<Task, sqlx::Error> {
sqlx::query_as!(Task, "SELECT * FROM task WHERE id = ?1", id,)
.fetch_one(pool)
.await
}
pub async fn insert(&self, pool: &SqlitePool) -> Result<Task, sqlx::Error> { pub async fn insert(&self, pool: &SqlitePool) -> Result<Task, sqlx::Error> {
sqlx::query_as!( sqlx::query_as!(
Task, Task,
@ -33,8 +45,13 @@ impl Task {
.await .await
} }
pub async fn delete(pool: &SqlitePool, id: i64) -> Result<SqliteQueryResult, sqlx::Error> { pub async fn update(&self, pool: &SqlitePool) -> Result<SqliteQueryResult, sqlx::Error> {
sqlx::query!("DELETE FROM task WHERE id = ?1", id) sqlx::query!(
"UPDATE task SET title=?2, description=?3 WHERE id = ?1",
self.id,
self.title,
self.description,
)
.execute(pool) .execute(pool)
.await .await
} }

View File

@ -2,6 +2,7 @@ use askama_axum::Template;
use axum::extract::{Json, Path, State}; use axum::extract::{Json, Path, State};
use axum::http::StatusCode; use axum::http::StatusCode;
use axum::response::IntoResponse; use axum::response::IntoResponse;
use axum::Form;
use serde::Deserialize; use serde::Deserialize;
use crate::global::FormErrorResponse; use crate::global::FormErrorResponse;
@ -60,3 +61,33 @@ pub async fn delete(state: State<AppState>, Path(id): Path<i64>) -> impl IntoRes
(StatusCode::OK, "") (StatusCode::OK, "")
} }
#[derive(Template)]
#[template(path = "partials/tasks/edit.html")]
struct TaskEditTemplate {
pub task: Task,
}
pub async fn edit(state: State<AppState>, Path(id): Path<i64>) -> impl IntoResponse {
TaskEditTemplate {
task: Task::by_id(&state.db_pool, id).await.unwrap(),
}
}
#[derive(Template)]
#[template(path = "partials/tasks/item.html")]
struct TaskItemTemplate {
pub task: Task,
}
pub async fn put(
state: State<AppState>,
Path(id): Path<i64>,
Form(req): Form<NewTask>,
) -> impl IntoResponse {
let mut task = Task::by_id(&state.db_pool, id).await.unwrap();
task.title = req.title;
task.description = req.description;
task.update(&state.db_pool).await.unwrap();
TaskItemTemplate { task }
}

View File

@ -2,7 +2,7 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Captains Log</title> <title>Captains Log</title>
<script type="text/javascript" src="/build/main.js"></script> <script src="https://unpkg.com/htmx.org@2.0.3"></script>
<link href="/assets/bootstrap.min.css" rel="stylesheet"> <link href="/assets/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
</head> </head>
@ -22,7 +22,7 @@
</header> </header>
<ul> <ul>
{% for task in tasks %} {% for task in tasks %}
<li>{{ task.title }}</li> {% include "partials/tasks/item.html" %}
{% endfor %} {% endfor %}
</ul> </ul>
</body> </body>

View File

@ -0,0 +1,21 @@
<form hx-target="this", hx-swap="outerHtml">
<div>
<label>Title</label>
<input type="text" name="title" value="{{ task.title }}"> </input>
</div>
<div>
<label>Description</label>
{% if let Some(desc) = task.description %}
<input type="text" name="description" value="{{ desc }}"> </input>
{% else %}
<input type="text" name="description" value=""> </input>
{% endif %}
</div>
<button hx-get="/tasks/{{ task.id }}">
Cancel
</button>
<button hx-put="/tasks/{{ task.id }}">
Submit
</button>
</form>

View File

@ -0,0 +1,9 @@
<li hx-target="this" hx-swap="outerHtml">
{{ task.title }}
{% match task.description %}
{% when Some with (val) %}
-- <span> {{ val }}</span>
{% when None %}
{% endmatch %}
<button hx-get="/tasks/{{task.id}}/edit">Edit</button>
</li>