use crate::core::handler::HandlerRef;
use crate::core::view::{Attr, AttrValue, Child, Element, View};
use crate::core::FlowRequest;
use super::runtime::current_request;
pub fn build_query_href(path: &str, pairs: &[(&str, &str)]) -> String {
let filtered: Vec<_> = pairs
.iter()
.filter(|(_, v)| !v.is_empty())
.copied()
.collect();
if filtered.is_empty() {
return path.to_string();
}
let qs = serde_urlencoded::to_string(filtered).unwrap_or_default();
if qs.is_empty() {
path.to_string()
} else {
format!("{path}?{qs}")
}
}
pub fn current_location_href() -> String {
let Some(req) = current_request() else {
return "/".into();
};
let pairs: Vec<_> = req
.query
.iter()
.map(|(k, v)| (k.as_str(), v.as_str()))
.collect();
build_query_href(&req.path, &pairs)
}
pub fn query_nav_link(
path: &str,
query: &[(&str, &str)],
active_class: impl Into<String>,
class: impl Into<String>,
children: Vec<Child>,
) -> View {
let href = build_query_href(path, query);
let active_class = active_class.into();
let class = class.into();
let current = current_location_href();
let is_active = href == current;
let merged_class = if is_active && !active_class.is_empty() {
format!("{class} {active_class}")
} else {
class
};
View::Element(Element {
tag: "a".into(),
attrs: vec![
Attr {
name: "href".into(),
value: AttrValue::Static(href),
},
Attr {
name: "class".into(),
value: AttrValue::Static(merged_class),
},
Attr {
name: "data-r-nav".into(),
value: AttrValue::Static("true".into()),
},
Attr {
name: "data-r-active-class".into(),
value: AttrValue::Static(active_class),
},
],
children,
dom_id: None,
})
}
pub fn loader_refresh_input(
path: &str,
param: &str,
value: &str,
preserve: &[&str],
input_type: &str,
extra_attrs: Vec<(&str, AttrValue)>,
) -> View {
let preserve_json = serde_json::to_string(preserve).unwrap_or_else(|_| "[]".into());
let path_js = serde_json::to_string(path).unwrap_or_else(|_| "\"/\"".into());
let param_js = serde_json::to_string(param).unwrap_or_else(|_| "\"\"".into());
let handler_body = format!(
r#"(async (event, state, __resuma) => {{
const input = event.target;
if (!(input instanceof HTMLInputElement || input instanceof HTMLSelectElement)) return;
const v = input.value;
if (!v) return;
const q = new URLSearchParams(location.search);
const params = {{}};
params[{param_js}] = v;
for (const k of {preserve_json}) {{
const cur = q.get(k);
if (cur) params[k] = cur;
}}
await __resuma.navigate(__resuma.buildUrl({path_js}, params));
}})"#,
param_js = param_js,
preserve_json = preserve_json,
path_js = path_js,
);
let symbol = format!("loader_refresh_{}", param.replace('-', "_"));
let change_handler = crate::__private::register_handler(
"change",
"__page__",
&symbol,
&handler_body,
vec![],
vec![],
);
let change_handler = match change_handler {
AttrValue::Handler(h) => h,
_ => HandlerRef {
event: "change".into(),
chunk: "__page__".into(),
symbol: symbol.clone(),
captures: vec![],
inline: Some(handler_body.clone()),
},
};
let mut attrs = vec![
Attr {
name: "type".into(),
value: AttrValue::Static(input_type.into()),
},
Attr {
name: "name".into(),
value: AttrValue::Static(param.into()),
},
Attr {
name: "value".into(),
value: AttrValue::Static(value.into()),
},
Attr {
name: "on:change".into(),
value: AttrValue::Handler(change_handler),
},
];
for (name, value) in extra_attrs {
attrs.push(Attr {
name: name.into(),
value,
});
}
View::Element(Element {
tag: "input".into(),
attrs,
children: vec![],
dom_id: None,
})
}
pub fn loader_refresh_form(path: &str, preserve_from_url: &[&str], children: Vec<Child>) -> View {
let preserve_json = serde_json::to_string(preserve_from_url).unwrap_or_else(|_| "[]".into());
let path_js = serde_json::to_string(path).unwrap_or_else(|_| "\"/\"".into());
let handler_body = format!(
r#"(async (event, state, __resuma) => {{
event.preventDefault();
const form = event.target;
if (!(form instanceof HTMLFormElement)) return;
const fd = new FormData(form);
const params = {{}};
fd.forEach((val, key) => {{ if (String(val)) params[key] = String(val); }});
const q = new URLSearchParams(location.search);
for (const k of {preserve_json}) {{
if (!params[k]) {{
const cur = q.get(k);
if (cur) params[k] = cur;
}}
}}
await __resuma.navigate(__resuma.buildUrl({path_js}, params));
}})"#,
preserve_json = preserve_json,
path_js = path_js,
);
let submit_handler = match crate::__private::register_handler(
"submit",
"__page__",
"loader_refresh_form",
&handler_body,
vec![],
vec![],
) {
AttrValue::Handler(h) => h,
_ => HandlerRef {
event: "submit".into(),
chunk: "__page__".into(),
symbol: "loader_refresh_form".into(),
captures: vec![],
inline: Some(handler_body.clone()),
},
};
View::Element(Element {
tag: "form".into(),
attrs: vec![
Attr {
name: "method".into(),
value: AttrValue::Static("GET".into()),
},
Attr {
name: "action".into(),
value: AttrValue::Static(path.into()),
},
Attr {
name: "data-r-loader-refresh".into(),
value: AttrValue::Static("true".into()),
},
Attr {
name: "on:submit".into(),
value: AttrValue::Handler(submit_handler),
},
],
children,
dom_id: None,
})
}
pub fn theme_into_pwa(theme: &crate::core::Theme, cfg: &mut super::pwa::FlowPwaConfig) {
if !theme.primary.is_empty() {
cfg.theme_color = theme.primary.clone();
}
if !theme.background.is_empty() {
cfg.background_color = theme.background.clone();
}
}
pub fn query_pairs_from(req: &FlowRequest) -> Vec<(String, String)> {
req.query
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect()
}
pub fn pairs_as_refs(pairs: &[(String, String)]) -> Vec<(&str, &str)> {
pairs
.iter()
.map(|(k, v)| (k.as_str(), v.as_str()))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn build_query_href_omits_empty() {
assert_eq!(build_query_href("/r", &[]), "/r");
assert_eq!(
build_query_href("/r", &[("fecha", "2026-06-02"), ("servicio", "")]),
"/r?fecha=2026-06-02"
);
}
}