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