use maud::{Markup, PreEscaped, html};
use crate::components::attrs::{HtmlAttrs, escape_attr};
use crate::components::swap::{
ModalHostKey, SwapKey, form_hx_get_for_url, form_hx_get_route, form_hx_post_for_url,
form_hx_post_route, nav_main_attrs,
};
use crate::http::{FkSelectGet, FragmentGet, FragmentPost, RouteUrl};
pub const HTMX_TARGET_BODY_MODAL: &str = ModalHostKey::SELECTOR;
pub const HTMX_SWAP_BODY_MODAL: &str = "beforeend";
pub const FK_DROPDOWN_ID_PREFIX: &str = "fk-dropdown-";
pub fn form_post_region<K: SwapKey>(action: &str) -> HtmlAttrs {
form_hx_post_for_url::<K>(action)
}
pub fn form_post_region_route<K: SwapKey, R: RouteUrl + FragmentPost<K>>(route: R) -> HtmlAttrs {
form_hx_post_route::<K, R>(route)
}
pub fn form_get_region<K: SwapKey>(action: &str) -> HtmlAttrs {
form_hx_get_for_url::<K>(action)
}
pub fn form_get_region_route<K: SwapKey, R: RouteUrl + FragmentGet<K>>(route: R) -> HtmlAttrs {
form_hx_get_route::<K, R>(route)
}
pub fn row_attr_navigate(url: &str) -> HtmlAttrs {
nav_main_attrs(url)
}
pub fn row_attr_navigate_route(route: impl RouteUrl) -> HtmlAttrs {
row_attr_navigate(&route.url())
}
pub fn row_attr_select(name: &str, value: &str, display: &str) -> HtmlAttrs {
row_attr_select_extra(name, value, display, &[])
}
fn fk_select_click_js(name: &str, value: &str, display: &str, extra: &[(&str, &str)]) -> String {
let value_json = if let Ok(n) = value.parse::<u64>() {
serde_json::Value::from(n)
} else {
serde_json::Value::from(value)
};
let mut detail = serde_json::json!({
"name": name,
"value": value_json,
"display": display,
});
if let Some(map) = detail.as_object_mut() {
for (k, v) in extra {
map.insert((*k).to_string(), serde_json::Value::from(*v));
}
}
format!(
"$dispatch('fk-select', {detail}); const el=$event.currentTarget; if(el.closest('.fk-picker-results')) return; el.closest('dialog.modal')?.remove()"
)
}
pub fn row_attr_select_extra(
name: &str,
value: &str,
display: &str,
extra: &[(&str, &str)],
) -> HtmlAttrs {
HtmlAttrs::new()
.set(
"class",
"cursor-pointer hover:bg-base-200 transition-colors",
)
.set("@click", fk_select_click_js(name, value, display, extra))
}
pub fn button_fk_select(label: &str, name: &str, value: &str, display: &str) -> Markup {
let js = fk_select_click_js(name, value, display, &[]);
html! {
(PreEscaped(format!(
r#"<button type="button" class="btn btn-outline btn-sm" @click="{}">"#,
escape_attr(&js),
)))
(label)
(PreEscaped("</button>"))
}
}
pub fn row_attr_select_multi(name: &str, value: &str, display: &str) -> HtmlAttrs {
row_attr_select_multi_extra(name, value, display, &[])
}
pub fn row_attr_select_multi_extra(
name: &str,
value: &str,
display: &str,
extra: &[(&str, &str)],
) -> HtmlAttrs {
let mut detail = serde_json::json!({
"name": name,
"value": value,
"display": display,
});
if let Some(map) = detail.as_object_mut() {
for (k, v) in extra {
map.insert((*k).to_string(), serde_json::Value::from(*v));
}
}
let js = format!("$dispatch('fk-multi-select', {})", detail);
let class_expr = format!(
"((Alpine.store('m2mSelections') && Alpine.store('m2mSelections')[{name:?}]) || []).some(item => item.Key === {value:?}) ? 'bg-success text-success-content hover:bg-success border-success' : 'hover:bg-base-200'"
);
HtmlAttrs::new()
.set("class", "cursor-pointer transition-colors")
.set(":class", class_expr)
.set("@click", js)
}
pub fn hx_head_append(children: Markup) -> Markup {
html! {
head hx-head="append" {
(children)
}
}
}
pub fn hx_partial_with_head(head: Markup, body: Markup) -> Markup {
html! {
(hx_head_append(head))
(body)
}
}
pub fn modal_open_attrs(href: &str) -> HtmlAttrs {
HtmlAttrs::new()
.set("hx-get", href)
.set("hx-target", ModalHostKey::SELECTOR)
.set("hx-swap", HTMX_SWAP_BODY_MODAL)
.set("hx-push-url", "false")
}
pub fn modal_picker_open_route<R, K, M>(route: R) -> HtmlAttrs
where
R: RouteUrl + FkSelectGet<K, M>,
K: SwapKey,
M: SwapKey,
{
modal_open_attrs(&route.url())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn row_attr_select_skips_removing_dialog_from_typeahead() {
let html = row_attr_select("CustomerId", "1", "Acme").as_string();
assert!(html.contains("fk-select"), "{html}");
assert!(html.contains("fk-picker-results"), "{html}");
assert!(html.contains("dialog.modal"), "{html}");
}
}