add docs and refactor GFStyle

This commit is contained in:
Mitchell Marino 2024-06-18 18:30:16 -05:00
parent 1dc93d69f4
commit 5f4358277e
20 changed files with 579 additions and 282 deletions

View File

@ -9,6 +9,7 @@ use web_sys::MouseEvent;
type ButtonAction<FD> = dyn Fn(MouseEvent, RwSignal<FD>) + 'static; type ButtonAction<FD> = dyn Fn(MouseEvent, RwSignal<FD>) + 'static;
/// Data used for the button control.
pub struct ButtonData<FD: FormToolData> { pub struct ButtonData<FD: FormToolData> {
pub text: String, pub text: String,
pub action: Option<Rc<ButtonAction<FD>>>, pub action: Option<Rc<ButtonAction<FD>>>,
@ -31,18 +32,21 @@ impl<FD: FormToolData> Clone for ButtonData<FD> {
} }
impl<FD: FormToolData> FormBuilder<FD> { impl<FD: FormToolData> FormBuilder<FD> {
/// Builds a button and adds it to the form.
pub fn button(self, builder: impl BuilderFn<ButtonBuilder<FD>>) -> Self { pub fn button(self, builder: impl BuilderFn<ButtonBuilder<FD>>) -> Self {
let button_builder = ButtonBuilder::new(); let button_builder = ButtonBuilder::new();
let control = builder(button_builder); let control = builder(button_builder);
self.button_helper(control) self.button_helper(control)
} }
/// Builds a button using the form's context and adds it to the form.
pub fn button_cx(self, builder: impl BuilderCxFn<ButtonBuilder<FD>, FD::Context>) -> Self { pub fn button_cx(self, builder: impl BuilderCxFn<ButtonBuilder<FD>, FD::Context>) -> Self {
let button_builder = ButtonBuilder::new(); let button_builder = ButtonBuilder::new();
let control = builder(button_builder, self.cx.clone()); let control = builder(button_builder, self.cx.clone());
self.button_helper(control) self.button_helper(control)
} }
/// The common functionality for adding a button.
fn button_helper(mut self, control: ButtonBuilder<FD>) -> Self { fn button_helper(mut self, control: ButtonBuilder<FD>) -> Self {
let render_data = ControlRenderData { let render_data = ControlRenderData {
data: control.data, data: control.data,
@ -74,6 +78,7 @@ impl<FD: FormToolData> FormBuilder<FD> {
} }
} }
/// The struct that allows you to specify the attributes of the button.
pub struct ButtonBuilder<FD: FormToolData> { pub struct ButtonBuilder<FD: FormToolData> {
pub(crate) styles: Vec<<FD::Style as FormStyle>::StylingAttributes>, pub(crate) styles: Vec<<FD::Style as FormStyle>::StylingAttributes>,
pub(crate) data: ButtonData<FD>, pub(crate) data: ButtonData<FD>,
@ -81,6 +86,7 @@ pub struct ButtonBuilder<FD: FormToolData> {
} }
impl<FD: FormToolData> ButtonBuilder<FD> { impl<FD: FormToolData> ButtonBuilder<FD> {
/// Creates a new [`ButtonBuilder`].
fn new() -> Self { fn new() -> Self {
ButtonBuilder { ButtonBuilder {
styles: Vec::default(), styles: Vec::default(),
@ -90,8 +96,6 @@ impl<FD: FormToolData> ButtonBuilder<FD> {
} }
/// Sets the function to decide when to render the control. /// Sets the function to decide when to render the control.
///
/// Validations for components that are not shown DO NOT run.
pub fn show_when( pub fn show_when(
mut self, mut self,
when: impl Fn(Signal<FD>, Rc<FD::Context>) -> bool + 'static, when: impl Fn(Signal<FD>, Rc<FD::Context>) -> bool + 'static,
@ -100,16 +104,19 @@ impl<FD: FormToolData> ButtonBuilder<FD> {
self self
} }
/// Adds a styling attribute to the button.
pub fn style(mut self, style: <FD::Style as FormStyle>::StylingAttributes) -> Self { pub fn style(mut self, style: <FD::Style as FormStyle>::StylingAttributes) -> Self {
self.styles.push(style); self.styles.push(style);
self self
} }
/// Sets the text of the button.
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
} }
/// Sets the action that is preformed when the button is clicked.
pub fn action(mut self, action: impl Fn(MouseEvent, RwSignal<FD>) + 'static) -> Self { pub fn action(mut self, action: impl Fn(MouseEvent, RwSignal<FD>) + 'static) -> Self {
self.data.action = Some(Rc::new(action)); self.data.action = Some(Rc::new(action));
self self

View File

@ -3,6 +3,7 @@ use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
use leptos::{Signal, View}; use leptos::{Signal, View};
use std::rc::Rc; use std::rc::Rc;
/// Data used for the checkbox control.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct CheckboxData { pub struct CheckboxData {
pub name: String, pub name: String,
@ -24,6 +25,7 @@ impl ControlData for CheckboxData {
} }
impl<FD: FormToolData> FormBuilder<FD> { impl<FD: FormToolData> FormBuilder<FD> {
/// Builds a checkbox and adds it to the form.
pub fn checkbox<FDT: Clone + PartialEq + 'static>( pub fn checkbox<FDT: Clone + PartialEq + 'static>(
self, self,
builder: impl BuilderFn<ControlBuilder<FD, CheckboxData, FDT>>, builder: impl BuilderFn<ControlBuilder<FD, CheckboxData, FDT>>,
@ -31,6 +33,7 @@ impl<FD: FormToolData> FormBuilder<FD> {
self.new_control(builder) self.new_control(builder)
} }
/// Builds a checkbox using the form's context and adds it to the form.
pub fn checkbox_cx<FDT: Clone + PartialEq + 'static>( pub fn checkbox_cx<FDT: Clone + PartialEq + 'static>(
self, self,
builder: impl BuilderCxFn<ControlBuilder<FD, CheckboxData, FDT>, FD::Context>, builder: impl BuilderCxFn<ControlBuilder<FD, CheckboxData, FDT>, FD::Context>,
@ -40,11 +43,20 @@ impl<FD: FormToolData> FormBuilder<FD> {
} }
impl<FD: FormToolData, FDT> ControlBuilder<FD, CheckboxData, FDT> { impl<FD: FormToolData, FDT> ControlBuilder<FD, CheckboxData, FDT> {
/// Sets the name of the checkbox.
///
/// This is used for the html element's "name" attribute.
/// In forms, the name attribute is the key that the data is sent
/// with.
///
/// For checkbox controls, the value "checked" is sent or no key value
/// pair is sent.
pub fn named(mut self, control_name: impl ToString) -> Self { pub fn named(mut self, control_name: impl ToString) -> Self {
self.data.name = control_name.to_string(); self.data.name = control_name.to_string();
self self
} }
/// Sets the text of the checkbox's label.
pub fn labeled(mut self, label: impl ToString) -> Self { pub fn labeled(mut self, label: impl ToString) -> Self {
self.data.label = Some(label.to_string()); self.data.label = Some(label.to_string());
self self

View File

@ -2,6 +2,7 @@ use super::{BuilderCxFn, BuilderFn, ControlBuilder, ControlData};
use crate::{FormBuilder, FormToolData}; use crate::{FormBuilder, FormToolData};
impl<FD: FormToolData> FormBuilder<FD> { impl<FD: FormToolData> FormBuilder<FD> {
/// Builds a custom component and adds it to the form.
pub fn custom<CC: ControlData, FDT: Clone + PartialEq + 'static>( pub fn custom<CC: ControlData, FDT: Clone + PartialEq + 'static>(
mut self, mut self,
control_data: CC, control_data: CC,
@ -13,6 +14,8 @@ impl<FD: FormToolData> FormBuilder<FD> {
self self
} }
/// Builds a custom component using the form's context and adds it to the
/// form.
pub fn custom_cx<CC: ControlData, FDT: Clone + PartialEq + 'static>( pub fn custom_cx<CC: ControlData, FDT: Clone + PartialEq + 'static>(
mut self, mut self,
control_data: CC, control_data: CC,
@ -24,6 +27,8 @@ impl<FD: FormToolData> FormBuilder<FD> {
self self
} }
/// Builds a custom component, starting with the default
/// CustomControlData, and adds it to the form.
pub fn custom_default<CC: Default + ControlData, FDT: Clone + PartialEq + 'static>( pub fn custom_default<CC: Default + ControlData, FDT: Clone + PartialEq + 'static>(
self, self,
builder: impl BuilderFn<ControlBuilder<FD, CC, FDT>>, builder: impl BuilderFn<ControlBuilder<FD, CC, FDT>>,
@ -31,6 +36,8 @@ impl<FD: FormToolData> FormBuilder<FD> {
self.new_control(builder) self.new_control(builder)
} }
/// Builds a custom component, starting with the default
/// CustomControlData using the form's context, and adds it to the form.
pub fn custom_default_cx<CC: Default + ControlData, FDT: Clone + PartialEq + 'static>( pub fn custom_default_cx<CC: Default + ControlData, FDT: Clone + PartialEq + 'static>(
self, self,
builder: impl BuilderCxFn<ControlBuilder<FD, CC, FDT>, FD::Context>, builder: impl BuilderCxFn<ControlBuilder<FD, CC, FDT>, FD::Context>,

View File

@ -6,6 +6,10 @@ use crate::{form::FormToolData, form_builder::FormBuilder};
use leptos::{CollectView, RwSignal}; use leptos::{CollectView, RwSignal};
impl<FD: FormToolData> FormBuilder<FD> { impl<FD: FormToolData> FormBuilder<FD> {
/// Creates a form group.
///
/// This creates a subsection of the form that controls can be added to
/// like a normal form.
pub fn group(mut self, builder: impl Fn(FormBuilder<FD>) -> FormBuilder<FD>) -> Self { pub fn group(mut self, builder: impl Fn(FormBuilder<FD>) -> FormBuilder<FD>) -> Self {
let mut group_builder = FormBuilder::new_group(self.cx); let mut group_builder = FormBuilder::new_group(self.cx);
group_builder = builder(group_builder); group_builder = builder(group_builder);
@ -30,6 +34,7 @@ impl<FD: FormToolData> FormBuilder<FD> {
}); });
let view = fs.group(render_data.clone()); let view = fs.group(render_data.clone());
// TODO: add conditional rendering
let validation_cb = move || { let validation_cb = move || {
let mut success = true; let mut success = true;

View File

@ -3,6 +3,7 @@ use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
use leptos::View; use leptos::View;
use std::rc::Rc; use std::rc::Rc;
/// Data used for the heading control.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct HeadingData { pub struct HeadingData {
pub title: String, pub title: String,
@ -19,10 +20,12 @@ impl VanityControlData for HeadingData {
} }
impl<FD: FormToolData> FormBuilder<FD> { impl<FD: FormToolData> FormBuilder<FD> {
/// Builds a heading and adds it to the form.
pub fn heading(self, builder: impl BuilderFn<VanityControlBuilder<FD, HeadingData>>) -> Self { pub fn heading(self, builder: impl BuilderFn<VanityControlBuilder<FD, HeadingData>>) -> Self {
self.new_vanity(builder) self.new_vanity(builder)
} }
/// Builds a hehading using the form's context and adds it to the form.
pub fn heading_cx( pub fn heading_cx(
self, self,
builder: impl BuilderCxFn<VanityControlBuilder<FD, HeadingData>, FD::Context>, builder: impl BuilderCxFn<VanityControlBuilder<FD, HeadingData>, FD::Context>,
@ -32,6 +35,7 @@ impl<FD: FormToolData> FormBuilder<FD> {
} }
impl<FD: FormToolData> VanityControlBuilder<FD, HeadingData> { impl<FD: FormToolData> VanityControlBuilder<FD, HeadingData> {
/// Sets the title of this heading.
pub fn title(mut self, title: impl ToString) -> Self { pub fn title(mut self, title: impl ToString) -> Self {
self.data.title = title.to_string(); self.data.title = title.to_string();
self self

View File

@ -6,6 +6,7 @@ use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
use leptos::{Signal, View}; use leptos::{Signal, View};
use std::rc::Rc; use std::rc::Rc;
/// Data used for the hidden control.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct HiddenData { pub struct HiddenData {
pub name: String, pub name: String,
@ -23,10 +24,19 @@ impl VanityControlData for HiddenData {
impl GetterVanityControlData for HiddenData {} impl GetterVanityControlData for HiddenData {}
impl<FD: FormToolData> FormBuilder<FD> { impl<FD: FormToolData> FormBuilder<FD> {
/// Builds a hidden form control and adds it to the form.
///
/// This will be an input in the html form allowing you to insert some
/// data the you might not want the user modifying.
pub fn hidden(self, builder: impl BuilderFn<VanityControlBuilder<FD, HiddenData>>) -> Self { pub fn hidden(self, builder: impl BuilderFn<VanityControlBuilder<FD, HiddenData>>) -> Self {
self.new_vanity(builder) self.new_vanity(builder)
} }
/// Builds a hidden form control using the form's context and adds it to
/// the form.
///
/// This will be an input in the html form allowing you to insert some
/// data the you might not want the user modifying.
pub fn hidden_cx( pub fn hidden_cx(
self, self,
builder: impl BuilderCxFn<VanityControlBuilder<FD, HiddenData>, FD::Context>, builder: impl BuilderCxFn<VanityControlBuilder<FD, HiddenData>, FD::Context>,
@ -36,6 +46,11 @@ impl<FD: FormToolData> FormBuilder<FD> {
} }
impl<FD: FormToolData> VanityControlBuilder<FD, HiddenData> { impl<FD: FormToolData> VanityControlBuilder<FD, HiddenData> {
/// Sets the name of the hidden control.
///
/// This is used for the html element's "name" attribute.
/// In forms, the name attribute is the key that the data is sent
/// with.
pub fn named(mut self, control_name: impl ToString) -> Self { pub fn named(mut self, control_name: impl ToString) -> Self {
self.data.name = control_name.to_string(); self.data.name = control_name.to_string();
self self

View File

@ -6,6 +6,7 @@ use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
use leptos::{Signal, View}; use leptos::{Signal, View};
use std::rc::Rc; use std::rc::Rc;
/// Data used for the output control.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct OutputData; pub struct OutputData;
@ -21,10 +22,19 @@ impl VanityControlData for OutputData {
impl GetterVanityControlData for OutputData {} impl GetterVanityControlData for OutputData {}
impl<FD: FormToolData> FormBuilder<FD> { impl<FD: FormToolData> FormBuilder<FD> {
/// Builds an output form control and adds it to the form.
///
/// This control allows you to output some text to the user based on the
/// form data.
pub fn output(self, builder: impl BuilderFn<VanityControlBuilder<FD, OutputData>>) -> Self { pub fn output(self, builder: impl BuilderFn<VanityControlBuilder<FD, OutputData>>) -> Self {
self.new_vanity(builder) self.new_vanity(builder)
} }
/// Builds an output form control using the form's context and adds it to
/// the form.
///
/// This control allows you to output some text to the user based on the
/// form data and form context.
pub fn output_cx( pub fn output_cx(
self, self,
builder: impl BuilderCxFn<VanityControlBuilder<FD, OutputData>, FD::Context>, builder: impl BuilderCxFn<VanityControlBuilder<FD, OutputData>, FD::Context>,

View File

@ -5,11 +5,15 @@ use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
use leptos::{Signal, View}; use leptos::{Signal, View};
use std::rc::Rc; use std::rc::Rc;
/// Data used for the radio buttons control.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct RadioButtonsData { pub struct RadioButtonsData {
pub name: String, pub name: String,
pub label: Option<String>, pub label: Option<String>,
pub options: Vec<String>, /// The options for the select.
///
/// The first value is the string to display, the second is the value.
pub options: Vec<(String, String)>,
} }
impl ControlData for RadioButtonsData { impl ControlData for RadioButtonsData {
@ -28,6 +32,7 @@ impl ControlData for RadioButtonsData {
impl ValidatedControlData for RadioButtonsData {} impl ValidatedControlData for RadioButtonsData {}
impl<FD: FormToolData> FormBuilder<FD> { impl<FD: FormToolData> FormBuilder<FD> {
/// Builds a radio buttons control and adds it to the form.
pub fn radio_buttons<FDT: Clone + PartialEq + 'static>( pub fn radio_buttons<FDT: Clone + PartialEq + 'static>(
self, self,
builder: impl BuilderFn<ControlBuilder<FD, RadioButtonsData, FDT>>, builder: impl BuilderFn<ControlBuilder<FD, RadioButtonsData, FDT>>,
@ -35,6 +40,8 @@ impl<FD: FormToolData> FormBuilder<FD> {
self.new_control(builder) self.new_control(builder)
} }
/// Builds a radio buttons control using the form's context and adds it to
/// the form.
pub fn radio_buttons_cx<FDT: Clone + PartialEq + 'static>( pub fn radio_buttons_cx<FDT: Clone + PartialEq + 'static>(
self, self,
builder: impl BuilderCxFn<ControlBuilder<FD, RadioButtonsData, FDT>, FD::Context>, builder: impl BuilderCxFn<ControlBuilder<FD, RadioButtonsData, FDT>, FD::Context>,
@ -44,24 +51,60 @@ impl<FD: FormToolData> FormBuilder<FD> {
} }
impl<FD: FormToolData, FDT> ControlBuilder<FD, RadioButtonsData, FDT> { impl<FD: FormToolData, FDT> ControlBuilder<FD, RadioButtonsData, FDT> {
/// Sets the name of the radio button inputs.
///
/// This is used for the html element's "name" attribute.
/// In forms, the name attribute is the key that the data is sent
/// with.
pub fn named(mut self, control_name: impl ToString) -> Self { pub fn named(mut self, control_name: impl ToString) -> Self {
self.data.name = control_name.to_string(); self.data.name = control_name.to_string();
self self
} }
/// Sets the label for the radio button group.
pub fn labeled(mut self, label: impl ToString) -> Self { pub fn labeled(mut self, label: impl ToString) -> Self {
self.data.label = Some(label.to_string()); self.data.label = Some(label.to_string());
self self
} }
/// Adds the option to the radio button group.
pub fn with_option(mut self, option: impl ToString) -> Self { pub fn with_option(mut self, option: impl ToString) -> Self {
self.data.options.push(option.to_string()); self.data
.options
.push((option.to_string(), option.to_string()));
self self
} }
/// Adds the option to the radio button group, specifying a different
/// value than what is displayed.
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
}
/// Adds all the options in the provided iterator to the radio button
/// group.
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.options.push(option.to_string()); self.data
.options
.push((option.to_string(), option.to_string()));
}
self
}
/// Adds all the (display_string, value) pairs in the provided iterator
/// to the radio button group.
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
} }

View File

@ -5,6 +5,7 @@ use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
use leptos::{Signal, View}; use leptos::{Signal, View};
use std::rc::Rc; use std::rc::Rc;
/// Data used for the select control.
#[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 name: String, pub name: String,
@ -31,12 +32,16 @@ impl ControlData for SelectData {
impl ValidatedControlData for SelectData {} impl ValidatedControlData for SelectData {}
impl<FD: FormToolData> FormBuilder<FD> { impl<FD: FormToolData> FormBuilder<FD> {
/// Builds a select control and adds it to the form.
pub fn select<FDT: Clone + PartialEq + 'static>( pub fn select<FDT: Clone + PartialEq + 'static>(
self, self,
builder: impl BuilderFn<ControlBuilder<FD, SelectData, FDT>>, builder: impl BuilderFn<ControlBuilder<FD, SelectData, FDT>>,
) -> Self { ) -> Self {
self.new_control(builder) self.new_control(builder)
} }
/// Builds a select control using the form's context and adds it to the
/// form.
pub fn select_cx<FDT: Clone + PartialEq + 'static>( pub fn select_cx<FDT: Clone + PartialEq + 'static>(
self, self,
builder: impl BuilderCxFn<ControlBuilder<FD, SelectData, FDT>, FD::Context>, builder: impl BuilderCxFn<ControlBuilder<FD, SelectData, FDT>, FD::Context>,
@ -46,16 +51,23 @@ impl<FD: FormToolData> FormBuilder<FD> {
} }
impl<FD: FormToolData, FDT> ControlBuilder<FD, SelectData, FDT> { impl<FD: FormToolData, FDT> ControlBuilder<FD, SelectData, FDT> {
/// Sets the name of the radio button inputs.
///
/// This is used for the html element's "name" attribute.
/// In forms, the name attribute is the key that the data is sent
/// with.
pub fn named(mut self, control_name: impl ToString) -> Self { pub fn named(mut self, control_name: impl ToString) -> Self {
self.data.name = control_name.to_string(); self.data.name = control_name.to_string();
self self
} }
/// Sets the label for the select.
pub fn labeled(mut self, label: impl ToString) -> Self { pub fn labeled(mut self, label: impl ToString) -> Self {
self.data.label = Some(label.to_string()); self.data.label = Some(label.to_string());
self self
} }
/// Adds the option to the select.
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 .options
@ -63,6 +75,8 @@ impl<FD: FormToolData, FDT> ControlBuilder<FD, SelectData, FDT> {
self self
} }
/// Adds the option to the select, specifying a different
/// value than what is displayed.
pub fn with_option_valued(mut self, display: impl ToString, value: impl ToString) -> Self { pub fn with_option_valued(mut self, display: impl ToString, value: impl ToString) -> Self {
self.data self.data
.options .options
@ -70,6 +84,7 @@ impl<FD: FormToolData, FDT> ControlBuilder<FD, SelectData, FDT> {
self self
} }
/// Adds all the options in the provided iterator to the select.
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
@ -79,6 +94,8 @@ impl<FD: FormToolData, FDT> ControlBuilder<FD, SelectData, FDT> {
self self
} }
/// Adds all the (display_string, value) pairs in the provided iterator
/// to the select.
pub fn with_options_valued( pub fn with_options_valued(
mut self, mut self,
options: impl Iterator<Item = (impl ToString, impl ToString)>, options: impl Iterator<Item = (impl ToString, impl ToString)>,

View File

@ -3,6 +3,7 @@ use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
use leptos::{Signal, View}; use leptos::{Signal, View};
use std::{ops::RangeInclusive, rc::Rc}; use std::{ops::RangeInclusive, rc::Rc};
/// Data used for the slider control.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SliderData { pub struct SliderData {
pub name: String, pub name: String,
@ -37,6 +38,7 @@ impl ControlData for SliderData {
} }
impl<FD: FormToolData> FormBuilder<FD> { impl<FD: FormToolData> FormBuilder<FD> {
/// Builds a slider (or range) control and adds it to the form.
pub fn slider<FDT: Clone + PartialEq + 'static>( pub fn slider<FDT: Clone + PartialEq + 'static>(
self, self,
builder: impl BuilderFn<ControlBuilder<FD, SliderData, FDT>>, builder: impl BuilderFn<ControlBuilder<FD, SliderData, FDT>>,
@ -44,6 +46,8 @@ impl<FD: FormToolData> FormBuilder<FD> {
self.new_control(builder) self.new_control(builder)
} }
/// Bulids a slider (or range) control using the form's context and adds
/// it to the form.
pub fn slider_cx<FDT: Clone + PartialEq + 'static>( pub fn slider_cx<FDT: Clone + PartialEq + 'static>(
self, self,
builder: impl BuilderCxFn<ControlBuilder<FD, SliderData, FDT>, FD::Context>, builder: impl BuilderCxFn<ControlBuilder<FD, SliderData, FDT>, FD::Context>,
@ -53,26 +57,36 @@ impl<FD: FormToolData> FormBuilder<FD> {
} }
impl<FD: FormToolData, FDT> ControlBuilder<FD, SliderData, FDT> { impl<FD: FormToolData, FDT> ControlBuilder<FD, SliderData, FDT> {
/// Sets the name of the slider.
///
/// This is used for the html element's "name" attribute.
/// In forms, the name attribute is the key that the data is sent
/// with.
pub fn named(mut self, control_name: impl ToString) -> Self { pub fn named(mut self, control_name: impl ToString) -> Self {
self.data.name = control_name.to_string(); self.data.name = control_name.to_string();
self self
} }
/// Sets the label for the slider.
pub fn labeled(mut self, label: impl ToString) -> Self { pub fn labeled(mut self, label: impl ToString) -> Self {
self.data.label = Some(label.to_string()); self.data.label = Some(label.to_string());
self self
} }
/// Sets the minimum value for the slider.
pub fn min(mut self, min: i32) -> Self { pub fn min(mut self, min: i32) -> Self {
self.data.min = min; self.data.min = min;
self self
} }
/// Sets the maximum value for the slider.
pub fn max(mut self, max: i32) -> Self { pub fn max(mut self, max: i32) -> Self {
self.data.max = max; self.data.max = max;
self self
} }
/// Sets the minimum and maximum values for the slider by providing a
/// range.
pub fn range(mut self, range: RangeInclusive<i32>) -> Self { pub fn range(mut self, range: RangeInclusive<i32>) -> Self {
self.data.min = *range.start(); self.data.min = *range.start();
self.data.max = *range.end(); self.data.max = *range.end();

View File

@ -3,6 +3,7 @@ use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
use leptos::{prelude::Signal, View}; use leptos::{prelude::Signal, View};
use std::rc::Rc; use std::rc::Rc;
/// Data used for the spacer control.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct SpacerData { pub struct SpacerData {
pub height: Option<String>, pub height: Option<String>,
@ -19,10 +20,12 @@ impl VanityControlData for SpacerData {
} }
impl<FD: FormToolData> FormBuilder<FD> { impl<FD: FormToolData> FormBuilder<FD> {
/// Builds a spacer and adds it to the form.
pub fn spacer(self, builder: impl BuilderFn<VanityControlBuilder<FD, SpacerData>>) -> Self { pub fn spacer(self, builder: impl BuilderFn<VanityControlBuilder<FD, SpacerData>>) -> Self {
self.new_vanity(builder) self.new_vanity(builder)
} }
/// Builds a spacer using the form's context and adds it to the form.
pub fn spacer_cx( pub fn spacer_cx(
self, self,
builder: impl BuilderCxFn<VanityControlBuilder<FD, SpacerData>, FD::Context>, builder: impl BuilderCxFn<VanityControlBuilder<FD, SpacerData>, FD::Context>,
@ -32,6 +35,9 @@ impl<FD: FormToolData> FormBuilder<FD> {
} }
impl<FD: FormToolData> VanityControlBuilder<FD, SpacerData> { impl<FD: FormToolData> VanityControlBuilder<FD, SpacerData> {
/// Sets the height of the spacer.
///
/// This may or may not be respected based on the Style implementation.
pub fn height(mut self, height: impl ToString) -> Self { pub fn height(mut self, height: impl ToString) -> Self {
self.data.height = Some(height.to_string()); self.data.height = Some(height.to_string());
self self

View File

@ -5,6 +5,7 @@ use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
use leptos::{Signal, View}; use leptos::{Signal, View};
use std::{ops::RangeInclusive, rc::Rc}; use std::{ops::RangeInclusive, rc::Rc};
/// Data used for the stepper control.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct StepperData { pub struct StepperData {
pub name: String, pub name: String,
@ -30,6 +31,7 @@ impl ControlData for StepperData {
impl ValidatedControlData for StepperData {} impl ValidatedControlData for StepperData {}
impl<FD: FormToolData> FormBuilder<FD> { impl<FD: FormToolData> FormBuilder<FD> {
/// Builds a stepper control and adds it to the form.
pub fn stepper<FDT: Clone + PartialEq + 'static>( pub fn stepper<FDT: Clone + PartialEq + 'static>(
self, self,
builder: impl BuilderFn<ControlBuilder<FD, StepperData, FDT>>, builder: impl BuilderFn<ControlBuilder<FD, StepperData, FDT>>,
@ -37,6 +39,8 @@ impl<FD: FormToolData> FormBuilder<FD> {
self.new_control(builder) self.new_control(builder)
} }
/// Builds a new stepper control using the form's context and adds it to
/// the form.
pub fn stepper_cx<FDT: Clone + PartialEq + 'static>( pub fn stepper_cx<FDT: Clone + PartialEq + 'static>(
self, self,
builder: impl BuilderCxFn<ControlBuilder<FD, StepperData, FDT>, FD::Context>, builder: impl BuilderCxFn<ControlBuilder<FD, StepperData, FDT>, FD::Context>,
@ -46,31 +50,41 @@ impl<FD: FormToolData> FormBuilder<FD> {
} }
impl<FD: FormToolData, FDT> ControlBuilder<FD, StepperData, FDT> { impl<FD: FormToolData, FDT> ControlBuilder<FD, StepperData, FDT> {
/// Sets the name of the stepper control.
///
/// This is used for the html element's "name" attribute.
/// In forms, the name attribute is the key that the data is sent
/// with.
pub fn named(mut self, control_name: impl ToString) -> Self { pub fn named(mut self, control_name: impl ToString) -> Self {
self.data.name = control_name.to_string(); self.data.name = control_name.to_string();
self self
} }
/// Sets the label of the stepper.
pub fn labeled(mut self, label: impl ToString) -> Self { pub fn labeled(mut self, label: impl ToString) -> Self {
self.data.label = Some(label.to_string()); self.data.label = Some(label.to_string());
self self
} }
/// Sets the step ammount.
pub fn step(mut self, step: i32) -> Self { pub fn step(mut self, step: i32) -> Self {
self.data.step = Some(step); self.data.step = Some(step);
self self
} }
/// Sets a minimum value.
pub fn min(mut self, min: i32) -> Self { pub fn min(mut self, min: i32) -> Self {
self.data.min = Some(min); self.data.min = Some(min);
self self
} }
/// Sets a maximum value.
pub fn max(mut self, max: i32) -> Self { pub fn max(mut self, max: i32) -> Self {
self.data.max = Some(max); self.data.max = Some(max);
self self
} }
/// Sets the minimum and maximum values using the range.
pub fn range(mut self, range: RangeInclusive<i32>) -> Self { pub fn range(mut self, range: RangeInclusive<i32>) -> Self {
self.data.min = Some(*range.start()); self.data.min = Some(*range.start());
self.data.max = Some(*range.end()); self.data.max = Some(*range.end());

View File

@ -3,6 +3,7 @@ use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
use leptos::{prelude::Signal, View}; use leptos::{prelude::Signal, View};
use std::rc::Rc; use std::rc::Rc;
/// Data used for the submit button control.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct SubmitData { pub struct SubmitData {
pub text: String, pub text: String,
@ -19,10 +20,13 @@ impl VanityControlData for SubmitData {
} }
impl<FD: FormToolData> FormBuilder<FD> { impl<FD: FormToolData> FormBuilder<FD> {
/// Builds a submit button and adds it to the form.
pub fn submit(self, builder: impl BuilderFn<VanityControlBuilder<FD, SubmitData>>) -> Self { pub fn submit(self, builder: impl BuilderFn<VanityControlBuilder<FD, SubmitData>>) -> Self {
self.new_vanity(builder) self.new_vanity(builder)
} }
/// Builds a submit button using the form's context and adds it to the
/// form.
pub fn submit_cx( pub fn submit_cx(
self, self,
builder: impl BuilderCxFn<VanityControlBuilder<FD, SubmitData>, FD::Context>, builder: impl BuilderCxFn<VanityControlBuilder<FD, SubmitData>, FD::Context>,
@ -32,6 +36,7 @@ impl<FD: FormToolData> FormBuilder<FD> {
} }
impl<FD: FormToolData> VanityControlBuilder<FD, SubmitData> { impl<FD: FormToolData> VanityControlBuilder<FD, SubmitData> {
/// Sets the submit button's text.
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

View File

@ -5,10 +5,13 @@ use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
use leptos::{Signal, View}; use leptos::{Signal, View};
use std::rc::Rc; use std::rc::Rc;
/// Data used for the text area control.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct TextAreaData { pub struct TextAreaData {
pub name: String, pub name: String,
pub placeholder: Option<String>, pub placeholder: Option<String>,
pub label: Option<String>,
pub initial_text: String,
} }
impl ControlData for TextAreaData { impl ControlData for TextAreaData {
@ -27,6 +30,7 @@ impl ControlData for TextAreaData {
impl ValidatedControlData for TextAreaData {} impl ValidatedControlData for TextAreaData {}
impl<FD: FormToolData> FormBuilder<FD> { impl<FD: FormToolData> FormBuilder<FD> {
/// Builds a text area control and adds it to the form.
pub fn text_area<FDT: Clone + PartialEq + 'static>( pub fn text_area<FDT: Clone + PartialEq + 'static>(
self, self,
builder: impl BuilderFn<ControlBuilder<FD, TextAreaData, FDT>>, builder: impl BuilderFn<ControlBuilder<FD, TextAreaData, FDT>>,
@ -34,6 +38,8 @@ impl<FD: FormToolData> FormBuilder<FD> {
self.new_control(builder) self.new_control(builder)
} }
/// Builds a text area control using the forms context and adds it to the
/// form.
pub fn text_area_cx<FDT: Clone + PartialEq + 'static>( pub fn text_area_cx<FDT: Clone + PartialEq + 'static>(
self, self,
builder: impl BuilderCxFn<ControlBuilder<FD, TextAreaData, FDT>, FD::Context>, builder: impl BuilderCxFn<ControlBuilder<FD, TextAreaData, FDT>, FD::Context>,
@ -43,8 +49,31 @@ impl<FD: FormToolData> FormBuilder<FD> {
} }
impl<FD: FormToolData, FDT> ControlBuilder<FD, TextAreaData, FDT> { impl<FD: FormToolData, FDT> ControlBuilder<FD, TextAreaData, FDT> {
/// Sets the name of the text area.
///
/// This is used for the html element's "name" attribute.
/// In forms, the name attribute is the key that the data is sent
/// with.
pub fn named(mut self, control_name: impl ToString) -> Self {
self.data.name = control_name.to_string();
self
}
/// Sets the label for the text area.
pub fn labeled(mut self, label: impl ToString) -> Self {
self.data.label = Some(label.to_string());
self
}
/// Sets the placeholder for the text area.
pub fn placeholder(mut self, placeholder: impl ToString) -> Self { pub fn placeholder(mut self, placeholder: impl ToString) -> Self {
self.data.placeholder = Some(placeholder.to_string()); self.data.placeholder = Some(placeholder.to_string());
self self
} }
/// Sets the intial_text for the text area.
pub fn initial_text(mut self, text: impl ToString) -> Self {
self.data.initial_text = text.to_string();
self
}
} }

View File

@ -5,6 +5,7 @@ use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
use leptos::{Signal, View}; use leptos::{Signal, View};
use std::rc::Rc; use std::rc::Rc;
/// Data used for the text input control.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TextInputData { pub struct TextInputData {
pub name: String, pub name: String,
@ -42,6 +43,7 @@ impl ControlData for TextInputData {
impl ValidatedControlData for TextInputData {} impl ValidatedControlData for TextInputData {}
impl<FD: FormToolData> FormBuilder<FD> { impl<FD: FormToolData> FormBuilder<FD> {
/// Builds a text input control and adds it to the form.
pub fn text_input<FDT: Clone + PartialEq + 'static>( pub fn text_input<FDT: Clone + PartialEq + 'static>(
self, self,
builder: impl BuilderFn<ControlBuilder<FD, TextInputData, FDT>>, builder: impl BuilderFn<ControlBuilder<FD, TextInputData, FDT>>,
@ -49,6 +51,8 @@ impl<FD: FormToolData> FormBuilder<FD> {
self.new_control(builder) self.new_control(builder)
} }
/// Builds a text input control using the form's context and adds it to
/// the form.
pub fn text_input_cx<FDT: Clone + PartialEq + 'static>( pub fn text_input_cx<FDT: Clone + PartialEq + 'static>(
self, self,
builder: impl BuilderCxFn<ControlBuilder<FD, TextInputData, FDT>, FD::Context>, builder: impl BuilderCxFn<ControlBuilder<FD, TextInputData, FDT>, FD::Context>,
@ -58,28 +62,49 @@ impl<FD: FormToolData> FormBuilder<FD> {
} }
impl<FD: FormToolData, FDT> ControlBuilder<FD, TextInputData, FDT> { impl<FD: FormToolData, FDT> ControlBuilder<FD, TextInputData, FDT> {
/// Sets the name of the text input.
///
/// This is used for the html element's "name" attribute.
/// In forms, the name attribute is the key that the data is sent
/// with.
pub fn named(mut self, control_name: impl ToString) -> Self { pub fn named(mut self, control_name: impl ToString) -> Self {
self.data.name = control_name.to_string(); self.data.name = control_name.to_string();
self self
} }
/// Sets the label for the text input.
pub fn labeled(mut self, label: impl ToString) -> Self {
self.data.label = Some(label.to_string());
self
}
/// Sets the placeholder for the text input.
pub fn placeholder(mut self, placeholder: impl ToString) -> Self { pub fn placeholder(mut self, placeholder: impl ToString) -> Self {
self.data.placeholder = Some(placeholder.to_string()); self.data.placeholder = Some(placeholder.to_string());
self self
} }
pub fn label(mut self, label: impl ToString) -> Self { /// Sets the intial_text for the text input.
self.data.label = Some(label.to_string());
self
}
pub fn initial_text(mut self, text: impl ToString) -> Self { pub fn initial_text(mut self, text: impl ToString) -> Self {
self.data.initial_text = text.to_string(); self.data.initial_text = text.to_string();
self self
} }
/// Sets the text input to be the "password" type.
pub fn password(mut self) -> Self { pub fn password(mut self) -> Self {
self.data.input_type = "password"; self.data.input_type = "password";
self self
} }
/// Sets the text input to be the "date" type.
pub fn date(mut self) -> Self {
self.data.input_type = "date";
self
}
/// Sets the text input to be the specified type.
pub fn input_type(mut self, input_type: &'static str) -> Self {
self.data.input_type = input_type;
self
}
} }

View File

@ -28,7 +28,7 @@ impl<FD: FormToolData> FormValidator<FD> {
} }
} }
/// A constructed form object. /// A constructed, rendered form object.
/// ///
/// With this, you can render the form, get the form data, or get /// With this, you can render the form, get the form data, or get
/// a validator for the data. /// a validator for the data.
@ -48,7 +48,7 @@ impl<FD: FormToolData> Form<FD> {
} }
} }
/// Validates the [`FormToolData`], returning the result /// Validates the [`FormToolData`], returning the result.
pub fn validate(&self) -> Result<(), String> { pub fn validate(&self) -> Result<(), String> {
let validator = self.validator(); let validator = self.validator();
validator.validate(&self.fd.get_untracked()) validator.validate(&self.fd.get_untracked())
@ -79,13 +79,21 @@ impl<FD: FormToolData> IntoView for Form<FD> {
/// A trait allowing a form to be built around its containing data. /// 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 /// This trait defines a function that can be used to build all the data
/// to physically lay out a form, and how that data should be parsed and validated. /// needed to physically lay out a form, and how that data should be parsed
/// and validated.
pub trait FormToolData: Clone + 'static { pub trait FormToolData: Clone + 'static {
/// The style that this form uses.
type Style: FormStyle; 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; type Context: 'static;
/// Defines how the form should be layed out and how the data should be parsed and validated. /// 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. /// To construct a [`From`] object, use one of the `get_form` methods.
/// ///
@ -100,12 +108,13 @@ pub trait FormToolData: Clone + 'static {
/// [`ActionForm`](leptos_router::ActionForm) that sends the form data /// [`ActionForm`](leptos_router::ActionForm) that sends the form data
/// directly by calling the server function. /// directly by calling the server function.
/// ///
/// By doing this, we avoid doing the [`FromFormData`](leptos_router::FromFormData) /// By doing this, we avoid doing the
/// [`FromFormData`](leptos_router::FromFormData)
/// conversion. However, to support /// conversion. However, to support
/// [Progressive Enhancement](https://book.leptos.dev/progressive_enhancement/index.html), /// [Progressive Enhancement](https://book.leptos.dev/progressive_enhancement/index.html),
/// you should name the form elements to work with a plain ActionForm anyway. /// you should name the form elements to work with a plain ActionForm
/// If progresssive enhancement is not important to you, you may freely /// anyway. If progresssive enhancement is not important to you, you may
/// use this version /// freely use this version.
/// ///
/// For the other ways to construct a [`Form`], see: /// For the other ways to construct a [`Form`], see:
/// - [`get_action_form`](Self::get_action_form) /// - [`get_action_form`](Self::get_action_form)

View File

@ -19,11 +19,6 @@ use web_sys::{FormData, SubmitEvent};
/// A builder for laying out forms. /// A builder for laying out forms.
/// ///
/// 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.
///
/// ### Type Parameters
/// - `FD`: FormToolData - Your form data
/// - `FS`: FormStyle - The form style you are using
/// - `CX`: Context - A user defined context that you are rendering the form in
pub struct FormBuilder<FD: FormToolData> { pub struct FormBuilder<FD: FormToolData> {
pub(crate) cx: Rc<FD::Context>, pub(crate) cx: Rc<FD::Context>,
/// The list of [`ValidationFn`]s. /// The list of [`ValidationFn`]s.
@ -56,11 +51,13 @@ impl<FD: FormToolData> FormBuilder<FD> {
} }
} }
/// Adds a styling attribute to the entire form.
pub fn style(mut self, style: <FD::Style as FormStyle>::StylingAttributes) -> Self { pub fn style(mut self, style: <FD::Style as FormStyle>::StylingAttributes) -> Self {
self.styles.push(style); self.styles.push(style);
self self
} }
/// Adds a new vanity control to the form.
pub(crate) fn new_vanity<C: VanityControlData + Default>( pub(crate) fn new_vanity<C: VanityControlData + Default>(
mut self, mut self,
builder: impl BuilderFn<VanityControlBuilder<FD, C>>, builder: impl BuilderFn<VanityControlBuilder<FD, C>>,
@ -71,6 +68,7 @@ impl<FD: FormToolData> FormBuilder<FD> {
self self
} }
/// Adds a new vanity control to the form using the form's context.
pub(crate) fn new_vanity_cx<C: VanityControlData + Default>( pub(crate) fn new_vanity_cx<C: VanityControlData + Default>(
mut self, mut self,
builder: impl BuilderCxFn<VanityControlBuilder<FD, C>, FD::Context>, builder: impl BuilderCxFn<VanityControlBuilder<FD, C>, FD::Context>,
@ -81,6 +79,7 @@ impl<FD: FormToolData> FormBuilder<FD> {
self self
} }
/// Adds a new control to the form using the form's context.
pub(crate) fn new_control<C: ControlData + Default, FDT: Clone + PartialEq + 'static>( pub(crate) fn new_control<C: ControlData + Default, FDT: Clone + PartialEq + 'static>(
mut self, mut self,
builder: impl BuilderFn<ControlBuilder<FD, C, FDT>>, builder: impl BuilderFn<ControlBuilder<FD, C, FDT>>,
@ -91,6 +90,7 @@ impl<FD: FormToolData> FormBuilder<FD> {
self self
} }
/// Adds a new control to the form using the form's context.
pub(crate) fn new_control_cx<C: ControlData + Default, FDT: Clone + PartialEq + 'static>( pub(crate) fn new_control_cx<C: ControlData + Default, FDT: Clone + PartialEq + 'static>(
mut self, mut self,
builder: impl BuilderCxFn<ControlBuilder<FD, C, FDT>, FD::Context>, builder: impl BuilderCxFn<ControlBuilder<FD, C, FDT>, FD::Context>,
@ -101,8 +101,8 @@ impl<FD: FormToolData> FormBuilder<FD> {
self self
} }
// TODO: test this from a user context. A user adding a custom defined component. /// Adds a vanity control to the form.
pub fn add_vanity<C: VanityControlData>( pub(crate) fn add_vanity<C: VanityControlData>(
&mut self, &mut self,
vanity_control: VanityControlBuilder<FD, C>, vanity_control: VanityControlBuilder<FD, C>,
) { ) {
@ -135,14 +135,20 @@ impl<FD: FormToolData> FormBuilder<FD> {
self.render_fns.push(Box::new(render_fn)); self.render_fns.push(Box::new(render_fn));
} }
// TODO: test this from a user context. A user adding a custom defined component. /// Adds a control to the form.
pub fn add_control<C: ControlData, FDT: Clone + PartialEq + 'static>( pub(crate) fn add_control<C: ControlData, FDT: Clone + PartialEq + 'static>(
&mut self, &mut self,
control: ControlBuilder<FD, C, FDT>, control: ControlBuilder<FD, C, FDT>,
) { ) {
let built_control_data = match control.build() { let built_control_data = match control.build() {
Ok(c) => c, Ok(c) => c,
Err(e) => panic!("Invalid Component: {}", e), Err(e) => {
let item_name = std::any::type_name::<C>()
.rsplit("::")
.next()
.expect("split to have at least 1 element");
panic!("Invalid Component ({}): {}", item_name, e)
}
}; };
if let Some(validation_fn) = built_control_data.validation_fn.clone() { if let Some(validation_fn) = built_control_data.validation_fn.clone() {
@ -174,6 +180,8 @@ impl<FD: FormToolData> FormBuilder<FD> {
self.render_fns.push(Box::new(render_fn)); self.render_fns.push(Box::new(render_fn));
} }
/// Helper for building all the functions and everything needed to render
/// the view.
fn build_control_view<C: ControlData, FDT: 'static>( fn build_control_view<C: ControlData, FDT: 'static>(
fd: RwSignal<FD>, fd: RwSignal<FD>,
fs: Rc<FD::Style>, fs: Rc<FD::Style>,
@ -271,6 +279,7 @@ impl<FD: FormToolData> FormBuilder<FD> {
(view, validation_cb) (view, validation_cb)
} }
/// Helper for creating a setter function.
fn create_value_setter<CRT: 'static, FDT: 'static>( fn create_value_setter<CRT: 'static, FDT: 'static>(
validation_cb: Box<dyn Fn() -> bool + 'static>, validation_cb: Box<dyn Fn() -> bool + 'static>,
validation_signal_set: WriteSignal<Result<(), String>>, validation_signal_set: WriteSignal<Result<(), String>>,
@ -301,6 +310,7 @@ impl<FD: FormToolData> FormBuilder<FD> {
Rc::new(value_setter) Rc::new(value_setter)
} }
/// Builds the direct send version of the form.
pub(crate) fn build_form<ServFn>( pub(crate) fn build_form<ServFn>(
self, self,
action: Action<ServFn, Result<ServFn::Output, ServerFnError<ServFn::Error>>>, action: Action<ServFn, Result<ServFn::Output, ServerFnError<ServFn::Error>>>,
@ -357,6 +367,7 @@ impl<FD: FormToolData> FormBuilder<FD> {
} }
} }
/// Builds the action form version of the form.
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>>>,
@ -407,6 +418,7 @@ impl<FD: FormToolData> FormBuilder<FD> {
} }
} }
/// builds the plain form version of the form.
pub(crate) fn build_plain_form(self, url: String, fd: FD, fs: FD::Style) -> Form<FD> { pub(crate) fn build_plain_form(self, url: String, fd: FD, fs: FD::Style) -> Form<FD> {
let fd = create_rw_signal(fd); let fd = create_rw_signal(fd);
let fs = Rc::new(fs); let fs = Rc::new(fs);
@ -447,6 +459,7 @@ impl<FD: FormToolData> FormBuilder<FD> {
} }
} }
/// Creates a [`FormValidator`] from this builder.
pub(crate) fn validator(&self) -> FormValidator<FD> { pub(crate) fn validator(&self) -> FormValidator<FD> {
FormValidator { FormValidator {
validations: self.validations.clone(), validations: self.validations.clone(),

View File

@ -1,3 +1,8 @@
//! `leptos_form_tool` offers a declaritve way to create forms for
//! [leptos](https://leptos.dev/).
//!
//! To learn more, see the
//! [README.md](https://github.com/MitchellMarinoDev/leptos_form_tool/src/branch/main/README.md)
pub mod controls; pub mod controls;
mod form; mod form;
mod form_builder; mod form_builder;

View File

@ -12,22 +12,129 @@ use leptos::*;
use std::rc::Rc; use std::rc::Rc;
use web_sys::MouseEvent; use web_sys::MouseEvent;
pub enum GridFormStylingAttributes { /// Styling attributes for the [`GridFormStyle`].
#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub enum GFStyleAttr {
/// Set the width of the control out of 12.
/// Defaults to 12/12 (full width).
Width(u32), Width(u32),
/// Adds a tooltip to the control.
/// This sets the html title attribute, which shows the text when the
/// user hovers their mouse over the control for a couple seconds.
Tooltip(String),
} }
/// A complete useable example for defining a form style.
///
/// This can be used directly in by your form, or you can copy `grid_form.rs`
/// into your project and make any neccesary change. You will also want to
/// copy `grid_form.scss` from the git repo and put that in the `styles`
/// directory for your leptos project to get all the styling.
#[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)] #[derive(Clone, Copy, Debug, Default, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct GridFormStyle; pub struct GridFormStyle;
impl FormStyle for GridFormStyle { impl FormStyle for GridFormStyle {
type StylingAttributes = GridFormStylingAttributes; type StylingAttributes = GFStyleAttr;
fn form_frame(&self, form: ControlRenderData<Self, View>) -> View { fn form_frame(&self, form: ControlRenderData<Self, View>) -> View {
view! { <div class="form_grid">{form.data}</div> }.into_view() view! { <div class="form_grid">{form.data}</div> }.into_view()
} }
/// A common function that wraps the given view in the styles
fn custom_component(&self, styles: &[Self::StylingAttributes], inner: View) -> View {
let mut width = 12;
let mut tooltip = None;
for style in styles.iter() {
match style {
GFStyleAttr::Width(w) => width = *w,
GFStyleAttr::Tooltip(t) => tooltip = Some(t),
}
}
view! {
<div style:grid-column=format!("span {}", width) title=tooltip>
{inner}
</div>
}
.into_view()
}
fn group(&self, group: Rc<ControlRenderData<Self, View>>) -> View {
let view = view! {
<div class="form_group form_grid">
{&group.data}
</div>
}
.into_view();
self.custom_component(&group.styles, view)
}
fn spacer(&self, control: Rc<ControlRenderData<Self, SpacerData>>) -> View {
self.custom_component(
&control.styles,
view! { <div style:height=control.data.height.as_ref()></div> }.into_view(),
)
}
fn heading(&self, control: Rc<ControlRenderData<Self, HeadingData>>) -> View { fn heading(&self, control: Rc<ControlRenderData<Self, HeadingData>>) -> View {
view! { <h2 class="form_heading">{&control.data.title}</h2> }.into_view() self.custom_component(
&control.styles,
view! { <h2 class="form_heading">{&control.data.title}</h2> }.into_view(),
)
}
fn submit(&self, control: Rc<ControlRenderData<Self, SubmitData>>) -> View {
self.custom_component(
&control.styles,
view! { <input type="submit" value=&control.data.text class="form_submit"/> }
.into_view(),
)
}
fn button<FD: FormToolData>(
&self,
control: Rc<ControlRenderData<Self, ButtonData<FD>>>,
data_signal: RwSignal<FD>,
) -> View {
let action = control.data.action.clone();
let on_click = move |ev: MouseEvent| {
if let Some(action) = action.clone() {
action(ev, data_signal)
}
};
let view = view! {
<button
type="button"
class="form_button"
on:click=on_click
>
{&control.data.text}
</button>
}
.into_view();
self.custom_component(&control.styles, view)
}
fn output(
&self,
control: Rc<ControlRenderData<Self, OutputData>>,
value_getter: Option<Signal<String>>,
) -> View {
let view = view! { <span>{move || value_getter.map(|g| g.get())}</span> }.into_view();
self.custom_component(&control.styles, view)
}
fn hidden(
&self,
control: Rc<ControlRenderData<Self, HiddenData>>,
value_getter: Option<Signal<String>>,
) -> View {
let value_getter = move || value_getter.map(|g| g.get());
view! { <input name=&control.data.name prop:value=value_getter style="visibility: hidden; column-span: none"/> }
.into_view()
} }
fn text_input( fn text_input(
@ -37,38 +144,120 @@ impl FormStyle for GridFormStyle {
value_setter: Rc<dyn Fn(<TextInputData as ControlData>::ReturnType)>, value_setter: Rc<dyn Fn(<TextInputData as ControlData>::ReturnType)>,
validation_state: Signal<Result<(), String>>, validation_state: Signal<Result<(), String>>,
) -> View { ) -> View {
// TODO: extract this to a common thing let view = view! {
let mut width = 1; <div>
for style in control.styles.iter() { <label for=&control.data.name class="form_label">
match style { {control.data.label.as_ref()}
GridFormStylingAttributes::Width(w) => width = *w, </label>
} <span class="form_error">{move || validation_state.get().err()}</span>
</div>
<input
type=control.data.input_type
id=&control.data.name
name=&control.data.name
placeholder=control.data.placeholder.as_ref()
prop:value=move || value_getter.get()
on:focusout=move |ev| {
value_setter(event_target_value(&ev));
}
class="form_input"
class=("form_input_invalid", move || validation_state.get().is_err())
/>
} }
.into_view();
view! { self.custom_component(&control.styles, view)
<div style:grid-column=format!("span {}", width)> }
fn text_area(
&self,
control: Rc<ControlRenderData<Self, TextAreaData>>,
value_getter: Signal<<TextAreaData as ControlData>::ReturnType>,
value_setter: Rc<dyn Fn(<TextAreaData as ControlData>::ReturnType)>,
validation_state: Signal<Result<(), String>>,
) -> View {
let view = view! {
<div>
<div> <div>
<label for=&control.data.name class="form_label"> <label for=&control.data.name class="form_label">
{control.data.label.as_ref()} {control.data.label.as_ref()}
</label> </label>
<span class="form_error">{move || validation_state.get().err()}</span> <span class="form_error">{move || validation_state.get().err()}</span>
</div> </div>
<input <textarea
type=control.data.input_type
id=&control.data.name id=&control.data.name
name=&control.data.name name=&control.data.name
placeholder=control.data.placeholder.as_ref() placeholder=control.data.placeholder.as_ref()
class="form_input"
prop:value=move || value_getter.get() prop:value=move || value_getter.get()
on:focusout=move |ev| { on:change=move |ev| {
value_setter(event_target_value(&ev)); value_setter(event_target_value(&ev));
} }
>
</textarea>
class="form_input"
class=("form_input_invalid", move || validation_state.get().is_err())
/>
</div> </div>
} }
.into_view() .into_view();
self.custom_component(&control.styles, view)
}
fn radio_buttons(
&self,
control: Rc<ControlRenderData<Self, RadioButtonsData>>,
value_getter: Signal<<RadioButtonsData as ControlData>::ReturnType>,
value_setter: Rc<dyn Fn(<RadioButtonsData as ControlData>::ReturnType)>,
validation_state: Signal<Result<(), String>>,
) -> View {
let buttons_view = control
.data
.options
.iter()
.map(|(display, value)| {
let value_setter = value_setter.clone();
let display = display.clone();
let value = value.clone();
let value_clone = value.clone();
let value_clone2 = value.clone();
view! {
<input
type="radio"
id=&value
name=&control.data.name
value=&value
prop:checked=move || { &value_getter.get() == &value_clone }
on:input=move |ev| {
let new_value = event_target_checked(&ev);
if new_value {
value_setter(value_clone2.clone());
}
}
/>
<label for=&value>{display}</label>
<br/>
}
})
.collect_view();
let view = view! {
<div>
<label for=&control.data.name class="form_label">
{control.data.label.as_ref()}
</label>
<span class="form_error">{move || validation_state.get().err()}</span>
</div>
<div
class="form_input"
class:form_input_invalid=move || validation_state.get().is_err()
>
{buttons_view}
</div>
}
.into_view();
self.custom_component(&control.styles, view)
} }
fn select( fn select(
@ -78,13 +267,6 @@ impl FormStyle for GridFormStyle {
value_setter: Rc<dyn Fn(<SelectData as ControlData>::ReturnType)>, value_setter: Rc<dyn Fn(<SelectData as ControlData>::ReturnType)>,
validation_state: Signal<Result<(), String>>, validation_state: Signal<Result<(), String>>,
) -> View { ) -> View {
let mut width = 1;
for style in control.styles.iter() {
match style {
GridFormStylingAttributes::Width(w) => width = *w,
}
}
let options_view = control let options_view = control
.data .data
.options .options
@ -100,8 +282,7 @@ impl FormStyle for GridFormStyle {
}) })
.collect_view(); .collect_view();
view! { let view = view! {
<div style:grid-column=format!("span {}", width)>
<div> <div>
<label for=&control.data.name class="form_label"> <label for=&control.data.name class="form_label">
{control.data.label.as_ref()} {control.data.label.as_ref()}
@ -119,118 +300,10 @@ impl FormStyle for GridFormStyle {
{options_view} {options_view}
</select> </select>
</div>
} }
.into_view() .into_view();
}
fn submit(&self, control: Rc<ControlRenderData<Self, SubmitData>>) -> View { self.custom_component(&control.styles, view)
view! { <input type="submit" value=&control.data.text class="form_submit"/> }.into_view()
}
fn text_area(
&self,
control: Rc<ControlRenderData<Self, TextAreaData>>,
value_getter: Signal<<TextAreaData as ControlData>::ReturnType>,
value_setter: Rc<dyn Fn(<TextAreaData as ControlData>::ReturnType)>,
validation_state: Signal<Result<(), String>>,
) -> View {
view! {
<div>
{move || format!("{:?}", validation_state.get())}
<textarea
id=&control.data.name
name=&control.data.name
placeholder=control.data.placeholder.as_ref()
class="form_input"
prop:value=move || value_getter.get()
on:change=move |ev| {
value_setter(event_target_value(&ev));
}
>
</textarea>
</div>
}
.into_view()
}
fn custom_component(&self, view: View) -> View {
view
}
fn hidden(
&self,
control: Rc<ControlRenderData<Self, HiddenData>>,
value_getter: Option<Signal<String>>,
) -> View {
let value_getter = move || value_getter.map(|g| g.get());
view! { <input name=&control.data.name prop:value=value_getter style="visibility: hidden"/> }
.into_view()
}
fn radio_buttons(
&self,
control: Rc<ControlRenderData<Self, RadioButtonsData>>,
value_getter: Signal<<RadioButtonsData as ControlData>::ReturnType>,
value_setter: Rc<dyn Fn(<RadioButtonsData as ControlData>::ReturnType)>,
validation_state: Signal<Result<(), String>>,
) -> View {
let mut width = 1;
for style in control.styles.iter() {
match style {
GridFormStylingAttributes::Width(w) => width = *w,
}
}
let value_setter = Rc::new(value_setter);
let buttons_view = control
.data
.options
.iter()
.map(|o| {
let value_setter = value_setter.clone();
let o_clone1 = o.clone();
let o_clone2 = o.clone();
view! {
<input
type="radio"
id=o.clone()
_str
name=&control.data.name
value=o.clone()
prop:checked=move || { value_getter.get() == o_clone1 }
on:input=move |ev| {
let new_value = event_target_checked(&ev);
if new_value {
value_setter(o_clone2.clone());
}
}
/>
<label for=o>{o}</label>
<br/>
}
})
.collect_view();
view! {
<div style:grid-column=format!("span {}", width)>
<div>
<label for=&control.data.name class="form_label">
{control.data.label.as_ref()}
</label>
<span class="form_error">{move || validation_state.get().err()}</span>
</div>
<div
class="form_input"
class:form_input_invalid=move || validation_state.get().is_err()
>
{buttons_view}
</div>
</div>
}
.into_view()
} }
fn checkbox( fn checkbox(
@ -239,15 +312,7 @@ impl FormStyle for GridFormStyle {
value_getter: Signal<<CheckboxData as ControlData>::ReturnType>, value_getter: Signal<<CheckboxData as ControlData>::ReturnType>,
value_setter: Rc<dyn Fn(<CheckboxData as ControlData>::ReturnType)>, value_setter: Rc<dyn Fn(<CheckboxData as ControlData>::ReturnType)>,
) -> View { ) -> View {
let mut width = 1; let view = view! {
for style in control.styles.iter() {
match style {
GridFormStylingAttributes::Width(w) => width = *w,
}
}
view! {
<div style:grid-column=format!("span {}", width)>
<label for=&control.data.name class="form_label"> <label for=&control.data.name class="form_label">
{control.data.label.as_ref()} {control.data.label.as_ref()}
</label> </label>
@ -265,9 +330,10 @@ impl FormStyle for GridFormStyle {
<span>{control.data.label.as_ref()}</span> <span>{control.data.label.as_ref()}</span>
</label> </label>
</div>
} }
.into_view() .into_view();
self.custom_component(&control.styles, view)
} }
fn stepper( fn stepper(
@ -277,15 +343,7 @@ impl FormStyle for GridFormStyle {
value_setter: Rc<dyn Fn(<StepperData as ControlData>::ReturnType)>, value_setter: Rc<dyn Fn(<StepperData as ControlData>::ReturnType)>,
validation_state: Signal<Result<(), String>>, validation_state: Signal<Result<(), String>>,
) -> View { ) -> View {
let mut width = 1; let view = view! {
for style in control.styles.iter() {
match style {
GridFormStylingAttributes::Width(w) => width = *w,
}
}
view! {
<div style:grid-column=format!("span {}", width)>
<div> <div>
<label for=&control.data.name class="form_label"> <label for=&control.data.name class="form_label">
{control.data.label.as_ref()} {control.data.label.as_ref()}
@ -304,17 +362,10 @@ impl FormStyle for GridFormStyle {
class="form_input" class="form_input"
/> />
</div>
} }
.into_view() .into_view();
}
fn output( self.custom_component(&control.styles, view)
&self,
_control: Rc<ControlRenderData<Self, OutputData>>,
value_getter: Option<Signal<String>>,
) -> View {
view! { <div>{move || value_getter.map(|g| g.get())}</div> }.into_view()
} }
fn slider( fn slider(
@ -324,15 +375,7 @@ impl FormStyle for GridFormStyle {
value_setter: Rc<dyn Fn(<SliderData as ControlData>::ReturnType)>, value_setter: Rc<dyn Fn(<SliderData as ControlData>::ReturnType)>,
validation_state: Signal<Result<(), String>>, validation_state: Signal<Result<(), String>>,
) -> View { ) -> View {
let mut width = 1; let view = view! {
for style in control.styles.iter() {
match style {
GridFormStylingAttributes::Width(w) => width = *w,
}
}
view! {
<div style:grid-column=format!("span {}", width)>
<div> <div>
<label for=&control.data.name class="form_label"> <label for=&control.data.name class="form_label">
{control.data.label.as_ref()} {control.data.label.as_ref()}
@ -355,68 +398,9 @@ impl FormStyle for GridFormStyle {
class="form_input" class="form_input"
/> />
</div>
} }
.into_view() .into_view();
}
fn button<FD: FormToolData>( self.custom_component(&control.styles, view)
&self,
control: Rc<ControlRenderData<Self, ButtonData<FD>>>,
data_signal: RwSignal<FD>,
) -> View {
let mut width = 1;
for style in control.styles.iter() {
match style {
GridFormStylingAttributes::Width(w) => width = *w,
}
}
let action = control.data.action.clone();
let on_click = move |ev: MouseEvent| {
if let Some(action) = action.clone() {
action(ev, data_signal)
}
};
view! {
<button
type="button"
class="form_button"
on:click=on_click
style:grid-column=format!("span {}", width)
>
{&control.data.text}
</button>
}
.into_view()
}
fn group(&self, group: Rc<ControlRenderData<Self, View>>) -> View {
let mut width = 12;
for style in group.styles.iter() {
match style {
GridFormStylingAttributes::Width(w) => width = *w,
}
}
view! {
<div class="form_group form_grid" style:grid-column=format!("span {}", width)>
{&group.data}
</div>
}
.into_view()
}
fn spacer(&self, control: Rc<ControlRenderData<Self, SpacerData>>) -> View {
let mut width = 12;
for style in control.styles.iter() {
match style {
GridFormStylingAttributes::Width(w) => width = *w,
}
}
view! { <div style:grid-column=format!("span {}", width) style:height=control.data.height.as_ref()></div> }
.into_view()
} }
} }

View File

@ -1,7 +1,5 @@
mod grid_form; mod grid_form;
use std::rc::Rc;
use crate::{ use crate::{
controls::{ controls::{
button::ButtonData, checkbox::CheckboxData, heading::HeadingData, hidden::HiddenData, button::ButtonData, checkbox::CheckboxData, heading::HeadingData, hidden::HiddenData,
@ -12,10 +10,19 @@ use crate::{
FormToolData, FormToolData,
}; };
use leptos::{RwSignal, Signal, View}; use leptos::{RwSignal, Signal, View};
use std::rc::Rc;
pub use grid_form::{GridFormStyle, GridFormStylingAttributes}; pub use grid_form::{GFStyleAttr, GridFormStyle};
/// Defines a way to style a form.
///
/// Provides methods for rendering all the controls.
/// This provider is in charge of figuring out what html elements should be
/// rendered and how they should be styled.
pub trait FormStyle: 'static { pub trait FormStyle: 'static {
/// The type of styling attributes that this [`FormStyle`] takes.
///
/// These styling attributes can be applied to any of the controls.
type StylingAttributes; type StylingAttributes;
/// Render any containing components for the form. /// Render any containing components for the form.
@ -26,12 +33,68 @@ 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, form: ControlRenderData<Self, View>) -> View;
/// Wraps the view of a custom component.
///
/// The rendering of the custom component is given by the `inner` view.
/// Here the styler has a chance wrap the view with other components, or
/// applying the styling attributes.
///
/// This method does not need to be called by the custom component, but
/// the custom component may make use of this method for the
/// aforementioned reasons.
fn custom_component(&self, style: &[Self::StylingAttributes], inner: View) -> View;
/// Renders a group.
///
/// The inner view for the group's components is provided.
/// This method should wrap the group in any visual grouping elements,
/// and apply the styles.
fn group(&self, group: Rc<ControlRenderData<Self, View>>) -> View;
/// Renders a spacer.
///
/// See [`SpacerData`].
fn spacer(&self, control: Rc<ControlRenderData<Self, SpacerData>>) -> View;
/// Renders a heading for a section of the form.
fn heading(&self, control: Rc<ControlRenderData<Self, HeadingData>>) -> View; fn heading(&self, control: Rc<ControlRenderData<Self, HeadingData>>) -> View;
/// Renders a submit button.
///
/// See [`SubmitData`].
fn submit(&self, control: Rc<ControlRenderData<Self, SubmitData>>) -> View;
/// Renders a button.
///
/// See [`BuuttonData`]
fn button<FD: FormToolData>(
&self,
control: Rc<ControlRenderData<Self, ButtonData<FD>>>,
data_signal: RwSignal<FD>,
) -> View;
/// Renders some output text.
///
/// See [`OutputData`].
fn output(
&self,
control: Rc<ControlRenderData<Self, OutputData>>,
value_getter: Option<Signal<String>>,
) -> View;
/// Renders a input control that should be hidden from the user.
///
/// See [`HiddenData`].
fn hidden( fn hidden(
&self, &self,
control: Rc<ControlRenderData<Self, HiddenData>>, control: Rc<ControlRenderData<Self, HiddenData>>,
value_getter: Option<Signal<String>>, value_getter: Option<Signal<String>>,
) -> View; ) -> View;
/// Renders a text input control.
///
/// See [`TextInputData`].
fn text_input( fn text_input(
&self, &self,
control: Rc<ControlRenderData<Self, TextInputData>>, control: Rc<ControlRenderData<Self, TextInputData>>,
@ -39,6 +102,10 @@ pub trait FormStyle: 'static {
value_setter: Rc<dyn Fn(<TextInputData as ControlData>::ReturnType)>, value_setter: Rc<dyn Fn(<TextInputData as ControlData>::ReturnType)>,
validation_state: Signal<Result<(), String>>, validation_state: Signal<Result<(), String>>,
) -> View; ) -> View;
/// Renders a text area control.
///
/// See [`TextAreaData`].
fn text_area( fn text_area(
&self, &self,
control: Rc<ControlRenderData<Self, TextAreaData>>, control: Rc<ControlRenderData<Self, TextAreaData>>,
@ -46,6 +113,10 @@ pub trait FormStyle: 'static {
value_setter: Rc<dyn Fn(<TextAreaData as ControlData>::ReturnType)>, value_setter: Rc<dyn Fn(<TextAreaData as ControlData>::ReturnType)>,
validation_state: Signal<Result<(), String>>, validation_state: Signal<Result<(), String>>,
) -> View; ) -> View;
/// Renders a group of radio buttons.
///
/// See [`RadioButtonsData`].
fn radio_buttons( fn radio_buttons(
&self, &self,
control: Rc<ControlRenderData<Self, RadioButtonsData>>, control: Rc<ControlRenderData<Self, RadioButtonsData>>,
@ -53,6 +124,10 @@ pub trait FormStyle: 'static {
value_setter: Rc<dyn Fn(<RadioButtonsData as ControlData>::ReturnType)>, value_setter: Rc<dyn Fn(<RadioButtonsData as ControlData>::ReturnType)>,
validation_state: Signal<Result<(), String>>, validation_state: Signal<Result<(), String>>,
) -> View; ) -> View;
/// Renders a select (or dropdown) control.
///
/// See [`SelectData`].
fn select( fn select(
&self, &self,
control: Rc<ControlRenderData<Self, SelectData>>, control: Rc<ControlRenderData<Self, SelectData>>,
@ -60,17 +135,20 @@ pub trait FormStyle: 'static {
value_setter: Rc<dyn Fn(<SelectData as ControlData>::ReturnType)>, value_setter: Rc<dyn Fn(<SelectData as ControlData>::ReturnType)>,
validation_state: Signal<Result<(), String>>, validation_state: Signal<Result<(), String>>,
) -> View; ) -> View;
fn button<FD: FormToolData>(
&self, /// Renders a checkbox control.
control: Rc<ControlRenderData<Self, ButtonData<FD>>>, ///
data_signal: RwSignal<FD>, /// See [`CheckboxData`].
) -> View;
fn checkbox( fn checkbox(
&self, &self,
control: Rc<ControlRenderData<Self, CheckboxData>>, control: Rc<ControlRenderData<Self, CheckboxData>>,
value_getter: Signal<<CheckboxData as ControlData>::ReturnType>, value_getter: Signal<<CheckboxData as ControlData>::ReturnType>,
value_setter: Rc<dyn Fn(<CheckboxData as ControlData>::ReturnType)>, value_setter: Rc<dyn Fn(<CheckboxData as ControlData>::ReturnType)>,
) -> View; ) -> View;
/// Renders a stepper control.
///
/// See [`StepperData`].
fn stepper( fn stepper(
&self, &self,
control: Rc<ControlRenderData<Self, StepperData>>, control: Rc<ControlRenderData<Self, StepperData>>,
@ -78,11 +156,10 @@ pub trait FormStyle: 'static {
value_setter: Rc<dyn Fn(<StepperData as ControlData>::ReturnType)>, value_setter: Rc<dyn Fn(<StepperData as ControlData>::ReturnType)>,
validation_state: Signal<Result<(), String>>, validation_state: Signal<Result<(), String>>,
) -> View; ) -> View;
fn output(
&self, /// Renders a slider control.
control: Rc<ControlRenderData<Self, OutputData>>, ///
value_getter: Option<Signal<String>>, /// See [`SliderData`].
) -> View;
fn slider( fn slider(
&self, &self,
control: Rc<ControlRenderData<Self, SliderData>>, control: Rc<ControlRenderData<Self, SliderData>>,
@ -90,8 +167,4 @@ pub trait FormStyle: 'static {
value_setter: Rc<dyn Fn(<SliderData as ControlData>::ReturnType)>, value_setter: Rc<dyn Fn(<SliderData as ControlData>::ReturnType)>,
validation_state: Signal<Result<(), String>>, validation_state: Signal<Result<(), String>>,
) -> View; ) -> View;
fn submit(&self, control: Rc<ControlRenderData<Self, SubmitData>>) -> View;
fn custom_component(&self, view: View) -> View;
fn group(&self, group: Rc<ControlRenderData<Self, View>>) -> View;
fn spacer(&self, control: Rc<ControlRenderData<Self, SpacerData>>) -> View;
} }