generated from mitchell/rust_template
Compare commits
No commits in common. "a5fd4f85ee182852d61e60b244282d535a1f3086" and "1dc676cc370b97ee017c31af1f356793164a001c" have entirely different histories.
a5fd4f85ee
...
1dc676cc37
@ -1,67 +1,48 @@
|
|||||||
use super::ControlRenderData;
|
use super::{ControlRenderData, VanityControlBuilder, VanityControlData};
|
||||||
use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
|
use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
|
||||||
use leptos::RwSignal;
|
use leptos::{RwSignal, Signal, View};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use web_sys::MouseEvent;
|
use web_sys::MouseEvent;
|
||||||
|
|
||||||
type ButtonAction<FD> = dyn Fn(MouseEvent, &mut FD);
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ButtonData<FD: FormToolData> {
|
pub struct ButtonData<FD: FormToolData> {
|
||||||
pub(crate) text: String,
|
pub(crate) text: String,
|
||||||
pub(crate) action: Option<Rc<ButtonAction<FD>>>,
|
pub(crate) action: Option<Rc<dyn Fn(MouseEvent, &mut FD)>>,
|
||||||
|
|
||||||
|
// this will need to be set before calling the build method
|
||||||
|
pub(crate) fd_signal: RwSignal<FD>,
|
||||||
}
|
}
|
||||||
impl<FD: FormToolData> Default for ButtonData<FD> {
|
|
||||||
fn default() -> Self {
|
impl<FD: FormToolData> VanityControlData for ButtonData<FD> {
|
||||||
ButtonData {
|
fn build_control<FS: FormStyle>(
|
||||||
text: String::default(),
|
fs: &FS,
|
||||||
action: None,
|
control: ControlRenderData<FS, Self>,
|
||||||
}
|
_value_getter: Option<Signal<String>>,
|
||||||
|
) -> View {
|
||||||
|
fs.button(control)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
|
impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
|
||||||
pub fn button(
|
pub fn button(
|
||||||
mut self,
|
mut self,
|
||||||
builder: impl Fn(ButtonBuilder<FD, FS>) -> ButtonBuilder<FD, FS>,
|
builder: impl Fn(
|
||||||
|
VanityControlBuilder<FD, FS, ButtonData<FD>>,
|
||||||
|
) -> VanityControlBuilder<FD, FS, ButtonData<FD>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let button_builder = ButtonBuilder::new();
|
let data = ButtonData {
|
||||||
let control = builder(button_builder);
|
text: String::default(),
|
||||||
|
action: None,
|
||||||
let render_data = ControlRenderData {
|
fd_signal: self.fd,
|
||||||
data: control.data,
|
|
||||||
styles: control.styles,
|
|
||||||
};
|
};
|
||||||
|
let vanity_builder = VanityControlBuilder::new(data);
|
||||||
let render_fn = move |fs: &FS, fd: RwSignal<FD>| {
|
let control = builder(vanity_builder);
|
||||||
let view = fs.button(render_data, fd);
|
self.add_vanity(control);
|
||||||
(view, None)
|
|
||||||
};
|
|
||||||
self.render_fns.push(Box::new(render_fn));
|
|
||||||
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
impl<FD: FormToolData, FS: FormStyle> VanityControlBuilder<FD, FS, ButtonData<FD>> {
|
||||||
pub struct ButtonBuilder<FD: FormToolData, FS: FormStyle> {
|
|
||||||
pub(crate) styles: Vec<FS::StylingAttributes>,
|
|
||||||
pub(crate) data: ButtonData<FD>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<FD: FormToolData, FS: FormStyle> ButtonBuilder<FD, FS> {
|
|
||||||
fn new() -> Self {
|
|
||||||
ButtonBuilder {
|
|
||||||
styles: Vec::default(),
|
|
||||||
data: ButtonData::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn style(mut self, style: FS::StylingAttributes) -> Self {
|
|
||||||
self.styles.push(style);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn text(mut self, text: impl ToString) -> Self {
|
pub fn text(mut self, text: impl ToString) -> Self {
|
||||||
self.data.text = text.to_string();
|
self.data.text = text.to_string();
|
||||||
self
|
self
|
||||||
|
|||||||
@ -4,8 +4,9 @@ use leptos::{CollectView, RwSignal};
|
|||||||
|
|
||||||
impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
|
impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
|
||||||
pub fn group(mut self, builder: impl Fn(FormBuilder<FD, FS>) -> FormBuilder<FD, FS>) -> Self {
|
pub fn group(mut self, builder: impl Fn(FormBuilder<FD, FS>) -> FormBuilder<FD, FS>) -> Self {
|
||||||
let mut group_builder = FormBuilder::new();
|
let mut group_builder = FormBuilder::new_group(self.fd, self.fs);
|
||||||
group_builder = builder(group_builder);
|
group_builder = builder(group_builder);
|
||||||
|
self.fs = group_builder.fs; // take the style back
|
||||||
|
|
||||||
for validation in group_builder.validations {
|
for validation in group_builder.validations {
|
||||||
self.validations.push(validation);
|
self.validations.push(validation);
|
||||||
@ -15,13 +16,10 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
|
|||||||
let (views, validation_cbs): (Vec<_>, Vec<_>) = group_builder
|
let (views, validation_cbs): (Vec<_>, Vec<_>) = group_builder
|
||||||
.render_fns
|
.render_fns
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|r_fn| r_fn(fs, fd))
|
.map(|r_fn| r_fn(&fs, fd))
|
||||||
.unzip();
|
.unzip();
|
||||||
|
|
||||||
let view = fs.group(super::ControlRenderData {
|
let view = fs.group(views.collect_view(), group_builder.styles);
|
||||||
data: views.collect_view(),
|
|
||||||
styles: group_builder.styles,
|
|
||||||
});
|
|
||||||
let validation_cb = move || {
|
let validation_cb = move || {
|
||||||
let mut success = true;
|
let mut success = true;
|
||||||
for validation in validation_cbs.iter().flatten() {
|
for validation in validation_cbs.iter().flatten() {
|
||||||
|
|||||||
@ -16,6 +16,7 @@ impl VanityControlData for HeadingData {
|
|||||||
fs.heading(control)
|
fs.heading(control)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// TODO: impl GetterVanityControl
|
||||||
|
|
||||||
impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
|
impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
|
||||||
pub fn heading(
|
pub fn heading(
|
||||||
|
|||||||
@ -70,8 +70,8 @@ pub trait ValidatedControlData: ControlData {}
|
|||||||
|
|
||||||
/// The data needed to render a interactive control of type `C`.
|
/// The data needed to render a interactive control of type `C`.
|
||||||
pub struct ControlRenderData<FS: FormStyle + ?Sized, C: ?Sized> {
|
pub struct ControlRenderData<FS: FormStyle + ?Sized, C: ?Sized> {
|
||||||
pub styles: Vec<FS::StylingAttributes>,
|
pub data: Box<C>,
|
||||||
pub data: C,
|
pub style: Vec<FS::StylingAttributes>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The data needed to render a read-only control of type `C`.
|
/// The data needed to render a read-only control of type `C`.
|
||||||
@ -100,8 +100,8 @@ impl<FD: FormToolData, FS: FormStyle, C: VanityControlData> VanityControlBuilder
|
|||||||
pub(crate) fn build(self) -> BuiltVanityControlData<FD, FS, C> {
|
pub(crate) fn build(self) -> BuiltVanityControlData<FD, FS, C> {
|
||||||
BuiltVanityControlData {
|
BuiltVanityControlData {
|
||||||
render_data: ControlRenderData {
|
render_data: ControlRenderData {
|
||||||
data: self.data,
|
data: Box::new(self.data),
|
||||||
styles: self.style_attributes,
|
style: self.style_attributes,
|
||||||
},
|
},
|
||||||
getter: self.getter,
|
getter: self.getter,
|
||||||
}
|
}
|
||||||
@ -208,8 +208,8 @@ impl<FD: FormToolData, FS: FormStyle, C: ControlData, FDT> ControlBuilder<FD, FS
|
|||||||
|
|
||||||
Ok(BuiltControlData {
|
Ok(BuiltControlData {
|
||||||
render_data: ControlRenderData {
|
render_data: ControlRenderData {
|
||||||
data: self.data,
|
data: Box::new(self.data),
|
||||||
styles: self.style_attributes,
|
style: self.style_attributes,
|
||||||
},
|
},
|
||||||
getter,
|
getter,
|
||||||
setter,
|
setter,
|
||||||
|
|||||||
@ -1,12 +1,15 @@
|
|||||||
|
use leptos::{Signal, View};
|
||||||
|
|
||||||
use super::{ControlBuilder, ControlData, ControlRenderData, ValidatedControlData};
|
use super::{ControlBuilder, ControlData, ControlRenderData, ValidatedControlData};
|
||||||
use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
|
use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
|
||||||
use leptos::{Signal, View};
|
|
||||||
|
// TODO: have an option to have a display string and a value string in the options field
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||||
pub struct SelectData {
|
pub struct SelectData {
|
||||||
pub(crate) name: String,
|
pub(crate) name: String,
|
||||||
pub(crate) label: Option<String>,
|
pub(crate) label: Option<String>,
|
||||||
pub(crate) options: Vec<(String, String)>,
|
pub(crate) options: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ControlData for SelectData {
|
impl ControlData for SelectData {
|
||||||
@ -47,36 +50,13 @@ impl<FD: FormToolData, FS: FormStyle, FDT> ControlBuilder<FD, FS, SelectData, FD
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_option(mut self, option: impl ToString) -> Self {
|
pub fn with_option(mut self, option: impl ToString) -> Self {
|
||||||
self.data
|
self.data.options.push(option.to_string());
|
||||||
.options
|
|
||||||
.push((option.to_string(), option.to_string()));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_option_valued(mut self, display: impl ToString, value: impl ToString) -> Self {
|
|
||||||
self.data
|
|
||||||
.options
|
|
||||||
.push((display.to_string(), value.to_string()));
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with_options(mut self, options: impl Iterator<Item = impl ToString>) -> Self {
|
pub fn with_options(mut self, options: impl Iterator<Item = impl ToString>) -> Self {
|
||||||
for option in options {
|
for option in options {
|
||||||
self.data
|
self.data.options.push(option.to_string());
|
||||||
.options
|
|
||||||
.push((option.to_string(), option.to_string()));
|
|
||||||
}
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn with_options_valued(
|
|
||||||
mut self,
|
|
||||||
options: impl Iterator<Item = (impl ToString, impl ToString)>,
|
|
||||||
) -> Self {
|
|
||||||
for option in options {
|
|
||||||
self.data
|
|
||||||
.options
|
|
||||||
.push((option.0.to_string(), option.1.to_string()));
|
|
||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|||||||
12
src/form.rs
12
src/form.rs
@ -78,7 +78,7 @@ impl<FD: FormToolData> IntoView for Form<FD> {
|
|||||||
///
|
///
|
||||||
/// This trait defines a function that can be used to build all the data needed
|
/// 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.
|
/// to physically lay out a form, and how that data should be parsed and validated.
|
||||||
pub trait FormToolData: Clone + 'static {
|
pub trait FormToolData: Default + Clone + 'static {
|
||||||
type Style: FormStyle;
|
type Style: FormStyle;
|
||||||
|
|
||||||
/// Defines how the form should be layed out and how the data should be parsed and validated.
|
/// Defines how the form should be layed out and how the data should be parsed and validated.
|
||||||
@ -97,9 +97,9 @@ pub trait FormToolData: Clone + 'static {
|
|||||||
/// component. Call [`get_action_form`]\() to get the
|
/// component. Call [`get_action_form`]\() to get the
|
||||||
/// [`ActionForm`](leptos_router::ActionForm) version.
|
/// [`ActionForm`](leptos_router::ActionForm) version.
|
||||||
fn get_form(self, action: impl ToString, style: Self::Style) -> Form<Self> {
|
fn get_form(self, action: impl ToString, style: Self::Style) -> Form<Self> {
|
||||||
let builder = FormBuilder::new();
|
let builder = FormBuilder::new(self, style);
|
||||||
let builder = Self::build_form(builder);
|
let builder = Self::build_form(builder);
|
||||||
builder.build_plain_form(action.to_string(), self, style)
|
builder.build_plain_form(action.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Constructs a [`Form`] for this [`FormToolData`] type.
|
/// Constructs a [`Form`] for this [`FormToolData`] type.
|
||||||
@ -118,9 +118,9 @@ pub trait FormToolData: Clone + 'static {
|
|||||||
<<ServFn::Client as Client<ServFn::Error>>::Request as ClientReq<ServFn::Error>>::FormData:
|
<<ServFn::Client as Client<ServFn::Error>>::Request as ClientReq<ServFn::Error>>::FormData:
|
||||||
From<FormData>,
|
From<FormData>,
|
||||||
{
|
{
|
||||||
let builder = FormBuilder::new();
|
let builder = FormBuilder::new(self, style);
|
||||||
let builder = Self::build_form(builder);
|
let builder = Self::build_form(builder);
|
||||||
builder.build_action_form(action, self, style)
|
builder.build_action_form(action)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets a [`Validator`] for this [`ToolFormData`].
|
/// Gets a [`Validator`] for this [`ToolFormData`].
|
||||||
@ -132,7 +132,7 @@ pub trait FormToolData: Clone + 'static {
|
|||||||
/// However, the code to render the views are not configured out, it
|
/// 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.
|
/// simply doesn't run, so the view needs to compile even on the server.
|
||||||
fn get_validator() -> FormValidator<Self> {
|
fn get_validator() -> FormValidator<Self> {
|
||||||
let builder = FormBuilder::new();
|
let builder = FormBuilder::new(Self::default(), Self::Style::default());
|
||||||
let builder = Self::build_form(builder);
|
let builder = Self::build_form(builder);
|
||||||
builder.validator()
|
builder.validator()
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
controls::{
|
controls::{
|
||||||
BuiltControlData, BuiltVanityControlData, ControlBuilder, ControlData, ControlRenderData,
|
BuiltControlData, BuiltVanityControlData, ControlBuilder, ControlData, FieldGetter,
|
||||||
FieldSetter, ParseFn, RenderFn, ValidationCb, ValidationFn, VanityControlBuilder,
|
FieldSetter, ParseFn, RenderFn, UnparseFn, ValidationCb, ValidationFn,
|
||||||
VanityControlData,
|
VanityControlBuilder, VanityControlData,
|
||||||
},
|
},
|
||||||
form::{Form, FormToolData, FormValidator},
|
form::{Form, FormToolData, FormValidator},
|
||||||
styles::FormStyle,
|
styles::FormStyle,
|
||||||
@ -20,6 +20,10 @@ use web_sys::{FormData, SubmitEvent};
|
|||||||
///
|
///
|
||||||
/// This builder allows you to specify what components should make up the form.
|
/// This builder allows you to specify what components should make up the form.
|
||||||
pub struct FormBuilder<FD: FormToolData, FS: FormStyle> {
|
pub struct FormBuilder<FD: FormToolData, FS: FormStyle> {
|
||||||
|
/// The [`ToolFormData`] signal.
|
||||||
|
pub(crate) fd: RwSignal<FD>,
|
||||||
|
/// The [`FormStyle`].
|
||||||
|
pub(crate) fs: FS,
|
||||||
/// The list of [`ValidationFn`]s.
|
/// The list of [`ValidationFn`]s.
|
||||||
pub(crate) validations: Vec<Rc<dyn ValidationFn<FD>>>,
|
pub(crate) validations: Vec<Rc<dyn ValidationFn<FD>>>,
|
||||||
/// The list of functions that will render the form.
|
/// The list of functions that will render the form.
|
||||||
@ -30,8 +34,21 @@ pub struct FormBuilder<FD: FormToolData, FS: FormStyle> {
|
|||||||
|
|
||||||
impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
|
impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
|
||||||
/// Creates a new [`FormBuilder`]
|
/// Creates a new [`FormBuilder`]
|
||||||
pub(crate) fn new() -> FormBuilder<FD, FS> {
|
pub(crate) fn new(starting_data: FD, form_style: FS) -> FormBuilder<FD, FS> {
|
||||||
|
let fd = create_rw_signal(starting_data);
|
||||||
FormBuilder {
|
FormBuilder {
|
||||||
|
fd,
|
||||||
|
fs: form_style,
|
||||||
|
validations: Vec::new(),
|
||||||
|
render_fns: Vec::new(),
|
||||||
|
styles: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn new_group(fd: RwSignal<FD>, fs: FS) -> FormBuilder<FD, FS> {
|
||||||
|
FormBuilder {
|
||||||
|
fd,
|
||||||
|
fs,
|
||||||
validations: Vec::new(),
|
validations: Vec::new(),
|
||||||
render_fns: Vec::new(),
|
render_fns: Vec::new(),
|
||||||
styles: Vec::new(),
|
styles: Vec::new(),
|
||||||
@ -87,17 +104,33 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
|
|||||||
&mut self,
|
&mut self,
|
||||||
control: ControlBuilder<FD, FS, C, FDT>,
|
control: ControlBuilder<FD, FS, C, FDT>,
|
||||||
) {
|
) {
|
||||||
let built_control_data = match control.build() {
|
let BuiltControlData {
|
||||||
|
render_data,
|
||||||
|
getter,
|
||||||
|
setter,
|
||||||
|
parse_fn,
|
||||||
|
unparse_fn,
|
||||||
|
validation_fn,
|
||||||
|
} = match control.build() {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(e) => panic!("Invalid Component: {}", e),
|
Err(e) => panic!("Invalid Component: {}", e),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(ref validation_fn) = built_control_data.validation_fn {
|
if let Some(ref validation_fn) = validation_fn {
|
||||||
self.validations.push(validation_fn.clone());
|
self.validations.push(validation_fn.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let render_fn = move |fs: &FS, fd: RwSignal<FD>| {
|
let render_fn = move |fs: &FS, fd: RwSignal<FD>| {
|
||||||
let (view, cb) = Self::build_control_view(fd, fs, built_control_data);
|
let (view, cb) = Self::build_control_view(
|
||||||
|
fd,
|
||||||
|
fs,
|
||||||
|
getter,
|
||||||
|
setter,
|
||||||
|
unparse_fn,
|
||||||
|
parse_fn,
|
||||||
|
validation_fn,
|
||||||
|
render_data,
|
||||||
|
);
|
||||||
(view, Some(cb))
|
(view, Some(cb))
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -107,17 +140,13 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
|
|||||||
fn build_control_view<C: ControlData, FDT: 'static>(
|
fn build_control_view<C: ControlData, FDT: 'static>(
|
||||||
fd: RwSignal<FD>,
|
fd: RwSignal<FD>,
|
||||||
fs: &FS,
|
fs: &FS,
|
||||||
control_data: BuiltControlData<FD, FS, C, FDT>,
|
getter: Rc<dyn FieldGetter<FD, FDT>>,
|
||||||
|
setter: Rc<dyn FieldSetter<FD, FDT>>,
|
||||||
|
unparse_fn: Box<dyn UnparseFn<<C as ControlData>::ReturnType, FDT>>,
|
||||||
|
parse_fn: Box<dyn ParseFn<<C as ControlData>::ReturnType, FDT>>,
|
||||||
|
validation_fn: Option<Rc<dyn ValidationFn<FD>>>,
|
||||||
|
render_data: crate::controls::ControlRenderData<FS, C>,
|
||||||
) -> (View, Box<dyn ValidationCb>) {
|
) -> (View, Box<dyn ValidationCb>) {
|
||||||
let BuiltControlData {
|
|
||||||
render_data,
|
|
||||||
getter,
|
|
||||||
setter,
|
|
||||||
parse_fn,
|
|
||||||
unparse_fn,
|
|
||||||
validation_fn,
|
|
||||||
} = control_data;
|
|
||||||
|
|
||||||
let (validation_signal, validation_signal_set) = create_signal(Ok(()));
|
let (validation_signal, validation_signal_set) = create_signal(Ok(()));
|
||||||
let validation_fn_clone = validation_fn.clone();
|
let validation_fn_clone = validation_fn.clone();
|
||||||
let value_getter = move || {
|
let value_getter = move || {
|
||||||
@ -203,26 +232,19 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
|
|||||||
pub(crate) fn build_action_form<ServFn>(
|
pub(crate) fn build_action_form<ServFn>(
|
||||||
self,
|
self,
|
||||||
action: Action<ServFn, Result<ServFn::Output, ServerFnError<ServFn::Error>>>,
|
action: Action<ServFn, Result<ServFn::Output, ServerFnError<ServFn::Error>>>,
|
||||||
fd: FD,
|
|
||||||
fs: FS,
|
|
||||||
) -> Form<FD>
|
) -> Form<FD>
|
||||||
where
|
where
|
||||||
ServFn: DeserializeOwned + ServerFn<InputEncoding = PostUrl> + 'static,
|
ServFn: DeserializeOwned + ServerFn<InputEncoding = PostUrl> + 'static,
|
||||||
<<ServFn::Client as Client<ServFn::Error>>::Request as ClientReq<ServFn::Error>>::FormData:
|
<<ServFn::Client as Client<ServFn::Error>>::Request as ClientReq<ServFn::Error>>::FormData:
|
||||||
From<FormData>,
|
From<FormData>,
|
||||||
{
|
{
|
||||||
let fd = create_rw_signal(fd);
|
|
||||||
|
|
||||||
let (views, validation_cbs): (Vec<_>, Vec<_>) = self
|
let (views, validation_cbs): (Vec<_>, Vec<_>) = self
|
||||||
.render_fns
|
.render_fns
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|r_fn| r_fn(&fs, fd))
|
.map(|r_fn| r_fn(&self.fs, self.fd))
|
||||||
.unzip();
|
.unzip();
|
||||||
|
|
||||||
let elements = fs.form_frame(ControlRenderData {
|
let elements = self.fs.form_frame(views.into_view(), self.styles);
|
||||||
data: views.into_view(),
|
|
||||||
styles: self.styles,
|
|
||||||
});
|
|
||||||
|
|
||||||
let on_submit = move |ev: SubmitEvent| {
|
let on_submit = move |ev: SubmitEvent| {
|
||||||
let mut failed = false;
|
let mut failed = false;
|
||||||
@ -243,25 +265,20 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Form {
|
Form {
|
||||||
fd,
|
fd: self.fd,
|
||||||
validations: self.validations,
|
validations: self.validations,
|
||||||
view,
|
view,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn build_plain_form(self, url: String, fd: FD, fs: FS) -> Form<FD> {
|
pub(crate) fn build_plain_form(self, url: String) -> Form<FD> {
|
||||||
let fd = create_rw_signal(fd);
|
|
||||||
|
|
||||||
let (views, validation_cbs): (Vec<_>, Vec<_>) = self
|
let (views, validation_cbs): (Vec<_>, Vec<_>) = self
|
||||||
.render_fns
|
.render_fns
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|r_fn| r_fn(&fs, fd))
|
.map(|r_fn| r_fn(&self.fs, self.fd))
|
||||||
.unzip();
|
.unzip();
|
||||||
|
|
||||||
let elements = fs.form_frame(ControlRenderData {
|
let elements = self.fs.form_frame(views.into_view(), self.styles);
|
||||||
data: views.into_view(),
|
|
||||||
styles: self.styles,
|
|
||||||
});
|
|
||||||
|
|
||||||
let on_submit = move |ev: SubmitEvent| {
|
let on_submit = move |ev: SubmitEvent| {
|
||||||
let mut failed = false;
|
let mut failed = false;
|
||||||
@ -282,7 +299,7 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
Form {
|
Form {
|
||||||
fd,
|
fd: self.fd,
|
||||||
validations: self.validations,
|
validations: self.validations,
|
||||||
view,
|
view,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,8 @@
|
|||||||
use super::FormStyle;
|
use super::FormStyle;
|
||||||
use crate::{
|
use crate::controls::{
|
||||||
controls::{
|
checkbox::CheckboxData, heading::HeadingData, hidden::HiddenData, output::OutputData,
|
||||||
button::ButtonData, checkbox::CheckboxData, heading::HeadingData, hidden::HiddenData,
|
select::SelectData, spacer::SpacerData, submit::SubmitData, text_area::TextAreaData,
|
||||||
output::OutputData, select::SelectData, spacer::SpacerData, submit::SubmitData,
|
text_input::TextInputData, ControlData, ControlRenderData,
|
||||||
text_area::TextAreaData, text_input::TextInputData, ControlData, ControlRenderData,
|
|
||||||
},
|
|
||||||
FormToolData,
|
|
||||||
};
|
};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
@ -21,8 +18,8 @@ pub struct GridFormStyle;
|
|||||||
impl FormStyle for GridFormStyle {
|
impl FormStyle for GridFormStyle {
|
||||||
type StylingAttributes = GridFormStylingAttributes;
|
type StylingAttributes = GridFormStylingAttributes;
|
||||||
|
|
||||||
fn form_frame(&self, form: ControlRenderData<Self, View>) -> View {
|
fn form_frame(&self, children: View, _styles: Vec<Self::StylingAttributes>) -> View {
|
||||||
view! { <div class="form_grid">{form.data}</div> }.into_view()
|
view! { <div class="form_grid">{children}</div> }.into_view()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn heading(&self, control: ControlRenderData<Self, HeadingData>) -> View {
|
fn heading(&self, control: ControlRenderData<Self, HeadingData>) -> View {
|
||||||
@ -38,7 +35,7 @@ impl FormStyle for GridFormStyle {
|
|||||||
) -> View {
|
) -> View {
|
||||||
// TODO: extract this to a common thing
|
// TODO: extract this to a common thing
|
||||||
let mut width = 1;
|
let mut width = 1;
|
||||||
for style in control.styles {
|
for style in control.style {
|
||||||
match style {
|
match style {
|
||||||
GridFormStylingAttributes::Width(w) => width = w,
|
GridFormStylingAttributes::Width(w) => width = w,
|
||||||
}
|
}
|
||||||
@ -81,10 +78,14 @@ impl FormStyle for GridFormStyle {
|
|||||||
.data
|
.data
|
||||||
.options
|
.options
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(display, value)| {
|
.map(|value| {
|
||||||
|
let cloned_value = value.clone();
|
||||||
view! {
|
view! {
|
||||||
<option value=value.clone() selected=move || { value_getter.get() == *value }>
|
<option
|
||||||
{display.clone()}
|
value=value.clone()
|
||||||
|
selected=move || {value_getter.get() == *cloned_value}
|
||||||
|
>
|
||||||
|
{value.clone()}
|
||||||
</option>
|
</option>
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -111,7 +112,10 @@ impl FormStyle for GridFormStyle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn submit(&self, control: ControlRenderData<Self, SubmitData>) -> View {
|
fn submit(&self, control: ControlRenderData<Self, SubmitData>) -> View {
|
||||||
view! { <input type="submit" value=control.data.text class="form_submit"/> }.into_view()
|
view! {
|
||||||
|
<input type="submit" value=control.data.text class="form_submit"/>
|
||||||
|
}
|
||||||
|
.into_view()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn text_area(
|
fn text_area(
|
||||||
@ -133,8 +137,7 @@ impl FormStyle for GridFormStyle {
|
|||||||
on:change=move |ev| {
|
on:change=move |ev| {
|
||||||
value_setter(event_target_value(&ev));
|
value_setter(event_target_value(&ev));
|
||||||
}
|
}
|
||||||
>
|
></textarea>
|
||||||
</textarea>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@ -165,7 +168,7 @@ impl FormStyle for GridFormStyle {
|
|||||||
validation_state: Signal<Result<(), String>>,
|
validation_state: Signal<Result<(), String>>,
|
||||||
) -> View {
|
) -> View {
|
||||||
let mut width = 1;
|
let mut width = 1;
|
||||||
for style in control.styles {
|
for style in control.style {
|
||||||
match style {
|
match style {
|
||||||
GridFormStylingAttributes::Width(w) => width = w,
|
GridFormStylingAttributes::Width(w) => width = w,
|
||||||
}
|
}
|
||||||
@ -195,7 +198,6 @@ impl FormStyle for GridFormStyle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<label for=&o>{&o}</label>
|
<label for=&o>{&o}</label>
|
||||||
<br/>
|
<br/>
|
||||||
}
|
}
|
||||||
@ -228,7 +230,7 @@ impl FormStyle for GridFormStyle {
|
|||||||
value_setter: Box<dyn Fn(<CheckboxData as ControlData>::ReturnType)>,
|
value_setter: Box<dyn Fn(<CheckboxData as ControlData>::ReturnType)>,
|
||||||
) -> View {
|
) -> View {
|
||||||
let mut width = 1;
|
let mut width = 1;
|
||||||
for style in control.styles {
|
for style in control.style {
|
||||||
match style {
|
match style {
|
||||||
GridFormStylingAttributes::Width(w) => width = w,
|
GridFormStylingAttributes::Width(w) => width = w,
|
||||||
}
|
}
|
||||||
@ -268,7 +270,7 @@ impl FormStyle for GridFormStyle {
|
|||||||
validation_state: Signal<Result<(), String>>,
|
validation_state: Signal<Result<(), String>>,
|
||||||
) -> View {
|
) -> View {
|
||||||
let mut width = 1;
|
let mut width = 1;
|
||||||
for style in control.styles {
|
for style in control.style {
|
||||||
match style {
|
match style {
|
||||||
GridFormStylingAttributes::Width(w) => width = w,
|
GridFormStylingAttributes::Width(w) => width = w,
|
||||||
}
|
}
|
||||||
@ -315,7 +317,7 @@ impl FormStyle for GridFormStyle {
|
|||||||
validation_state: Signal<Result<(), String>>,
|
validation_state: Signal<Result<(), String>>,
|
||||||
) -> View {
|
) -> View {
|
||||||
let mut width = 1;
|
let mut width = 1;
|
||||||
for style in control.styles {
|
for style in control.style {
|
||||||
match style {
|
match style {
|
||||||
GridFormStylingAttributes::Width(w) => width = w,
|
GridFormStylingAttributes::Width(w) => width = w,
|
||||||
}
|
}
|
||||||
@ -350,41 +352,37 @@ impl FormStyle for GridFormStyle {
|
|||||||
.into_view()
|
.into_view()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn button<FD: FormToolData>(
|
fn button<FD: crate::FormToolData>(
|
||||||
&self,
|
&self,
|
||||||
control: ControlRenderData<Self, ButtonData<FD>>,
|
control: ControlRenderData<Self, crate::controls::button::ButtonData<FD>>,
|
||||||
data_signal: RwSignal<FD>,
|
|
||||||
) -> View {
|
) -> View {
|
||||||
let mut width = 1;
|
let mut width = 1;
|
||||||
for style in control.styles {
|
for style in control.style {
|
||||||
match style {
|
match style {
|
||||||
GridFormStylingAttributes::Width(w) => width = w,
|
GridFormStylingAttributes::Width(w) => width = w,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let action = control.data.action.clone();
|
let action = control.data.action.clone();
|
||||||
|
let signal = control.data.fd_signal.clone();
|
||||||
let on_click = move |ev: MouseEvent| {
|
let on_click = move |ev: MouseEvent| {
|
||||||
if let Some(action) = action.clone() {
|
if let Some(action) = action.clone() {
|
||||||
data_signal.update(|fd| action(ev, fd));
|
signal.update(|fd| action(ev, fd));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
view! {
|
view! {
|
||||||
<button
|
<button type="button" class="form_button" on:click=on_click style:grid-column=format!("span {}", width)>
|
||||||
type="button"
|
|
||||||
class="form_button"
|
|
||||||
on:click=on_click
|
|
||||||
style:grid-column=format!("span {}", width)
|
|
||||||
>
|
|
||||||
{control.data.text}
|
{control.data.text}
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
.into_view()
|
.into_view()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn group(&self, group: ControlRenderData<Self, View>) -> View {
|
// TODO: change this and form frame to use ControlRenderData
|
||||||
|
fn group(&self, inner: View, styles: Vec<Self::StylingAttributes>) -> View {
|
||||||
let mut width = 12;
|
let mut width = 12;
|
||||||
for style in group.styles {
|
for style in styles {
|
||||||
match style {
|
match style {
|
||||||
GridFormStylingAttributes::Width(w) => width = w,
|
GridFormStylingAttributes::Width(w) => width = w,
|
||||||
}
|
}
|
||||||
@ -392,7 +390,7 @@ impl FormStyle for GridFormStyle {
|
|||||||
|
|
||||||
view! {
|
view! {
|
||||||
<div class="form_group form_grid" style:grid-column=format!("span {}", width)>
|
<div class="form_group form_grid" style:grid-column=format!("span {}", width)>
|
||||||
{group.data}
|
{inner}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
.into_view()
|
.into_view()
|
||||||
@ -400,13 +398,15 @@ impl FormStyle for GridFormStyle {
|
|||||||
|
|
||||||
fn spacer(&self, control: ControlRenderData<Self, SpacerData>) -> View {
|
fn spacer(&self, control: ControlRenderData<Self, SpacerData>) -> View {
|
||||||
let mut width = 12;
|
let mut width = 12;
|
||||||
for style in control.styles {
|
for style in control.style {
|
||||||
match style {
|
match style {
|
||||||
GridFormStylingAttributes::Width(w) => width = w,
|
GridFormStylingAttributes::Width(w) => width = w,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
view! { <div style:grid-column=format!("span {}", width) style:height=control.data.height></div> }
|
view! {
|
||||||
|
<div style:grid-column=format!("span {}", width) style:height=control.data.height/>
|
||||||
|
}
|
||||||
.into_view()
|
.into_view()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,11 +9,11 @@ use crate::{
|
|||||||
},
|
},
|
||||||
FormToolData,
|
FormToolData,
|
||||||
};
|
};
|
||||||
use leptos::{RwSignal, Signal, View};
|
use leptos::{Signal, View};
|
||||||
|
|
||||||
pub use grid_form::{GridFormStyle, GridFormStylingAttributes};
|
pub use grid_form::{GridFormStyle, GridFormStylingAttributes};
|
||||||
|
|
||||||
pub trait FormStyle: 'static {
|
pub trait FormStyle: Default + 'static {
|
||||||
type StylingAttributes;
|
type StylingAttributes;
|
||||||
|
|
||||||
/// Render any containing components for the form.
|
/// Render any containing components for the form.
|
||||||
@ -23,7 +23,7 @@ pub trait FormStyle: 'static {
|
|||||||
///
|
///
|
||||||
/// Do NOT wrap it in an actual `form` element; any
|
/// Do NOT wrap it in an actual `form` element; any
|
||||||
/// wrapping should be done with `div` or similar elements.
|
/// wrapping should be done with `div` or similar elements.
|
||||||
fn form_frame(&self, form: ControlRenderData<Self, View>) -> View;
|
fn form_frame(&self, children: View, style: Vec<Self::StylingAttributes>) -> View;
|
||||||
fn heading(&self, control: ControlRenderData<Self, HeadingData>) -> View;
|
fn heading(&self, control: ControlRenderData<Self, HeadingData>) -> View;
|
||||||
fn hidden(
|
fn hidden(
|
||||||
&self,
|
&self,
|
||||||
@ -58,11 +58,7 @@ pub trait FormStyle: 'static {
|
|||||||
value_setter: Box<dyn Fn(<SelectData as ControlData>::ReturnType)>,
|
value_setter: Box<dyn Fn(<SelectData as ControlData>::ReturnType)>,
|
||||||
validation_state: Signal<Result<(), String>>,
|
validation_state: Signal<Result<(), String>>,
|
||||||
) -> View;
|
) -> View;
|
||||||
fn button<FD: FormToolData>(
|
fn button<FD: FormToolData>(&self, control: ControlRenderData<Self, ButtonData<FD>>) -> View;
|
||||||
&self,
|
|
||||||
control: ControlRenderData<Self, ButtonData<FD>>,
|
|
||||||
data_signal: RwSignal<FD>,
|
|
||||||
) -> View;
|
|
||||||
fn checkbox(
|
fn checkbox(
|
||||||
&self,
|
&self,
|
||||||
control: ControlRenderData<Self, CheckboxData>,
|
control: ControlRenderData<Self, CheckboxData>,
|
||||||
@ -89,7 +85,8 @@ pub trait FormStyle: 'static {
|
|||||||
validation_state: Signal<Result<(), String>>,
|
validation_state: Signal<Result<(), String>>,
|
||||||
) -> View;
|
) -> View;
|
||||||
fn submit(&self, control: ControlRenderData<Self, SubmitData>) -> View;
|
fn submit(&self, control: ControlRenderData<Self, SubmitData>) -> View;
|
||||||
|
// TODO: test custom component
|
||||||
fn custom_component(&self, view: View) -> View;
|
fn custom_component(&self, view: View) -> View;
|
||||||
fn group(&self, group: ControlRenderData<Self, View>) -> View;
|
fn group(&self, inner: View, style: Vec<Self::StylingAttributes>) -> View;
|
||||||
fn spacer(&self, control: ControlRenderData<Self, SpacerData>) -> View;
|
fn spacer(&self, control: ControlRenderData<Self, SpacerData>) -> View;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,6 @@
|
|||||||
use crate::{controls::ValidationFn, FormToolData};
|
use crate::{controls::ValidationFn, FormToolData};
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
/// A function that validates a field.
|
|
||||||
///
|
|
||||||
/// This is similar to [`ValidationFn`](crate::controls::ValidationFn)
|
|
||||||
/// but takes a &str for the name of the field for improved error messages.
|
|
||||||
type ValidationBuilderFn<T> = dyn Fn(&str, &T) -> Result<(), String> + 'static;
|
|
||||||
|
|
||||||
/// A helper builder that allows you to specify a validation function
|
/// A helper builder that allows you to specify a validation function
|
||||||
/// declaritivly
|
/// declaritivly
|
||||||
///
|
///
|
||||||
@ -20,7 +14,7 @@ pub struct ValidationBuilder<FD: FormToolData, T: 'static> {
|
|||||||
/// The getter function for the field to validate.
|
/// The getter function for the field to validate.
|
||||||
field_fn: Box<dyn Fn(&FD) -> &T + 'static>,
|
field_fn: Box<dyn Fn(&FD) -> &T + 'static>,
|
||||||
/// The functions to be called when validating.
|
/// The functions to be called when validating.
|
||||||
functions: Vec<Box<ValidationBuilderFn<T>>>,
|
functions: Vec<Box<dyn Fn(&str, &T) -> Result<(), String> + 'static>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<FD: FormToolData, T: 'static> ValidationBuilder<FD, T> {
|
impl<FD: FormToolData, T: 'static> ValidationBuilder<FD, T> {
|
||||||
|
|||||||
Reference in New Issue
Block a user