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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
use crate::{controls::ValidationFn, form_builder::FormBuilder, styles::FormStyle};
use leptos::{
    server_fn::{client::Client, codec::PostUrl, request::ClientReq, ServerFn},
    *,
};
use serde::de::DeserializeOwned;
use std::rc::Rc;
use web_sys::FormData;

/// A type that can be used to validate the form data.
///
/// This can be useful to use the same validation logic on the front
/// end and backend without duplicating the logic.
pub struct FormValidator<FD> {
    pub(crate) validations: Vec<Rc<dyn ValidationFn<FD>>>,
}

impl<FD: FormToolData> FormValidator<FD> {
    /// Validates the given form data.
    ///
    /// This runs all the validation functions for all the fields
    /// in the form. The first falure to occur (if any) will be returned.
    pub fn validate(&self, form_data: &FD) -> Result<(), String> {
        for v in self.validations.iter() {
            (*v)(form_data)?;
        }
        Ok(())
    }
}

/// A constructed, rendered form object.
///
/// With this, you can render the form, get the form data, or get
/// a validator for the data.
#[derive(Clone)]
pub struct Form<FD: FormToolData> {
    /// The form data signal.
    pub fd: RwSignal<FD>,
    /// The list of validations
    pub(crate) validations: Vec<Rc<dyn ValidationFn<FD>>>,
    pub(crate) view: View,
}

impl<FD: FormToolData> Form<FD> {
    /// Gets the [`FormValidator`] for this form.
    pub fn validator(&self) -> FormValidator<FD> {
        FormValidator {
            validations: self.validations.clone(),
        }
    }

    /// Validates the [`FormToolData`], returning the result.
    pub fn validate(&self) -> Result<(), String> {
        let validator = self.validator();
        validator.validate(&self.fd.get_untracked())
    }

    /// Gets the view associated with this [`Form`].
    pub fn view(&self) -> View {
        self.view.clone()
    }

    /// Splits this [`Form`] into it's parts.
    pub fn to_parts(self) -> (RwSignal<FD>, FormValidator<FD>, View) {
        (
            self.fd,
            FormValidator {
                validations: self.validations,
            },
            self.view,
        )
    }
}

impl<FD: FormToolData> IntoView for Form<FD> {
    fn into_view(self) -> View {
        self.view()
    }
}

