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;
/// Data used for the button control.
pub struct ButtonData<FD: FormToolData> {
pub text: String,
pub action: Option<Rc<ButtonAction<FD>>>,
@ -31,18 +32,21 @@ impl<FD: FormToolData> Clone for ButtonData<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 {
let button_builder = ButtonBuilder::new();
let control = builder(button_builder);
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 {
let button_builder = ButtonBuilder::new();
let control = builder(button_builder, self.cx.clone());
self.button_helper(control)
}
/// The common functionality for adding a button.
fn button_helper(mut self, control: ButtonBuilder<FD>) -> Self {
let render_data = ControlRenderData {
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(crate) styles: Vec<<FD::Style as FormStyle>::StylingAttributes>,
pub(crate) data: ButtonData<FD>,
@ -81,6 +86,7 @@ pub struct ButtonBuilder<FD: FormToolData> {
}
impl<FD: FormToolData> ButtonBuilder<FD> {
/// Creates a new [`ButtonBuilder`].
fn new() -> Self {
ButtonBuilder {
styles: Vec::default(),
@ -90,8 +96,6 @@ impl<FD: FormToolData> ButtonBuilder<FD> {
}
/// Sets the function to decide when to render the control.
///
/// Validations for components that are not shown DO NOT run.
pub fn show_when(
mut self,
when: impl Fn(Signal<FD>, Rc<FD::Context>) -> bool + 'static,
@ -100,16 +104,19 @@ impl<FD: FormToolData> ButtonBuilder<FD> {
self
}
/// Adds a styling attribute to the button.
pub fn style(mut self, style: <FD::Style as FormStyle>::StylingAttributes) -> Self {
self.styles.push(style);
self
}
/// Sets the text of the button.
pub fn text(mut self, text: impl ToString) -> Self {
self.data.text = text.to_string();
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 {
self.data.action = Some(Rc::new(action));
self

View File

@ -3,6 +3,7 @@ use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
use leptos::{Signal, View};
use std::rc::Rc;
/// Data used for the checkbox control.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct CheckboxData {
pub name: String,
@ -24,6 +25,7 @@ impl ControlData for CheckboxData {
}
impl<FD: FormToolData> FormBuilder<FD> {
/// Builds a checkbox and adds it to the form.
pub fn checkbox<FDT: Clone + PartialEq + 'static>(
self,
builder: impl BuilderFn<ControlBuilder<FD, CheckboxData, FDT>>,
@ -31,6 +33,7 @@ impl<FD: FormToolData> FormBuilder<FD> {
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>(
self,
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> {
/// 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 {
self.data.name = control_name.to_string();
self
}
/// Sets the text of the checkbox's label.
pub fn labeled(mut self, label: impl ToString) -> Self {
self.data.label = Some(label.to_string());
self

View File

@ -2,6 +2,7 @@ use super::{BuilderCxFn, BuilderFn, ControlBuilder, ControlData};
use crate::{FormBuilder, FormToolData};
impl<FD: FormToolData> FormBuilder<FD> {
/// Builds a custom component and adds it to the form.
pub fn custom<CC: ControlData, FDT: Clone + PartialEq + 'static>(
mut self,
control_data: CC,
@ -13,6 +14,8 @@ impl<FD: FormToolData> FormBuilder<FD> {
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>(
mut self,
control_data: CC,
@ -24,6 +27,8 @@ impl<FD: FormToolData> FormBuilder<FD> {
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>(
self,
builder: impl BuilderFn<ControlBuilder<FD, CC, FDT>>,
@ -31,6 +36,8 @@ impl<FD: FormToolData> FormBuilder<FD> {
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>(
self,
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};
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 {
let mut group_builder = FormBuilder::new_group(self.cx);
group_builder = builder(group_builder);
@ -30,6 +34,7 @@ impl<FD: FormToolData> FormBuilder<FD> {
});
let view = fs.group(render_data.clone());
// TODO: add conditional rendering
let validation_cb = move || {
let mut success = true;

View File

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

View File

@ -6,6 +6,7 @@ use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
use leptos::{Signal, View};
use std::rc::Rc;
/// Data used for the hidden control.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct HiddenData {
pub name: String,
@ -23,10 +24,19 @@ impl VanityControlData for HiddenData {
impl GetterVanityControlData for HiddenData {}
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 {
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(
self,
builder: impl BuilderCxFn<VanityControlBuilder<FD, HiddenData>, FD::Context>,
@ -36,6 +46,11 @@ impl<FD: FormToolData> FormBuilder<FD> {
}
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 {
self.data.name = control_name.to_string();
self

View File

@ -6,6 +6,7 @@ use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
use leptos::{Signal, View};
use std::rc::Rc;
/// Data used for the output control.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct OutputData;
@ -21,10 +22,19 @@ impl VanityControlData for OutputData {
impl GetterVanityControlData for OutputData {}
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 {
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(
self,
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 std::rc::Rc;
/// Data used for the radio buttons control.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct RadioButtonsData {
pub name: 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 {
@ -28,6 +32,7 @@ impl ControlData for RadioButtonsData {
impl ValidatedControlData for RadioButtonsData {}
impl<FD: FormToolData> FormBuilder<FD> {
/// Builds a radio buttons control and adds it to the form.
pub fn radio_buttons<FDT: Clone + PartialEq + 'static>(
self,
builder: impl BuilderFn<ControlBuilder<FD, RadioButtonsData, FDT>>,
@ -35,6 +40,8 @@ impl<FD: FormToolData> FormBuilder<FD> {
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>(
self,
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> {
/// 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 {
self.data.name = control_name.to_string();
self
}
/// Sets the label for the radio button group.
pub fn labeled(mut self, label: impl ToString) -> Self {
self.data.label = Some(label.to_string());
self
}
/// Adds the option to the radio button group.
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
}
/// 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 {
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
}

View File

@ -5,6 +5,7 @@ use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
use leptos::{Signal, View};
use std::rc::Rc;
/// Data used for the select control.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct SelectData {
pub name: String,
@ -31,12 +32,16 @@ impl ControlData for SelectData {
impl ValidatedControlData for SelectData {}
impl<FD: FormToolData> FormBuilder<FD> {
/// Builds a select control and adds it to the form.
pub fn select<FDT: Clone + PartialEq + 'static>(
self,
builder: impl BuilderFn<ControlBuilder<FD, SelectData, FDT>>,
) -> Self {
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>(
self,
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> {
/// 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 {
self.data.name = control_name.to_string();
self
}
/// Sets the label for the select.
pub fn labeled(mut self, label: impl ToString) -> Self {
self.data.label = Some(label.to_string());
self
}
/// Adds the option to the select.
pub fn with_option(mut self, option: impl ToString) -> Self {
self.data
.options
@ -63,6 +75,8 @@ impl<FD: FormToolData, FDT> ControlBuilder<FD, SelectData, FDT> {
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 {
self.data
.options
@ -70,6 +84,7 @@ impl<FD: FormToolData, FDT> ControlBuilder<FD, SelectData, FDT> {
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 {
for option in options {
self.data
@ -79,6 +94,8 @@ impl<FD: FormToolData, FDT> ControlBuilder<FD, SelectData, FDT> {
self
}
/// Adds all the (display_string, value) pairs in the provided iterator
/// to the select.
pub fn with_options_valued(
mut self,
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 std::{ops::RangeInclusive, rc::Rc};
/// Data used for the slider control.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SliderData {
pub name: String,
@ -37,6 +38,7 @@ impl ControlData for SliderData {
}
impl<FD: FormToolData> FormBuilder<FD> {
/// Builds a slider (or range) control and adds it to the form.
pub fn slider<FDT: Clone + PartialEq + 'static>(
self,
builder: impl BuilderFn<ControlBuilder<FD, SliderData, FDT>>,
@ -44,6 +46,8 @@ impl<FD: FormToolData> FormBuilder<FD> {
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>(
self,
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> {
/// 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 {
self.data.name = control_name.to_string();
self
}
/// Sets the label for the slider.
pub fn labeled(mut self, label: impl ToString) -> Self {
self.data.label = Some(label.to_string());
self
}
/// Sets the minimum value for the slider.
pub fn min(mut self, min: i32) -> Self {
self.data.min = min;
self
}
/// Sets the maximum value for the slider.
pub fn max(mut self, max: i32) -> Self {
self.data.max = max;
self
}
/// Sets the minimum and maximum values for the slider by providing a
/// range.
pub fn range(mut self, range: RangeInclusive<i32>) -> Self {
self.data.min = *range.start();
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 std::rc::Rc;
/// Data used for the spacer control.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct SpacerData {
pub height: Option<String>,
@ -19,10 +20,12 @@ impl VanityControlData for SpacerData {
}
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 {
self.new_vanity(builder)
}
/// Builds a spacer using the form's context and adds it to the form.
pub fn spacer_cx(
self,
builder: impl BuilderCxFn<VanityControlBuilder<FD, SpacerData>, FD::Context>,
@ -32,6 +35,9 @@ impl<FD: FormToolData> FormBuilder<FD> {
}
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 {
self.data.height = Some(height.to_string());
self

View File

@ -5,6 +5,7 @@ use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
use leptos::{Signal, View};
use std::{ops::RangeInclusive, rc::Rc};
/// Data used for the stepper control.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct StepperData {
pub name: String,
@ -30,6 +31,7 @@ impl ControlData for StepperData {
impl ValidatedControlData for StepperData {}
impl<FD: FormToolData> FormBuilder<FD> {
/// Builds a stepper control and adds it to the form.
pub fn stepper<FDT: Clone + PartialEq + 'static>(
self,
builder: impl BuilderFn<ControlBuilder<FD, StepperData, FDT>>,
@ -37,6 +39,8 @@ impl<FD: FormToolData> FormBuilder<FD> {
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>(
self,
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> {
/// 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 {
self.data.name = control_name.to_string();
self
}
/// Sets the label of the stepper.
pub fn labeled(mut self, label: impl ToString) -> Self {
self.data.label = Some(label.to_string());
self
}
/// Sets the step ammount.
pub fn step(mut self, step: i32) -> Self {
self.data.step = Some(step);
self
}
/// Sets a minimum value.
pub fn min(mut self, min: i32) -> Self {
self.data.min = Some(min);
self
}
/// Sets a maximum value.
pub fn max(mut self, max: i32) -> Self {
self.data.max = Some(max);
self
}
/// Sets the minimum and maximum values using the range.
pub fn range(mut self, range: RangeInclusive<i32>) -> Self {
self.data.min = Some(*range.start());
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 std::rc::Rc;
/// Data used for the submit button control.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct SubmitData {
pub text: String,
@ -19,10 +20,13 @@ impl VanityControlData for SubmitData {
}
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 {
self.new_vanity(builder)
}
/// Builds a submit button using the form's context and adds it to the
/// form.
pub fn submit_cx(
self,
builder: impl BuilderCxFn<VanityControlBuilder<FD, SubmitData>, FD::Context>,
@ -32,6 +36,7 @@ impl<FD: FormToolData> FormBuilder<FD> {
}
impl<FD: FormToolData> VanityControlBuilder<FD, SubmitData> {
/// Sets the submit button's text.
pub fn text(mut self, text: impl ToString) -> Self {
self.data.text = text.to_string();
self

View File

@ -5,10 +5,13 @@ use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
use leptos::{Signal, View};
use std::rc::Rc;
/// Data used for the text area control.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct TextAreaData {
pub name: String,
pub placeholder: Option<String>,
pub label: Option<String>,
pub initial_text: String,
}
impl ControlData for TextAreaData {
@ -27,6 +30,7 @@ impl ControlData for TextAreaData {
impl ValidatedControlData for TextAreaData {}
impl<FD: FormToolData> FormBuilder<FD> {
/// Builds a text area control and adds it to the form.
pub fn text_area<FDT: Clone + PartialEq + 'static>(
self,
builder: impl BuilderFn<ControlBuilder<FD, TextAreaData, FDT>>,
@ -34,6 +38,8 @@ impl<FD: FormToolData> FormBuilder<FD> {
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>(
self,
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> {
/// 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 {
self.data.placeholder = Some(placeholder.to_string());
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 std::rc::Rc;
/// Data used for the text input control.
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TextInputData {
pub name: String,
@ -42,6 +43,7 @@ impl ControlData for TextInputData {
impl ValidatedControlData for TextInputData {}
impl<FD: FormToolData> FormBuilder<FD> {
/// Builds a text input control and adds it to the form.
pub fn text_input<FDT: Clone + PartialEq + 'static>(
self,
builder: impl BuilderFn<ControlBuilder<FD, TextInputData, FDT>>,
@ -49,6 +51,8 @@ impl<FD: FormToolData> FormBuilder<FD> {
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>(
self,
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> {
/// 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 {
self.data.name = control_name.to_string();
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 {
self.data.placeholder = Some(placeholder.to_string());
self
}
pub fn label(mut self, label: impl ToString) -> Self {
self.data.label = Some(label.to_string());
self
}
/// Sets the intial_text for the text input.
pub fn initial_text(mut self, text: impl ToString) -> Self {
self.data.initial_text = text.to_string();
self
}
/// Sets the text input to be the "password" type.
pub fn password(mut self) -> Self {
self.data.input_type = "password";
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
/// 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> {
let validator = self.validator();
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.
///
/// 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.
/// This trait defines a function that can be used to build all the data
/// needed to physically lay out a form, and how that data should be parsed
/// and validated.
pub trait FormToolData: Clone + 'static {
/// The style that this form uses.
type Style: FormStyle;
/// The context that this form is rendered in.
///
/// This will need to be provided when building a form or a validator.
/// Therefore, you will need to be able to replicate this context
/// on the client for rendering and the server for validating.
type Context: 'static;
/// Defines how the form should be 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.
///
@ -100,12 +108,13 @@ pub trait FormToolData: Clone + 'static {
/// [`ActionForm`](leptos_router::ActionForm) that sends the form data
/// directly by calling the server function.
///
/// By doing this, we avoid doing the [`FromFormData`](leptos_router::FromFormData)
/// By doing this, we avoid doing the
/// [`FromFormData`](leptos_router::FromFormData)
/// conversion. However, to support
/// [Progressive Enhancement](https://book.leptos.dev/progressive_enhancement/index.html),
/// you should name the form elements to work with a plain ActionForm anyway.
/// If progresssive enhancement is not important to you, you may freely
/// use this version
/// you should name the form elements to work with a plain ActionForm
/// anyway. If progresssive enhancement is not important to you, you may
/// freely use this version.
///
/// For the other ways to construct a [`Form`], see:
/// - [`get_action_form`](Self::get_action_form)

View File

@ -19,11 +19,6 @@ use web_sys::{FormData, SubmitEvent};
/// A builder for laying out forms.
///
/// 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(crate) cx: Rc<FD::Context>,
/// 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 {
self.styles.push(style);
self
}
/// Adds a new vanity control to the form.
pub(crate) fn new_vanity<C: VanityControlData + Default>(
mut self,
builder: impl BuilderFn<VanityControlBuilder<FD, C>>,
@ -71,6 +68,7 @@ impl<FD: FormToolData> FormBuilder<FD> {
self
}
/// Adds a new vanity control to the form using the form's context.
pub(crate) fn new_vanity_cx<C: VanityControlData + Default>(
mut self,
builder: impl BuilderCxFn<VanityControlBuilder<FD, C>, FD::Context>,
@ -81,6 +79,7 @@ impl<FD: FormToolData> FormBuilder<FD> {
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>(
mut self,
builder: impl BuilderFn<ControlBuilder<FD, C, FDT>>,
@ -91,6 +90,7 @@ impl<FD: FormToolData> FormBuilder<FD> {
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>(
mut self,
builder: impl BuilderCxFn<ControlBuilder<FD, C, FDT>, FD::Context>,
@ -101,8 +101,8 @@ impl<FD: FormToolData> FormBuilder<FD> {
self
}
// TODO: test this from a user context. A user adding a custom defined component.
pub fn add_vanity<C: VanityControlData>(
/// Adds a vanity control to the form.
pub(crate) fn add_vanity<C: VanityControlData>(
&mut self,
vanity_control: VanityControlBuilder<FD, C>,
) {
@ -135,14 +135,20 @@ impl<FD: FormToolData> FormBuilder<FD> {
self.render_fns.push(Box::new(render_fn));
}
// TODO: test this from a user context. A user adding a custom defined component.
pub fn add_control<C: ControlData, FDT: Clone + PartialEq + 'static>(
/// Adds a control to the form.
pub(crate) fn add_control<C: ControlData, FDT: Clone + PartialEq + 'static>(
&mut self,
control: ControlBuilder<FD, C, FDT>,
) {
let built_control_data = match control.build() {
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() {
@ -174,6 +180,8 @@ impl<FD: FormToolData> FormBuilder<FD> {
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>(
fd: RwSignal<FD>,
fs: Rc<FD::Style>,
@ -271,6 +279,7 @@ impl<FD: FormToolData> FormBuilder<FD> {
(view, validation_cb)
}
/// Helper for creating a setter function.
fn create_value_setter<CRT: 'static, FDT: 'static>(
validation_cb: Box<dyn Fn() -> bool + 'static>,
validation_signal_set: WriteSignal<Result<(), String>>,
@ -301,6 +310,7 @@ impl<FD: FormToolData> FormBuilder<FD> {
Rc::new(value_setter)
}
/// Builds the direct send version of the form.
pub(crate) fn build_form<ServFn>(
self,
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>(
self,
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> {
let fd = create_rw_signal(fd);
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> {
FormValidator {
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;
mod form;
mod form_builder;

View File

@ -12,22 +12,129 @@ use leptos::*;
use std::rc::Rc;
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),
/// 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)]
pub struct GridFormStyle;
impl FormStyle for GridFormStyle {
type StylingAttributes = GridFormStylingAttributes;
type StylingAttributes = GFStyleAttr;
fn form_frame(&self, form: ControlRenderData<Self, View>) -> 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 {
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(
@ -37,16 +144,7 @@ impl FormStyle for GridFormStyle {
value_setter: Rc<dyn Fn(<TextInputData as ControlData>::ReturnType)>,
validation_state: Signal<Result<(), String>>,
) -> View {
// TODO: extract this to a common thing
let mut width = 1;
for style in control.styles.iter() {
match style {
GridFormStylingAttributes::Width(w) => width = *w,
}
}
view! {
<div style:grid-column=format!("span {}", width)>
let view = view! {
<div>
<label for=&control.data.name class="form_label">
{control.data.label.as_ref()}
@ -62,13 +160,104 @@ impl FormStyle for GridFormStyle {
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();
self.custom_component(&control.styles, 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 {
let view = view! {
<div>
<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>
<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()
.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(
@ -78,13 +267,6 @@ impl FormStyle for GridFormStyle {
value_setter: Rc<dyn Fn(<SelectData 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 options_view = control
.data
.options
@ -100,8 +282,7 @@ impl FormStyle for GridFormStyle {
})
.collect_view();
view! {
<div style:grid-column=format!("span {}", width)>
let view = view! {
<div>
<label for=&control.data.name class="form_label">
{control.data.label.as_ref()}
@ -119,118 +300,10 @@ impl FormStyle for GridFormStyle {
{options_view}
</select>
</div>
}
.into_view()
}
.into_view();
fn submit(&self, control: Rc<ControlRenderData<Self, SubmitData>>) -> 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()
self.custom_component(&control.styles, view)
}
fn checkbox(
@ -239,15 +312,7 @@ impl FormStyle for GridFormStyle {
value_getter: Signal<<CheckboxData as ControlData>::ReturnType>,
value_setter: Rc<dyn Fn(<CheckboxData as ControlData>::ReturnType)>,
) -> View {
let mut width = 1;
for style in control.styles.iter() {
match style {
GridFormStylingAttributes::Width(w) => width = *w,
}
}
view! {
<div style:grid-column=format!("span {}", width)>
let view = view! {
<label for=&control.data.name class="form_label">
{control.data.label.as_ref()}
</label>
@ -265,9 +330,10 @@ impl FormStyle for GridFormStyle {
<span>{control.data.label.as_ref()}</span>
</label>
</div>
}
.into_view()
.into_view();
self.custom_component(&control.styles, view)
}
fn stepper(
@ -277,15 +343,7 @@ impl FormStyle for GridFormStyle {
value_setter: Rc<dyn Fn(<StepperData 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,
}
}
view! {
<div style:grid-column=format!("span {}", width)>
let view = view! {
<div>
<label for=&control.data.name class="form_label">
{control.data.label.as_ref()}
@ -304,17 +362,10 @@ impl FormStyle for GridFormStyle {
class="form_input"
/>
</div>
}
.into_view()
}
.into_view();
fn output(
&self,
_control: Rc<ControlRenderData<Self, OutputData>>,
value_getter: Option<Signal<String>>,
) -> View {
view! { <div>{move || value_getter.map(|g| g.get())}</div> }.into_view()
self.custom_component(&control.styles, view)
}
fn slider(
@ -324,15 +375,7 @@ impl FormStyle for GridFormStyle {
value_setter: Rc<dyn Fn(<SliderData 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,
}
}
view! {
<div style:grid-column=format!("span {}", width)>
let view = view! {
<div>
<label for=&control.data.name class="form_label">
{control.data.label.as_ref()}
@ -355,68 +398,9 @@ impl FormStyle for GridFormStyle {
class="form_input"
/>
</div>
}
.into_view()
}
.into_view();
fn button<FD: FormToolData>(
&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()
self.custom_component(&control.styles, view)
}
}

View File

@ -1,7 +1,5 @@
mod grid_form;
use std::rc::Rc;
use crate::{
controls::{
button::ButtonData, checkbox::CheckboxData, heading::HeadingData, hidden::HiddenData,
@ -12,10 +10,19 @@ use crate::{
FormToolData,
};
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 {
/// The type of styling attributes that this [`FormStyle`] takes.
///
/// These styling attributes can be applied to any of the controls.
type StylingAttributes;
/// 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
/// wrapping should be done with `div` or similar elements.
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;
/// 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(
&self,
control: Rc<ControlRenderData<Self, HiddenData>>,
value_getter: Option<Signal<String>>,
) -> View;
/// Renders a text input control.
///
/// See [`TextInputData`].
fn text_input(
&self,
control: Rc<ControlRenderData<Self, TextInputData>>,
@ -39,6 +102,10 @@ pub trait FormStyle: 'static {
value_setter: Rc<dyn Fn(<TextInputData as ControlData>::ReturnType)>,
validation_state: Signal<Result<(), String>>,
) -> View;
/// Renders a text area control.
///
/// See [`TextAreaData`].
fn text_area(
&self,
control: Rc<ControlRenderData<Self, TextAreaData>>,
@ -46,6 +113,10 @@ pub trait FormStyle: 'static {
value_setter: Rc<dyn Fn(<TextAreaData as ControlData>::ReturnType)>,
validation_state: Signal<Result<(), String>>,
) -> View;
/// Renders a group of radio buttons.
///
/// See [`RadioButtonsData`].
fn radio_buttons(
&self,
control: Rc<ControlRenderData<Self, RadioButtonsData>>,
@ -53,6 +124,10 @@ pub trait FormStyle: 'static {
value_setter: Rc<dyn Fn(<RadioButtonsData as ControlData>::ReturnType)>,
validation_state: Signal<Result<(), String>>,
) -> View;
/// Renders a select (or dropdown) control.
///
/// See [`SelectData`].
fn select(
&self,
control: Rc<ControlRenderData<Self, SelectData>>,
@ -60,17 +135,20 @@ pub trait FormStyle: 'static {
value_setter: Rc<dyn Fn(<SelectData as ControlData>::ReturnType)>,
validation_state: Signal<Result<(), String>>,
) -> View;
fn button<FD: FormToolData>(
&self,
control: Rc<ControlRenderData<Self, ButtonData<FD>>>,
data_signal: RwSignal<FD>,
) -> View;
/// Renders a checkbox control.
///
/// See [`CheckboxData`].
fn checkbox(
&self,
control: Rc<ControlRenderData<Self, CheckboxData>>,
value_getter: Signal<<CheckboxData as ControlData>::ReturnType>,
value_setter: Rc<dyn Fn(<CheckboxData as ControlData>::ReturnType)>,
) -> View;
/// Renders a stepper control.
///
/// See [`StepperData`].
fn stepper(
&self,
control: Rc<ControlRenderData<Self, StepperData>>,
@ -78,11 +156,10 @@ pub trait FormStyle: 'static {
value_setter: Rc<dyn Fn(<StepperData as ControlData>::ReturnType)>,
validation_state: Signal<Result<(), String>>,
) -> View;
fn output(
&self,
control: Rc<ControlRenderData<Self, OutputData>>,
value_getter: Option<Signal<String>>,
) -> View;
/// Renders a slider control.
///
/// See [`SliderData`].
fn slider(
&self,
control: Rc<ControlRenderData<Self, SliderData>>,
@ -90,8 +167,4 @@ pub trait FormStyle: 'static {
value_setter: Rc<dyn Fn(<SliderData as ControlData>::ReturnType)>,
validation_state: Signal<Result<(), String>>,
) -> 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;
}