vertigo_forms/
search_panel.rs1use std::rc::Rc;
2
3use vertigo::{AutoMap, DomNode, Resource, ToComputed, Value, bind, dom};
4
5pub trait SearchResult {
6 fn is_empty(&self) -> bool;
7}
8
9impl<T> SearchResult for Vec<T> {
10 fn is_empty(&self) -> bool {
11 self.is_empty()
12 }
13}
14
15pub struct SearchPanel<T, K>
17where
18 T: Clone,
19 K: ToComputed<Resource<Rc<T>>>,
20{
21 pub query: Value<String>,
22 pub cache: AutoMap<String, K>,
23 pub render_results: Rc<dyn Fn(Rc<T>) -> DomNode>,
24 pub params: SearchPanelParams,
25}
26
27#[derive(Clone)]
28pub struct SearchPanelParams {
29 pub min_chars: usize,
30 pub prompt: String,
31 pub hint: String,
32 pub loading_text: String,
33 pub empty_text: String,
34}
35
36impl Default for SearchPanelParams {
37 fn default() -> Self {
38 Self {
39 min_chars: 3,
40 prompt: "Search: ".to_string(),
41 hint: "Enter at least {min_chars} letters".to_string(),
42 loading_text: "Loading...".to_string(),
43 empty_text: "No results".to_string(),
44 }
45 }
46}
47
48impl<T, K> SearchPanel<T, K>
49where
50 T: SearchResult + PartialEq + Clone + 'static,
51 K: ToComputed<Resource<Rc<T>>> + Clone + 'static,
52{
53 pub fn into_component(self) -> Self {
54 self
55 }
56
57 pub fn mount(self) -> DomNode {
58 let Self {
59 query,
60 cache,
61 render_results,
62 params,
63 } = self;
64 let prompt = params.prompt.clone();
65 let content = query.render_value(move |query| {
66 let SearchPanelParams {
67 min_chars,
68 prompt: _,
69 hint,
70 loading_text,
71 empty_text,
72 } = params.clone();
73 if query.len() < min_chars {
74 let msg = hint.replace("{min_chars}", &min_chars.to_string());
75 return dom! { <div>{msg}</div> };
76 }
77 let render_results = render_results.clone();
78 let content = cache
79 .get(&query)
80 .to_computed()
81 .render_value(move |books| match books {
82 Resource::Loading => dom! { <div>{loading_text.clone()}</div> },
83 Resource::Ready(dataset) => {
84 if !dataset.is_empty() {
85 render_results(dataset)
86 } else {
87 dom! {
88 <div>{empty_text.clone()}</div>
89 }
90 }
91 }
92 Resource::Error(err) => {
93 dom! { <div>{err}</div> }
94 }
95 });
96 dom! { <div>{content}</div> }
97 });
98
99 let on_input = bind!(query, |new_value: String| {
100 query.set(new_value);
101 });
102
103 let value = query.to_computed();
104
105 dom! {
106 <div>
107 { prompt }
108 <input {value} on_input={on_input}/>
109 { content }
110 </div>
111 }
112 }
113}