nova_forms/components/
select.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
use std::{fmt::Display, hash::Hash, str::FromStr};

use crate::{use_translation, QueryString, FieldWiring};
use html::Select;
use leptos::*;
use strum::{IntoEnumIterator, ParseError};

/// A component that renders a select field from an enum.
#[component]
pub fn Select<T>(
    /// The label of the input field.
    #[prop(into)] label: TextProp,
    /// The query string that binds the input field to the form data.
    #[prop(into)] bind: QueryString,
    /// The initial value of the input field.
    #[prop(optional, into)] value: MaybeProp<T>,
    /// A write signal that is updated with the parsed value of the input field.
    #[prop(optional, into)] change: Option<Callback<Result<T, T::Err>, ()>>,
    /// Set a custom error message for the input field.
    #[prop(optional, into)] error: MaybeProp<TextProp>,
) -> impl IntoView
where
    T: IntoEnumIterator + FromStr<Err = ParseError> + Into<&'static str> + Clone + Copy + Default + Eq + Hash + Display + 'static
{    
    let FieldWiring {
        qs,
        value,
        raw_value,
        error,
        set_raw_value,
        render_mode,
        ..
    } = FieldWiring::<T>::wire(label.clone(), bind, value, change, error);

    // Get value on load from the input field.
    let node_ref = NodeRef::<Select>::new();
    node_ref.on_load(move |element| {
        let element: &web_sys::HtmlSelectElement = &*element;
        let value = element.value();
        if !value.is_empty() {
            set_raw_value.call(value);
        }
    });
 
    view! {
        <div
            class="field select"
            class:error=move || error.get().is_some()
            class:ok=move || error.get().is_none()
        >
            <label for=qs.to_string()>{label}</label>
            {move || {
                let qs = qs.clone();

                if render_mode.get() {
                    let text_elem = html::input()
                        .attr("type", "text")
                        .attr("readonly", true)
                        .attr("id", qs.to_string())
                        .attr("name", qs.to_string())
                        .attr("value", move || raw_value.get());

                    text_elem.into_view()
                } else {
                    view! {
                        <select _ref=node_ref id=qs.to_string() name=qs.to_string() on:input=move |ev| {
                            set_raw_value.call(event_target_value(&ev));
                        }>
                            <For
                                each={move || T::iter()}
                                key={|item| *item}
                                children={move |item| {
                                    let option_elem = html::option()
                                        .attr("id", format!("{}({})", qs.to_string(), item.into()))
                                        .attr("selected", move || value.get() == Ok(item))
                                        .attr("value", move || item.into())
                                        .child(use_translation(item));
                                    
                                    view! {
                                        {option_elem}
                                    }
                                }}
                            />
                        </select>
                    }.into_view()
                }
            }}
            {move || {
                if let Some(error) = error.get() {
                    view! { <span class="error-message">{error}</span> }
                        .into_view()
                } else
                if let Some(error) = error.get() {
                    view! { <span class="error-message">{error}</span> }
                        .into_view()
                } else {
                    View::default()
                }
            }}
        </div>
    }
}