/// A trait allowing a form to be built around its containing data.
///
/// This trait defines a function that can be used to build all the data
/// needed to physically lay out a form, and how that data should be parsed
/// and validated.
pub trait FormToolData: Clone + 'static {
    /// The style that this form uses.
    type Style: FormStyle;
    /// The context that this form is rendered in.
    ///
    /// This will need to be provided when building a form or a validator.
    /// Therefore, you will need to be able to replicate this context
    /// on the client for rendering and the server for validating.
    type Context: 'static;

    /// Defines how the form should be laid out and how the data should be
    /// parsed and validated.
    ///
    /// To construct a [`From`] object, use one of the `get_form` methods.
    ///
    /// Uses the given form builder to specify what fields should be present
    /// in the form, what properties those fields should have, and how that
    /// data should be parsed and checked.
    fn build_form(fb: FormBuilder<Self>) -> FormBuilder<Self>;

    /// Constructs a [`Form`] for this [`FormToolData`] type.
    ///
    /// This renders the form as a enhanced
    /// [`ActionForm`](leptos_router::ActionForm) that sends the form data
    /// directly by calling the server function.
    ///
    /// By doing this, we avoid doing the
    /// [`FromFormData`](leptos_router::FromFormData)
    /// conversion. However, to support
    /// [Progressive Enhancement](https://book.leptos.dev/progressive_enhancement/index.html),
    /// you should name the form elements to work with a plain ActionForm
    /// anyway. If progresssive enhancement is not important to you, you may
    /// freely use this version.
    ///
    /// For the other ways to construct a [`Form`], see:
    /// - [`get_action_form`](Self::get_action_form)
    /// - [`get_plain_form`](Self::get_plain_form)
    /// - [`get_form_controls`](Self::get_form_controls)
    fn get_form<ServFn>(
        self,
        action: Action<ServFn, Result<ServFn::Output, ServerFnError<ServFn::Error>>>,
        style: Self::Style,
        context: Self::Context,
    ) -> Form<Self>
    where
        ServFn: DeserializeOwned + ServerFn<InputEncoding = PostUrl> + 'static,
        <<ServFn::Client as Client<ServFn::Error>>::Request as ClientReq<ServFn::Error>>::FormData:
            From<FormData>,
        ServFn: From<Self>,
    {
        let builder = FormBuilder::new(context);
        let builder = Self::build_form(builder);
        builder.build_form(action, self, style)
    }

    /// Constructs a [`Form`] for this [`FormToolData`] type.
    ///
    /// This renders the form as a the leptos_router
    /// [`ActionForm`](leptos_router::ActionForm)
    /// component.
    ///
    /// For the other ways to construct a [`Form`], see:
    /// - [`get_form`](Self::get_form)
    /// - [`get_plain_form`](Self::get_plain_form)
    /// - [`get_form_controls`](Self::get_form_controls)
    fn get_action_form<ServFn>(
        self,
        action: Action<ServFn, Result<ServFn::Output, ServerFnError<ServFn::Error>>>,
        style: Self::Style,
        context: Self::Context,
    ) -> Form<Self>
    where
        ServFn: DeserializeOwned + ServerFn<InputEncoding = PostUrl> + 'static,
        <<ServFn::Client as Client<ServFn::Error>>::Request as ClientReq<ServFn::Error>>::FormData:
            From<FormData>,
    {
        let builder = FormBuilder::new(context);
        let builder = Self::build_form(builder);
        builder.build_action_form(action, self, style)
    }

    /// Constructs a [`Form`] for this [`FormToolData`] type.
    ///
    /// This renders the form as a the leptos_router
    /// [`Form`](leptos_router::Form)
    /// component.
    ///
    /// For the other ways to construct a [`Form`], see:
    /// - [`get_form`](Self::get_form)
    /// - [`get_action_form`](Self::get_action_form)
    /// - [`get_form_controls`](Self::get_form_controls)
    fn get_plain_form(
        self,
        action: impl ToString,
        style: Self::Style,
        context: Self::Context,
    ) -> Form<Self> {
        let builder = FormBuilder::new(context);
        let builder = Self::build_form(builder);
        builder.build_plain_form(action.to_string(), self, style)
    }

    /// Constructs a [`Form`] for this [`FormToolData`] type.
    ///
    /// This renders the form without wrapping it in any form html elements.
    /// This can be useful if you want to do that yourself, or if you are
    /// just using the FormData signal for some non-form purpose.
    ///
    /// For the other ways to construct a [`Form`], see:
    /// - [`get_form`](Self::get_form)
    /// - [`get_action_form`](Self::get_action_form)
    /// - [`get_plain_form`](Self::get_plain_form)
    fn get_form_controls(self, style: Self::Style, context: Self::Context) -> Form<Self> {
        let builder = FormBuilder::new(context);
        let builder = Self::build_form(builder);
        builder.build_form_controls(self, style)
    }

    /// Gets a [`FormValidator`] for this [`FormToolData`].
    ///
    /// This doesn't render the view, but just collects all the validation
    /// Functions from building the form. That means it can be called on the
    /// Server and no rendering will be done.
    ///
    /// However, the code to render the views are not configured out, it
    /// simply doesn't run, so the view needs to compile even on the server.
    fn get_validator(context: Self::Context) -> FormValidator<Self> {
        let builder = FormBuilder::new(context);
        let builder = Self::build_form(builder);
        builder.validator()
    }

    /// Validates this [`FormToolData`] struct.
    ///
    /// This is shorthand for creating a validator with
    /// [`get_validator`](Self::get_validator)()
    /// and then calling `validator.validate(&self, context)`.
    fn validate(&self, context: Self::Context) -> Result<(), String> {
        let validator = Self::get_validator(context);
        validator.validate(self)
    }
}