mirror of
https://github.com/robertwayne/axum-htmx
synced 2024-12-01 15:34:32 +01:00
Initial commit
This commit is contained in:
commit
ad8c771f0a
7 changed files with 254 additions and 0 deletions
22
.gitignore
vendored
Normal file
22
.gitignore
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# Directories
|
||||||
|
.cargo/
|
||||||
|
.turbo/
|
||||||
|
assets/
|
||||||
|
build/
|
||||||
|
data/
|
||||||
|
dist/
|
||||||
|
node_modules/
|
||||||
|
public/
|
||||||
|
target/
|
||||||
|
|
||||||
|
# Files
|
||||||
|
.env
|
||||||
|
.env.development
|
||||||
|
.env.production
|
||||||
|
.log
|
||||||
|
Cargo.lock
|
||||||
|
pnpm-lock.yaml
|
||||||
|
|
||||||
|
# User Settings
|
||||||
|
.idea
|
||||||
|
.vscode
|
15
Cargo.toml
Normal file
15
Cargo.toml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
[package]
|
||||||
|
name = "axum-htmx"
|
||||||
|
authors = ["Rob Wagner <rob@sombia.com>"]
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
description = "HTMX header extractors for axum."
|
||||||
|
repository = "https://github.com/robertwayne/axum-htmx"
|
||||||
|
categories = ["web-programming", "http-server"]
|
||||||
|
keywords = ["axum", "htmx", "header", "extractor"]
|
||||||
|
readme = "README.md"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
axum = { git = "https://github.com/tokio-rs/axum", branch = "main" }
|
39
README.md
Normal file
39
README.md
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
# axum-htmx
|
||||||
|
|
||||||
|
`axum-htmx` is a small extension library providing extractors for the various
|
||||||
|
[htmx](https://htmx.org/) headers within [axum](https://github.com/tokio-rs/axum).
|
||||||
|
|
||||||
|
__This crate is current a work-in-progress. There are many missing header implementations, and it builds against the upcoming, unreleased branch for `axum`.__
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```toml
|
||||||
|
axum-htmx = { git = "https://github.com/robertwayne/axum-htmx", branch = "main" }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
In this example, we'll look for the `HX-Boosted` header, which is set when applying the [hx-boost](https://htmx.org/attributes/hx-boost/) attribute to an element. In our case, we'll use it to determine what kind of response we send.
|
||||||
|
|
||||||
|
When is this useful? When using a templating engine, like [minijinja](https://github.com/mitsuhiko/minijinja), it is common to extend different templates from a `_base.html` template. However, HTMX works by sending partial responses, so extending our `_base.html` would result in lots of extra data being sent over the wire.
|
||||||
|
|
||||||
|
If we wanted to swap between pages, we would need to support both full template responses and partial responses _(as the page can be accessed directly or through a boosted anchor)_, so we look for the `HX-Boosted` header and extend from a `_partial.html` template instead.
|
||||||
|
|
||||||
|
```rs
|
||||||
|
async fn get_index(HxBoosted(boosted): HxBoosted) -> impl IntoResponse {
|
||||||
|
if boosted {
|
||||||
|
// Send a template extending from _partial.html
|
||||||
|
} else {
|
||||||
|
// Send a template extending from _base.html
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
`axum-htmx` is dual-licensed under either
|
||||||
|
|
||||||
|
- **[MIT License](/docs/LICENSE-MIT)**
|
||||||
|
- **[Apache License, Version 2.0](/docs/LICENSE-APACHE)**
|
||||||
|
|
||||||
|
at your option.
|
13
docs/LICENSE-APACHE
Normal file
13
docs/LICENSE-APACHE
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
Copyright 2023 Rob Wagner
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
7
docs/LICENSE-MIT
Normal file
7
docs/LICENSE-MIT
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
Copyright 2023 Rob Wagner
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
3
rustfmt.toml
Normal file
3
rustfmt.toml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
group_imports = "StdExternalCrate"
|
||||||
|
imports_granularity = "Crate"
|
||||||
|
reorder_imports = true
|
155
src/lib.rs
Normal file
155
src/lib.rs
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
|
use axum::{extract::FromRequestParts, http::request::Parts};
|
||||||
|
|
||||||
|
/// Represents all of the headers that can be sent in a request to the server.
|
||||||
|
///
|
||||||
|
/// See <https://htmx.org/reference/#request_headers> for more information.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub enum HxRequestHeader {
|
||||||
|
/// Indicates that the request is via an element using `hx-boost` attribute.
|
||||||
|
///
|
||||||
|
/// See <https://htmx.org/attributes/hx-boost/> for more information.
|
||||||
|
Boosted,
|
||||||
|
/// The current URL of the browser.
|
||||||
|
CurrentUrl,
|
||||||
|
/// `true` if the request is for history restoration after a miss in the
|
||||||
|
/// local history cache.
|
||||||
|
HistoryRestoreRequest,
|
||||||
|
/// The user response to an `hx-prompt`
|
||||||
|
///
|
||||||
|
/// See <https://htmx.org/attributes/hx-prompt/> for more information.
|
||||||
|
Prompt,
|
||||||
|
/// Always `true`.
|
||||||
|
Request,
|
||||||
|
/// The `id` of the target element, if it exists.
|
||||||
|
Target,
|
||||||
|
/// The `name` of the triggered element, if it exists.
|
||||||
|
TriggerName,
|
||||||
|
/// The `id` of the triggered element, if it exists.
|
||||||
|
Trigger,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HxRequestHeader {
|
||||||
|
pub fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
HxRequestHeader::Boosted => "HX-Boosted",
|
||||||
|
HxRequestHeader::CurrentUrl => "HX-Current-Url",
|
||||||
|
HxRequestHeader::HistoryRestoreRequest => "HX-History-Restore-Request",
|
||||||
|
HxRequestHeader::Prompt => "HX-Prompt",
|
||||||
|
HxRequestHeader::Request => "HX-Request",
|
||||||
|
HxRequestHeader::Target => "HX-Target",
|
||||||
|
HxRequestHeader::TriggerName => "HX-Trigger-Name",
|
||||||
|
HxRequestHeader::Trigger => "HX-Trigger",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Represents all of the headers that can be sent in a response to the client.
|
||||||
|
///
|
||||||
|
/// See <https://htmx.org/reference/#response_headers> for more information.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub enum HxResponseHeader {
|
||||||
|
/// Allows you to do a client-side redirect that does not do a full page
|
||||||
|
/// reload.
|
||||||
|
Location,
|
||||||
|
/// Pushes a new URL onto the history stack.
|
||||||
|
PushUrl,
|
||||||
|
/// Can be used to do a client-side redirect to a new location.
|
||||||
|
Redirect,
|
||||||
|
/// If set to `true`, the client will do a full refresh on the page.
|
||||||
|
Refresh,
|
||||||
|
/// Replaces the currelt URL in the location bar.
|
||||||
|
ReplaceUrl,
|
||||||
|
/// Allows you to specify how the response value will be swapped.
|
||||||
|
///
|
||||||
|
/// See <https://htmx.org/attributes/hx-swap/> for more information.
|
||||||
|
Reswap,
|
||||||
|
/// A CSS selector that update the target of the content update to a
|
||||||
|
/// different element on the page.
|
||||||
|
Retarget,
|
||||||
|
/// A CSS selector that allows you to choose which part of the response is
|
||||||
|
/// used to be swapped in. Overrides an existing `hx-select` on the
|
||||||
|
/// triggering element
|
||||||
|
Reselect,
|
||||||
|
/// Allows you to trigger client-side events.
|
||||||
|
///
|
||||||
|
/// See <https://htmx.org/headers/hx-trigger/> for more information.
|
||||||
|
Trigger,
|
||||||
|
/// Allows you to trigger client-side events.
|
||||||
|
///
|
||||||
|
/// See <https://htmx.org/headers/hx-trigger/> for more information.
|
||||||
|
TriggerAfterSettle,
|
||||||
|
/// Allows you to trigger client-side events.
|
||||||
|
///
|
||||||
|
/// See <https://htmx.org/headers/hx-trigger/> for more information.
|
||||||
|
TriggerAfterSwap,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HxResponseHeader {
|
||||||
|
pub fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
HxResponseHeader::Location => "HX-Location",
|
||||||
|
HxResponseHeader::PushUrl => "HX-Push-Url",
|
||||||
|
HxResponseHeader::Redirect => "HX-Redirect",
|
||||||
|
HxResponseHeader::Refresh => "HX-Refresh",
|
||||||
|
HxResponseHeader::ReplaceUrl => "HX-Replace-Url",
|
||||||
|
HxResponseHeader::Reswap => "HX-Reswap",
|
||||||
|
HxResponseHeader::Retarget => "HX-Retarget",
|
||||||
|
HxResponseHeader::Reselect => "HX-Reselect",
|
||||||
|
HxResponseHeader::Trigger => "HX-Trigger",
|
||||||
|
HxResponseHeader::TriggerAfterSettle => "HX-Trigger-After-Settle",
|
||||||
|
HxResponseHeader::TriggerAfterSwap => "HX-Trigger-After-Swap",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The `HX-Boosted` header. This header is set when a request is made with the
|
||||||
|
/// "hx-boost" attribute is set on an element.
|
||||||
|
///
|
||||||
|
/// This extractor does not fail if no header is present, instead returning a
|
||||||
|
/// `false` value.
|
||||||
|
///
|
||||||
|
/// See <https://htmx.org/attributes/hx-boost/> for more information.
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct HxBoosted(pub bool);
|
||||||
|
|
||||||
|
#[axum::async_trait]
|
||||||
|
impl<S> FromRequestParts<S> for HxBoosted
|
||||||
|
where
|
||||||
|
S: Send + Sync,
|
||||||
|
{
|
||||||
|
type Rejection = std::convert::Infallible;
|
||||||
|
|
||||||
|
async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
|
||||||
|
if parts
|
||||||
|
.headers
|
||||||
|
.contains_key(HxRequestHeader::Boosted.as_str())
|
||||||
|
{
|
||||||
|
return Ok(HxBoosted(true));
|
||||||
|
} else {
|
||||||
|
return Ok(HxBoosted(false));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct HxCurrentUrl(pub String);
|
||||||
|
|
||||||
|
#[axum::async_trait]
|
||||||
|
impl<S> FromRequestParts<S> for HxCurrentUrl
|
||||||
|
where
|
||||||
|
S: Send + Sync,
|
||||||
|
{
|
||||||
|
type Rejection = std::convert::Infallible;
|
||||||
|
|
||||||
|
async fn from_request_parts(parts: &mut Parts, _: &S) -> Result<Self, Self::Rejection> {
|
||||||
|
if let Some(url) = parts.headers.get(HxRequestHeader::CurrentUrl.as_str()) {
|
||||||
|
if let Ok(url) = url.to_str() {
|
||||||
|
return Ok(HxCurrentUrl(url.to_string()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Ok(HxCurrentUrl("".to_string()));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue