use crate::request::{RequestContext, api::Pager}; use crate::ui::htmx::{Event, HTMXAttributes, SwapStrategy}; use crate::ui::prelude::*; use maud::{PreEscaped, html}; /// Represents a search form with configurable options such as heading, placeholder, and CSS class. pub struct Search { post_url: String, heading: Option>, placeholder: Option, search_class: Option, } impl Search { /// Creates a new `Search` instance with the specified post URL. /// /// # Arguments /// * `post_url` - The URL where the search form will send the POST request. /// /// # Returns /// A new `Search` instance with default settings. #[must_use] pub const fn new(post_url: String) -> Self { Self { heading: None, placeholder: None, post_url, search_class: None, } } /// Sets the placeholder text for the search input field. /// /// # Arguments /// * `placeholder` - The placeholder text to display in the search input. /// /// # Returns /// The updated `Search` instance. #[must_use] pub fn placeholder(mut self, placeholder: String) -> Self { self.placeholder = Some(placeholder); self } /// Sets the heading to be displayed above the search form. /// /// # Arguments /// * `heading` - The heading element to display. /// /// # Returns /// The updated `Search` instance. #[must_use] pub fn heading(mut self, heading: PreEscaped) -> Self { self.heading = Some(heading); self } /// Sets the CSS class for the search input field. /// /// # Arguments /// * `class` - The CSS class to apply to the search input. /// /// # Returns /// The updated `Search` instance. #[must_use] pub fn search_class(mut self, class: String) -> Self { self.search_class = Some(class); self } /// Builds the HTML for search results based on the current page and query. /// /// # Arguments /// * `pager` - The `Pager` instance to paginate results. /// * `page` - The current page number. /// * `query` - The search query string. /// * `result_ui` - A function that transforms each result into HTML. /// /// # Returns /// The HTML string containing the search results. pub fn build_results( &self, pager: Pager, page: i64, query: &str, result_ui: impl Fn(&T) -> PreEscaped, ) -> PreEscaped { let results = pager.page(page as u64); let reslen = results.len(); html! { @for res in results { (result_ui(res)) } @if reslen as u64 == pager.items_per_page { (Div() .hx_get( &format!("{}?query={}&page={}", self.post_url, query, page+1) ).hx_trigger( Event::on_revealed() ).hx_swap(SwapStrategy::outerHTML)) }; } } /// Builds the full response based on the context (HTMX or full HTML response). /// /// # Arguments /// * `ctx` - The request context, used to determine if HTMX is enabled. /// * `results` - The `Pager` instance containing the search results. /// * `page` - The current page number. /// * `query` - The search query string. /// * `result_ui` - A function that transforms each result into HTML. /// /// # Returns /// The HTML string containing either the HTMX response or full page content. pub fn build_response( &self, ctx: &RequestContext, results: Pager, page: i64, query: &str, result_ui: impl Fn(&T) -> PreEscaped, ) -> PreEscaped { if ctx.is_htmx { // Return HTMX Search elements self.build_results(results, page, query, result_ui) } else { // Return full rendered site let first_page = self.build_results(results, page, query, result_ui); self.build(query, first_page) } } /// Builds the full search form and first search page results. /// /// # Arguments /// * `query` - The search query string. /// * `first_page` - The HTML string containing the first page of search results. /// /// # Returns /// The HTML string containing the entire search form and results UI. #[must_use] pub fn build(&self, query: &str, first_page: PreEscaped) -> PreEscaped { let no_html = Nothing(); html! { (self.heading.as_ref().unwrap_or_else(|| &no_html)) input type="search" name="query" value=(query) placeholder=(self.placeholder.as_ref().unwrap_or(&"Search...".to_string())) hx-get=(self.post_url) hx-trigger="input changed delay:500ms, keyup[key=='Enter'], load" hx-target="#search_results" hx-push-url="true" class=(self.search_class.as_deref().unwrap_or_default()) {}; div id="search_results" { @if !query.is_empty() { (first_page) } }; } } }