nova_forms/components/
radio.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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
use std::{fmt::Display, hash::Hash, str::FromStr};

use crate::{use_translation, NovaFormContext, QueryString};
use leptos::*;
use strum::{IntoEnumIterator, ParseError};

/// A component that renders radio buttons from an enum.
#[component]
pub fn Radio<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,
    /// A debug value that is used to autofill the input field in debug mode.
    #[prop(optional, into)] debug_value: Option<T>,
    /// 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)] sync: Option<WriteSignal<T>>,
    /// 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 (qs, form_value) = bind.form_value::<T>();

    let (input_value, set_input_value) = create_signal(None);

    let raw_value = Signal::derive(move || {
        if cfg!(debug_assertions) {
            input_value.get()
                .unwrap_or_else(|| value.get()
                    .or_else(|| form_value.clone().ok())
                    .or_else(|| debug_value.clone())
                    .unwrap_or_else(|| T::default())
                    .into()
                    .to_string())
        } else {
            input_value.get()
                .unwrap_or_else(|| value.get()
                    .or_else(|| form_value.clone().ok())
                    .map(|v| v.into().to_string())
                    .unwrap_or_default())
        }
    });

    let nova_form_context = expect_context::<NovaFormContext>();
    let validation_trigger = nova_form_context.register_input(qs.clone(), label.clone());

    let show_error = Signal::derive(move || {
        input_value.get().is_some() || validation_trigger.get()
    });

    let parsed_value = Signal::derive(move || T::from_str(raw_value.get().as_str()));

    let qs_clone = qs.clone();
    on_cleanup(move || {
        nova_form_context.deregister_input(qs_clone);
    });

    let qs_clone = qs.clone();
    create_effect(move |_| {
        let qs = qs_clone.clone();
        match parsed_value.get() {
            Err(_err) => nova_form_context.set_error(&qs, true),
            Ok(value) => if let Some(sync) = sync {
                sync.set(value);
            }
        }
    });

    /*let input_elem = html::input()
        .attr("type", "radio")
        .attr("id", qs.to_string())
        .attr("name", qs.to_string())
        .attr("checked", move || raw_value.get())
        .attr("value", move || raw_value.get().to_string())
        .on(ev::input, move |ev| {
            set_input_value.set(Some(event_target_value(&ev)));
        });*/

    view! {
        <div
            class="field radio"
            class:error=move || parsed_value.get().is_err() && show_error.get()
            class:ok=move || parsed_value.get().is_ok() && show_error.get()
        >   
            <fieldset>
                <legend>{label}</legend>
                <For
                    each={move || T::iter()}
                    key={|item| *item}
                    children={move |item| {
                        let input_elem = html::input()
                            .attr("type", "radio")
                            .attr("id", format!("{}({})", qs.to_string(), item.into()))
                            .attr("name", qs.to_string())
                            .attr("checked", move || parsed_value.get() == Ok(item))
                            .attr("value", move || item.into())
                            .on(ev::input, move |ev| {
                                set_input_value.set(Some(event_target_value(&ev)));
                            });
                        
                        view! {
                            <label for=format!("{}({})", qs.to_string(), item.into())>
                                {input_elem}
                                <span class="custom-radio"></span>
                                <span class="custom-radio-label">{use_translation(item)}</span>
                            </label>
                        }
                    }}
                />
                {move || {
                    if let Some(error) = error.get() {
                        view! { <span class="error-message">{error}</span> }
                            .into_view()
                    } else
                    if let (Err(err), true) = (
                        parsed_value.get(),
                        show_error.get(),
                    ) {
                        view! { <span class="error-message">{use_translation(err)}</span> }
                            .into_view()
                    } else {
                        View::default()
                    }
                }}
            </fieldset>
            
        </div>
    }
}