generated from mitchell/rust_template
third round of dev
This commit is contained in:
parent
80a967346c
commit
857dcec00f
@ -1,6 +1,6 @@
|
|||||||
use leptos::View;
|
use leptos::View;
|
||||||
|
|
||||||
use super::{VanityControl, VanityControlBuilder, VanityControlData};
|
use super::{ControlRenderData, VanityControlBuilder, VanityControlData};
|
||||||
use crate::{
|
use crate::{
|
||||||
form::{FormBuilder, FormData},
|
form::{FormBuilder, FormData},
|
||||||
styles::FormStyle,
|
styles::FormStyle,
|
||||||
@ -12,10 +12,7 @@ pub struct HeadingData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl VanityControlData for HeadingData {
|
impl VanityControlData for HeadingData {
|
||||||
fn build_control<FD: FormData, FS: FormStyle>(
|
fn build_control<FS: FormStyle>(fs: &FS, control: ControlRenderData<FS, Self>) -> View {
|
||||||
fs: &FS,
|
|
||||||
control: VanityControl<FS, Self>,
|
|
||||||
) -> View {
|
|
||||||
fs.heading(control)
|
fs.heading(control)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -23,7 +20,7 @@ impl VanityControlData for HeadingData {
|
|||||||
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
|
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
|
||||||
pub fn heading(
|
pub fn heading(
|
||||||
self,
|
self,
|
||||||
builder: impl Fn(VanityControlBuilder<FS, HeadingData>) -> VanityControl<FS, HeadingData>,
|
builder: impl Fn(VanityControlBuilder<FS, HeadingData>) -> VanityControlBuilder<FS, HeadingData>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.new_vanity(builder)
|
self.new_vanity(builder)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,40 +7,50 @@ pub mod submit;
|
|||||||
pub mod text_area;
|
pub mod text_area;
|
||||||
pub mod text_input;
|
pub mod text_input;
|
||||||
|
|
||||||
pub type ValidationFn<FD> = dyn Fn(&FD) -> Result<(), String> + 'static;
|
pub trait ValidationFn<FD: FormData>: Fn(&FD) -> Result<(), String> + 'static {}
|
||||||
pub type ParseFn<FD, CReturnType> = dyn Fn(CReturnType, &mut FD) -> Result<(), String> + 'static;
|
pub trait ParseFn<FD: FormData, C: ControlData>:
|
||||||
|
Fn(C::ReturnType, &mut FD) -> Result<(), String> + 'static
|
||||||
|
{
|
||||||
|
}
|
||||||
|
// implement the trait for all valid types
|
||||||
|
impl<FD, T> ValidationFn<FD> for T
|
||||||
|
where
|
||||||
|
FD: FormData,
|
||||||
|
T: Fn(&FD) -> Result<(), String> + 'static,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
// implement the trait for all valid types
|
||||||
|
impl<FD, C, T> ParseFn<FD, C> for T
|
||||||
|
where
|
||||||
|
FD: FormData,
|
||||||
|
C: ControlData,
|
||||||
|
T: Fn(C::ReturnType, &mut FD) -> Result<(), String> + 'static,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
pub trait VanityControlData: 'static {
|
pub trait VanityControlData: 'static {
|
||||||
fn build_control<FD: FormData, FS: FormStyle>(
|
fn build_control<FS: FormStyle>(fs: &FS, control: ControlRenderData<FS, Self>) -> View;
|
||||||
fs: &FS,
|
|
||||||
control: VanityControl<FS, Self>,
|
|
||||||
) -> View;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ControlData: 'static {
|
pub trait ControlData: 'static {
|
||||||
type ReturnType: Clone;
|
type ReturnType: Clone;
|
||||||
|
|
||||||
// TODO: this should also return a getter for the data
|
// TODO: this should also return a getter for the data
|
||||||
fn build_control<FD: FormData, FS: FormStyle>(
|
fn build_control<FS: FormStyle>(
|
||||||
fs: &FS,
|
fs: &FS,
|
||||||
control: Control<FD, FS, Self>,
|
control: ControlRenderData<FS, Self>,
|
||||||
|
validation_state: Signal<Result<(), String>>,
|
||||||
) -> (View, Signal<Self::ReturnType>);
|
) -> (View, Signal<Self::ReturnType>);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct VanityControl<FS: FormStyle + ?Sized, C: VanityControlData + ?Sized> {
|
pub struct ControlRenderData<FS: FormStyle + ?Sized, C: ?Sized> {
|
||||||
pub data: Box<C>,
|
pub data: Box<C>,
|
||||||
pub style: Vec<FS::StylingAttributes>,
|
pub style: Vec<FS::StylingAttributes>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Control<FD: FormData + ?Sized, FS: FormStyle + ?Sized, C: ControlData + ?Sized> {
|
|
||||||
pub data: Box<C>,
|
|
||||||
pub parse_fn: Box<ParseFn<FD, C::ReturnType>>,
|
|
||||||
pub validation: Box<ValidationFn<FD>>,
|
|
||||||
pub style: Vec<FS::StylingAttributes>,
|
|
||||||
}
|
|
||||||
pub struct VanityControlBuilder<FS: FormStyle, C: VanityControlData> {
|
pub struct VanityControlBuilder<FS: FormStyle, C: VanityControlData> {
|
||||||
style_attributes: Vec<FS::StylingAttributes>,
|
pub(crate) style_attributes: Vec<FS::StylingAttributes>,
|
||||||
data: C,
|
pub(crate) data: C,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<FS: FormStyle, C: VanityControlData> VanityControlBuilder<FS, C> {
|
impl<FS: FormStyle, C: VanityControlData> VanityControlBuilder<FS, C> {
|
||||||
@ -51,15 +61,8 @@ impl<FS: FormStyle, C: VanityControlData> VanityControlBuilder<FS, C> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(self) -> VanityControl<FS, C> {
|
pub(crate) fn build(self) -> ControlRenderData<FS, C> {
|
||||||
self.into()
|
ControlRenderData {
|
||||||
}
|
|
||||||
}
|
|
||||||
impl<FS: FormStyle, C: VanityControlData> Into<VanityControl<FS, C>>
|
|
||||||
for VanityControlBuilder<FS, C>
|
|
||||||
{
|
|
||||||
fn into(self) -> VanityControl<FS, C> {
|
|
||||||
VanityControl {
|
|
||||||
data: Box::new(self.data),
|
data: Box::new(self.data),
|
||||||
style: self.style_attributes,
|
style: self.style_attributes,
|
||||||
}
|
}
|
||||||
@ -67,10 +70,10 @@ impl<FS: FormStyle, C: VanityControlData> Into<VanityControl<FS, C>>
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct ControlBuilder<FD: FormData, FS: FormStyle, C: ControlData> {
|
pub struct ControlBuilder<FD: FormData, FS: FormStyle, C: ControlData> {
|
||||||
parse_fn: Box<ParseFn<FD, C::ReturnType>>,
|
pub(crate) parse_fn: Box<dyn ParseFn<FD, C>>,
|
||||||
validation_fn: Box<ValidationFn<FD>>,
|
pub(crate) validation_fn: Box<dyn ValidationFn<FD>>,
|
||||||
style_attributes: Vec<FS::StylingAttributes>,
|
pub(crate) style_attributes: Vec<FS::StylingAttributes>,
|
||||||
data: C,
|
pub(crate) data: C,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<FD: FormData, FS: FormStyle, C: ControlData> ControlBuilder<FD, FS, C> {
|
impl<FD: FormData, FS: FormStyle, C: ControlData> ControlBuilder<FD, FS, C> {
|
||||||
@ -83,15 +86,25 @@ impl<FD: FormData, FS: FormStyle, C: ControlData> ControlBuilder<FD, FS, C> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build(self) -> Control<FD, FS, C> {
|
pub(crate) fn build(
|
||||||
self.into()
|
self,
|
||||||
|
) -> (
|
||||||
|
ControlRenderData<FS, C>,
|
||||||
|
impl ParseFn<FD, C>,
|
||||||
|
impl ValidationFn<FD>,
|
||||||
|
) {
|
||||||
|
(
|
||||||
|
ControlRenderData {
|
||||||
|
data: Box::new(self.data),
|
||||||
|
style: self.style_attributes,
|
||||||
|
},
|
||||||
|
self.parse_fn,
|
||||||
|
self.validation_fn,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_fn(
|
pub fn parse_fn(mut self, parse_fn: impl ParseFn<FD, C>) -> Self {
|
||||||
mut self,
|
self.parse_fn = Box::new(parse_fn) as Box<dyn ParseFn<FD, C>>;
|
||||||
parse_fn: impl Fn(C::ReturnType, &mut FD) -> Result<(), String> + 'static,
|
|
||||||
) -> Self {
|
|
||||||
self.parse_fn = Box::new(parse_fn) as Box<ParseFn<FD, C::ReturnType>>;
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,7 +112,7 @@ impl<FD: FormData, FS: FormStyle, C: ControlData> ControlBuilder<FD, FS, C> {
|
|||||||
mut self,
|
mut self,
|
||||||
validation_fn: impl Fn(&FD) -> Result<(), String> + 'static,
|
validation_fn: impl Fn(&FD) -> Result<(), String> + 'static,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.validation_fn = Box::new(validation_fn) as Box<ValidationFn<FD>>;
|
self.validation_fn = Box::new(validation_fn) as Box<dyn ValidationFn<FD>>;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,15 +121,3 @@ impl<FD: FormData, FS: FormStyle, C: ControlData> ControlBuilder<FD, FS, C> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
impl<FD: FormData, FS: FormStyle, C: ControlData> Into<Control<FD, FS, C>>
|
|
||||||
for ControlBuilder<FD, FS, C>
|
|
||||||
{
|
|
||||||
fn into(self) -> Control<FD, FS, C> {
|
|
||||||
Control {
|
|
||||||
data: Box::new(self.data),
|
|
||||||
style: self.style_attributes,
|
|
||||||
parse_fn: self.parse_fn,
|
|
||||||
validation: self.validation_fn,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use leptos::{Signal, View};
|
use leptos::{Signal, View};
|
||||||
|
|
||||||
use super::{Control, ControlBuilder, ControlData};
|
use super::{ControlBuilder, ControlData, ControlRenderData};
|
||||||
use crate::{
|
use crate::{
|
||||||
form::{FormBuilder, FormData},
|
form::{FormBuilder, FormData},
|
||||||
styles::FormStyle,
|
styles::FormStyle,
|
||||||
@ -15,18 +15,19 @@ pub struct SelectData {
|
|||||||
impl ControlData for SelectData {
|
impl ControlData for SelectData {
|
||||||
type ReturnType = String;
|
type ReturnType = String;
|
||||||
|
|
||||||
fn build_control<FD: FormData, FS: FormStyle>(
|
fn build_control<FS: FormStyle>(
|
||||||
fs: &FS,
|
fs: &FS,
|
||||||
control: Control<FD, FS, Self>,
|
control: ControlRenderData<FS, Self>,
|
||||||
|
validation_state: Signal<Result<(), String>>,
|
||||||
) -> (View, Signal<Self::ReturnType>) {
|
) -> (View, Signal<Self::ReturnType>) {
|
||||||
fs.select(control)
|
fs.select(control, validation_state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
|
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
|
||||||
pub fn select(
|
pub fn select(
|
||||||
self,
|
self,
|
||||||
builder: impl Fn(ControlBuilder<FD, FS, SelectData>) -> Control<FD, FS, SelectData>,
|
builder: impl Fn(ControlBuilder<FD, FS, SelectData>) -> ControlBuilder<FD, FS, SelectData>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.new_control(builder)
|
self.new_control(builder)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use leptos::View;
|
use leptos::View;
|
||||||
|
|
||||||
use super::{VanityControl, VanityControlBuilder, VanityControlData};
|
use super::{ControlRenderData, VanityControlBuilder, VanityControlData};
|
||||||
use crate::{
|
use crate::{
|
||||||
form::{FormBuilder, FormData},
|
form::{FormBuilder, FormData},
|
||||||
styles::FormStyle,
|
styles::FormStyle,
|
||||||
@ -12,10 +12,7 @@ pub struct SubmitData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl VanityControlData for SubmitData {
|
impl VanityControlData for SubmitData {
|
||||||
fn build_control<FD: FormData, FS: FormStyle>(
|
fn build_control<FS: FormStyle>(fs: &FS, control: ControlRenderData<FS, Self>) -> View {
|
||||||
fs: &FS,
|
|
||||||
control: VanityControl<FS, Self>,
|
|
||||||
) -> View {
|
|
||||||
fs.submit(control)
|
fs.submit(control)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -23,7 +20,7 @@ impl VanityControlData for SubmitData {
|
|||||||
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
|
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
|
||||||
pub fn submit(
|
pub fn submit(
|
||||||
self,
|
self,
|
||||||
builder: impl Fn(VanityControlBuilder<FS, SubmitData>) -> VanityControl<FS, SubmitData>,
|
builder: impl Fn(VanityControlBuilder<FS, SubmitData>) -> VanityControlBuilder<FS, SubmitData>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.new_vanity(builder)
|
self.new_vanity(builder)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
use super::{Control, ControlBuilder, ControlData};
|
use super::{ControlBuilder, ControlData, ControlRenderData};
|
||||||
use crate::{
|
use crate::{
|
||||||
form::{FormBuilder, FormData},
|
form::{FormBuilder, FormData},
|
||||||
styles::FormStyle,
|
styles::FormStyle,
|
||||||
@ -14,18 +14,19 @@ pub struct TextAreaData {
|
|||||||
impl ControlData for TextAreaData {
|
impl ControlData for TextAreaData {
|
||||||
type ReturnType = String;
|
type ReturnType = String;
|
||||||
|
|
||||||
fn build_control<FD: FormData, FS: FormStyle>(
|
fn build_control<FS: FormStyle>(
|
||||||
fs: &FS,
|
fs: &FS,
|
||||||
control: Control<FD, FS, Self>,
|
control: ControlRenderData<FS, Self>,
|
||||||
|
validation_state: Signal<Result<(), String>>,
|
||||||
) -> (View, Signal<Self::ReturnType>) {
|
) -> (View, Signal<Self::ReturnType>) {
|
||||||
fs.text_area(control)
|
fs.text_area(control, validation_state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
|
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
|
||||||
pub fn text_area(
|
pub fn text_area(
|
||||||
self,
|
self,
|
||||||
builder: impl Fn(ControlBuilder<FD, FS, TextAreaData>) -> Control<FD, FS, TextAreaData>,
|
builder: impl Fn(ControlBuilder<FD, FS, TextAreaData>) -> ControlBuilder<FD, FS, TextAreaData>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.new_control(builder)
|
self.new_control(builder)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use leptos::{Signal, View};
|
use leptos::{Signal, View};
|
||||||
|
|
||||||
use super::{Control, ControlBuilder, ControlData};
|
use super::{ControlBuilder, ControlData, ControlRenderData};
|
||||||
use crate::{
|
use crate::{
|
||||||
form::{FormBuilder, FormData},
|
form::{FormBuilder, FormData},
|
||||||
styles::FormStyle,
|
styles::FormStyle,
|
||||||
@ -30,18 +30,19 @@ impl Default for TextInputData {
|
|||||||
impl ControlData for TextInputData {
|
impl ControlData for TextInputData {
|
||||||
type ReturnType = String;
|
type ReturnType = String;
|
||||||
|
|
||||||
fn build_control<FD: FormData, FS: FormStyle>(
|
fn build_control<FS: FormStyle>(
|
||||||
fs: &FS,
|
fs: &FS,
|
||||||
control: Control<FD, FS, Self>,
|
control: ControlRenderData<FS, Self>,
|
||||||
|
validation_state: Signal<Result<(), String>>,
|
||||||
) -> (View, Signal<Self::ReturnType>) {
|
) -> (View, Signal<Self::ReturnType>) {
|
||||||
fs.text_input(control)
|
fs.text_input(control, validation_state)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
|
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
|
||||||
pub fn text_input(
|
pub fn text_input(
|
||||||
self,
|
self,
|
||||||
builder: impl Fn(ControlBuilder<FD, FS, TextInputData>) -> Control<FD, FS, TextInputData>,
|
builder: impl Fn(ControlBuilder<FD, FS, TextInputData>) -> ControlBuilder<FD, FS, TextInputData>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.new_control(builder)
|
self.new_control(builder)
|
||||||
}
|
}
|
||||||
|
|||||||
224
src/form.rs
224
src/form.rs
@ -1,7 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
controls::{
|
controls::{
|
||||||
Control, ControlBuilder, ControlData, ValidationFn, VanityControl, VanityControlBuilder,
|
ControlBuilder, ControlData, ValidationFn, VanityControlBuilder, VanityControlData,
|
||||||
VanityControlData,
|
|
||||||
},
|
},
|
||||||
styles::FormStyle,
|
styles::FormStyle,
|
||||||
};
|
};
|
||||||
@ -10,31 +9,135 @@ use leptos::{
|
|||||||
WriteSignal,
|
WriteSignal,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Form {}
|
pub struct Validator<FD: FormData> {
|
||||||
|
validations: Vec<Box<dyn ValidationFn<FD>>>,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct FormBuilder<FD: FormData, FS: FormStyle> {
|
impl<FD: FormData> Validator<FD> {
|
||||||
|
pub fn validate(&self, form_data: &FD) -> Result<(), String> {
|
||||||
|
for v in self.validations.iter() {
|
||||||
|
(*v)(form_data)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A constructed form object.
|
||||||
|
///
|
||||||
|
/// With this, you can render the form, get the form data, or get
|
||||||
|
/// a validator for the data.
|
||||||
|
pub struct Form<FD: FormData> {
|
||||||
|
pub fd: ReadSignal<FD>,
|
||||||
|
validations: Vec<Box<dyn ValidationFn<FD>>>,
|
||||||
|
view: View,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<FD: FormData> Form<FD> {
|
||||||
|
pub fn validator(self) -> Validator<FD> {
|
||||||
|
Validator {
|
||||||
|
validations: self.validations,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate(&self, form_data: &FD) -> Result<(), String> {
|
||||||
|
for v in self.validations.iter() {
|
||||||
|
(*v)(form_data)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn view(&self) -> View {
|
||||||
|
self.view.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_parts(self) -> (ReadSignal<FD>, Validator<FD>, View) {
|
||||||
|
(
|
||||||
|
self.fd,
|
||||||
|
Validator {
|
||||||
|
validations: self.validations,
|
||||||
|
},
|
||||||
|
self.view,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<FD: FormData> IntoView for Form<FD> {
|
||||||
|
fn into_view(self) -> View {
|
||||||
|
self.view
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A version of the [`FormBuilder`] that contains all the data
|
||||||
|
/// needed for full building of a [`Form`].
|
||||||
|
struct FullFormBuilder<FD: FormData, FS: FormStyle> {
|
||||||
fd_get: ReadSignal<FD>,
|
fd_get: ReadSignal<FD>,
|
||||||
fd_set: WriteSignal<FD>,
|
fd_set: WriteSignal<FD>,
|
||||||
fs: FS,
|
fs: FS,
|
||||||
validations: Vec<Box<ValidationFn<FD>>>,
|
validations: Vec<Box<dyn ValidationFn<FD>>>,
|
||||||
views: Vec<View>,
|
views: Vec<View>,
|
||||||
}
|
}
|
||||||
|
/// The internal type for building forms
|
||||||
|
///
|
||||||
|
/// This allows us to build either the full form
|
||||||
|
/// with views, validation and data. Or we can just
|
||||||
|
/// build the validation functions.
|
||||||
|
///
|
||||||
|
/// This is useful in the context of a server that
|
||||||
|
/// cannot or should not render the form. You can
|
||||||
|
/// still get all the validation functions from the
|
||||||
|
/// form data.
|
||||||
|
enum FormBuilderInner<FD: FormData, FS: FormStyle> {
|
||||||
|
/// For building the form with views
|
||||||
|
FullBuilder(FullFormBuilder<FD, FS>),
|
||||||
|
/// For building only the validations for the form
|
||||||
|
ValidationBuilder {
|
||||||
|
validations: Vec<Box<dyn ValidationFn<FD>>>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FormBuilder<FD: FormData, FS: FormStyle> {
|
||||||
|
inner: FormBuilderInner<FD, FS>,
|
||||||
|
}
|
||||||
|
|
||||||
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
|
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
|
||||||
pub fn new(form_style: FS) -> FormBuilder<FD, FS> {
|
// TODO: remove the Default trait bound and bind it to this function only
|
||||||
let (fs_get, fs_set) = create_signal(FD::default());
|
fn new_full_builder(form_style: FS) -> FormBuilder<FD, FS> {
|
||||||
|
let (fd_get, fd_set) = create_signal(FD::default());
|
||||||
FormBuilder {
|
FormBuilder {
|
||||||
|
inner: FormBuilderInner::FullBuilder(FullFormBuilder {
|
||||||
fd_get,
|
fd_get,
|
||||||
fd_set,
|
fd_set,
|
||||||
fs: form_style,
|
fs: form_style,
|
||||||
validations: Vec::new(),
|
validations: Vec::new(),
|
||||||
views: Vec::new(),
|
views: Vec::new(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_full_builder_with(starting_data: FD, form_style: FS) -> FormBuilder<FD, FS> {
|
||||||
|
let (fd_get, fd_set) = create_signal(starting_data);
|
||||||
|
FormBuilder {
|
||||||
|
inner: FormBuilderInner::FullBuilder(FullFormBuilder {
|
||||||
|
fd_get,
|
||||||
|
fd_set,
|
||||||
|
fs: form_style,
|
||||||
|
validations: Vec::new(),
|
||||||
|
views: Vec::new(),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_validation_builder() -> FormBuilder<FD, FS> {
|
||||||
|
FormBuilder {
|
||||||
|
inner: FormBuilderInner::ValidationBuilder {
|
||||||
|
validations: Vec::new(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn new_vanity<C: VanityControlData + Default>(
|
pub(crate) fn new_vanity<C: VanityControlData + Default>(
|
||||||
mut self,
|
mut self,
|
||||||
builder: impl Fn(VanityControlBuilder<FS, C>) -> VanityControl<FS, C>,
|
builder: impl Fn(VanityControlBuilder<FS, C>) -> VanityControlBuilder<FS, C>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let vanity_builder = VanityControlBuilder::new(C::default());
|
let vanity_builder = VanityControlBuilder::new(C::default());
|
||||||
let control = builder(vanity_builder);
|
let control = builder(vanity_builder);
|
||||||
@ -44,7 +147,7 @@ impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
|
|||||||
|
|
||||||
pub(crate) fn new_control<C: ControlData + Default>(
|
pub(crate) fn new_control<C: ControlData + Default>(
|
||||||
mut self,
|
mut self,
|
||||||
builder: impl Fn(ControlBuilder<FD, FS, C>) -> Control<FD, FS, C>,
|
builder: impl Fn(ControlBuilder<FD, FS, C>) -> ControlBuilder<FD, FS, C>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let control_builder = ControlBuilder::new(C::default());
|
let control_builder = ControlBuilder::new(C::default());
|
||||||
let control = builder(control_builder);
|
let control = builder(control_builder);
|
||||||
@ -52,43 +155,102 @@ impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_vanity<C: VanityControlData>(&mut self, vanity_control: VanityControl<FS, C>) {
|
fn add_vanity<C: VanityControlData>(&mut self, vanity_control: VanityControlBuilder<FS, C>) {
|
||||||
let view = VanityControlData::build_control::<FD, FS>(&self.fs, vanity_control);
|
let full_builder = match &mut self.inner {
|
||||||
self.views.push(view);
|
FormBuilderInner::ValidationBuilder { validations: _ } => return,
|
||||||
|
FormBuilderInner::FullBuilder(full_builder) => full_builder,
|
||||||
|
};
|
||||||
|
let render_data = vanity_control.build();
|
||||||
|
let view = VanityControlData::build_control(&full_builder.fs, render_data);
|
||||||
|
full_builder.views.push(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add_control<C: ControlData>(&mut self, control: Control<FD, FS, C>) {
|
fn add_control<C: ControlData>(&mut self, control: ControlBuilder<FD, FS, C>) {
|
||||||
|
let full_builder = match &mut self.inner {
|
||||||
|
FormBuilderInner::ValidationBuilder { validations } => {
|
||||||
|
validations.push(control.validation_fn);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
FormBuilderInner::FullBuilder(full_builder) => full_builder,
|
||||||
|
};
|
||||||
|
let (render_data, parse_fn, validation_fn) = control.build();
|
||||||
let (validation_signal, validation_signal_set) = create_signal(Ok(()));
|
let (validation_signal, validation_signal_set) = create_signal(Ok(()));
|
||||||
let (view, control_value) = ControlData::build_control(&self.fs, control);
|
let (view, control_value) =
|
||||||
|
ControlData::build_control(&full_builder.fs, render_data, validation_signal.into());
|
||||||
// TODO: add a signal that triggers on submit to refresh the validation on_submit
|
// TODO: add a signal that triggers on submit to refresh the validation on_submit
|
||||||
// TODO: we might want a way to see if this is the first time this ran, which would
|
let fd_setter = full_builder.fd_set;
|
||||||
// prevent the form's validation to pop up before the user typed anything in
|
|
||||||
// TODO: add validation here that run on the input changing, and writes
|
|
||||||
// it to the fd signals
|
|
||||||
create_effect(move |last_value| {
|
create_effect(move |last_value| {
|
||||||
let control_value = control_value.get();
|
let control_value = control_value.get();
|
||||||
let mut validation_result;
|
let mut validation_result = Ok(());
|
||||||
self.fd_set.update(|v| {
|
fd_setter.update(|v| {
|
||||||
validation_result =
|
validation_result = (parse_fn)(control_value, v).and_then(|_| (validation_fn)(v));
|
||||||
(control.parse_fn)(control_value, v).and_then(|_| (*control.validation)(v));
|
|
||||||
});
|
});
|
||||||
// TODO: or this happened on a submit
|
// TODO: or this happened on a submit
|
||||||
if Some(validation_result) != last_value {
|
if last_value.is_some_and(|last_value| last_value != validation_result) {
|
||||||
validation_signal_set.set(validation_result);
|
validation_signal_set.set(validation_result.clone());
|
||||||
}
|
}
|
||||||
validation_result
|
validation_result
|
||||||
});
|
});
|
||||||
self.views.push(view);
|
full_builder.views.push(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: this should return a Form object
|
fn build(self) -> Form<FD> {
|
||||||
// The Form should have `form_view()`, and `validate(&FD)` functions.
|
match self.inner {
|
||||||
pub fn build(self) -> View {
|
FormBuilderInner::FullBuilder(full_builder) => Form {
|
||||||
self.views.into_view()
|
fd: full_builder.fd_get,
|
||||||
|
validations: full_builder.validations,
|
||||||
|
view: full_builder.views.into_view(),
|
||||||
|
},
|
||||||
|
FormBuilderInner::ValidationBuilder { validations } => Form {
|
||||||
|
fd: create_signal(FD::default()).0,
|
||||||
|
validations,
|
||||||
|
view: ().into_view(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn validator(self) -> Validator<FD> {
|
||||||
|
match self.inner {
|
||||||
|
FormBuilderInner::FullBuilder(full_builder) => Validator {
|
||||||
|
validations: full_builder.validations,
|
||||||
|
},
|
||||||
|
FormBuilderInner::ValidationBuilder { validations } => Validator { validations },
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A trait allowing a form to be built around its containing data.
|
||||||
|
///
|
||||||
|
/// This trait defines a function that can be used to build all the data needed
|
||||||
|
/// to physically lay out a form, and how that data should be parsed and validated.
|
||||||
pub trait FormData: Default + Clone + 'static {
|
pub trait FormData: Default + Clone + 'static {
|
||||||
// TODO: this should return a Form Object
|
type Style: FormStyle;
|
||||||
fn create_form() -> View;
|
|
||||||
|
/// Defines how the form should be layed out and how the data should be parsed and validated.
|
||||||
|
///
|
||||||
|
/// Uses the given form builder to specify what fields should be present
|
||||||
|
/// in the form, what properties those fields should have, and how that
|
||||||
|
/// data should be parsed and checked.
|
||||||
|
fn build_form(fb: FormBuilder<Self, Self::Style>) -> FormBuilder<Self, Self::Style>;
|
||||||
|
|
||||||
|
/// Gets the [`Form`] for this FormData type.
|
||||||
|
///
|
||||||
|
/// The [`Form`] provides the way to render the form
|
||||||
|
fn get_form(style: Self::Style) -> Form<Self> {
|
||||||
|
let builder = FormBuilder::new_full_builder(style);
|
||||||
|
let builder = Self::build_form(builder);
|
||||||
|
builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_form_with_starting_data(self, style: Self::Style) -> Form<Self> {
|
||||||
|
let builder = FormBuilder::new_full_builder_with(self, style);
|
||||||
|
let builder = Self::build_form(builder);
|
||||||
|
builder.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_validator() -> Validator<Self> {
|
||||||
|
let builder = FormBuilder::new_validation_builder();
|
||||||
|
let builder = Self::build_form(builder);
|
||||||
|
builder.validator()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,12 +2,9 @@ mod tw_grid;
|
|||||||
|
|
||||||
pub use tw_grid::{TailwindGridFormStyle, TailwindGridStylingAttributes};
|
pub use tw_grid::{TailwindGridFormStyle, TailwindGridStylingAttributes};
|
||||||
|
|
||||||
use crate::{
|
use crate::controls::{
|
||||||
controls::{
|
|
||||||
heading::HeadingData, select::SelectData, submit::SubmitData, text_area::TextAreaData,
|
heading::HeadingData, select::SelectData, submit::SubmitData, text_area::TextAreaData,
|
||||||
text_input::TextInputData, Control, ControlData, VanityControl,
|
text_input::TextInputData, ControlData, ControlRenderData,
|
||||||
},
|
|
||||||
form::FormData,
|
|
||||||
};
|
};
|
||||||
use leptos::{Signal, View};
|
use leptos::{Signal, View};
|
||||||
|
|
||||||
@ -17,20 +14,24 @@ pub trait FormStyle: 'static {
|
|||||||
// TODO: add form frame
|
// TODO: add form frame
|
||||||
// TODO: perhaps we don't want to send the full control type anymore.
|
// TODO: perhaps we don't want to send the full control type anymore.
|
||||||
// as the rendering shouldn't depend on parse or validate anymore.
|
// as the rendering shouldn't depend on parse or validate anymore.
|
||||||
fn heading(&self, control: VanityControl<Self, HeadingData>) -> View;
|
fn heading(&self, control: ControlRenderData<Self, HeadingData>) -> View;
|
||||||
fn text_input<FD: FormData>(
|
fn text_input(
|
||||||
&self,
|
&self,
|
||||||
control: Control<FD, Self, TextInputData>,
|
control: ControlRenderData<Self, TextInputData>,
|
||||||
|
validation_state: Signal<Result<(), String>>,
|
||||||
) -> (View, Signal<<TextInputData as ControlData>::ReturnType>);
|
) -> (View, Signal<<TextInputData as ControlData>::ReturnType>);
|
||||||
fn select<FD: FormData>(
|
fn select(
|
||||||
&self,
|
&self,
|
||||||
control: Control<FD, Self, SelectData>,
|
control: ControlRenderData<Self, SelectData>,
|
||||||
|
validation_state: Signal<Result<(), String>>,
|
||||||
) -> (View, Signal<<SelectData as ControlData>::ReturnType>);
|
) -> (View, Signal<<SelectData as ControlData>::ReturnType>);
|
||||||
fn submit(&self, control: VanityControl<Self, SubmitData>) -> View;
|
fn submit(&self, control: ControlRenderData<Self, SubmitData>) -> View;
|
||||||
fn text_area<FD: FormData>(
|
fn text_area(
|
||||||
&self,
|
&self,
|
||||||
control: Control<FD, Self, TextAreaData>,
|
control: ControlRenderData<Self, TextAreaData>,
|
||||||
|
validation_state: Signal<Result<(), String>>,
|
||||||
) -> (View, Signal<<TextAreaData as ControlData>::ReturnType>);
|
) -> (View, Signal<<TextAreaData as ControlData>::ReturnType>);
|
||||||
|
// TODO: test custom component
|
||||||
fn custom_component(&self, view: View) -> View;
|
fn custom_component(&self, view: View) -> View;
|
||||||
// TODO: add group
|
// TODO: add group
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,7 @@
|
|||||||
use super::FormStyle;
|
use super::FormStyle;
|
||||||
use crate::{
|
use crate::controls::{
|
||||||
controls::{
|
|
||||||
heading::HeadingData, select::SelectData, submit::SubmitData, text_area::TextAreaData,
|
heading::HeadingData, select::SelectData, submit::SubmitData, text_area::TextAreaData,
|
||||||
text_input::TextInputData, Control, VanityControl,
|
text_input::TextInputData, ControlRenderData,
|
||||||
},
|
|
||||||
form::FormData,
|
|
||||||
};
|
};
|
||||||
use leptos::*;
|
use leptos::*;
|
||||||
|
|
||||||
@ -17,7 +14,7 @@ pub struct TailwindGridFormStyle;
|
|||||||
impl FormStyle for TailwindGridFormStyle {
|
impl FormStyle for TailwindGridFormStyle {
|
||||||
type StylingAttributes = TailwindGridStylingAttributes;
|
type StylingAttributes = TailwindGridStylingAttributes;
|
||||||
|
|
||||||
fn heading(&self, control: VanityControl<Self, HeadingData>) -> View {
|
fn heading(&self, control: ControlRenderData<Self, HeadingData>) -> View {
|
||||||
view! {
|
view! {
|
||||||
<h2 class="text-xl py-2 text-center font-bold text-gray-700 border-b-2 border-gray-800/60 mb-8">
|
<h2 class="text-xl py-2 text-center font-bold text-gray-700 border-b-2 border-gray-800/60 mb-8">
|
||||||
{&control.data.title}
|
{&control.data.title}
|
||||||
@ -26,14 +23,17 @@ impl FormStyle for TailwindGridFormStyle {
|
|||||||
.into_view()
|
.into_view()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn text_input<FD: FormData>(
|
fn text_input(
|
||||||
&self,
|
&self,
|
||||||
control: Control<FD, Self, TextInputData>,
|
control: ControlRenderData<Self, TextInputData>,
|
||||||
) -> (View, ReadSignal<String>) {
|
validation_state: Signal<Result<(), String>>,
|
||||||
|
) -> (View, Signal<String>) {
|
||||||
let (read, write) = create_signal(String::new());
|
let (read, write) = create_signal(String::new());
|
||||||
|
|
||||||
|
leptos::logging::log!("Rendering text input");
|
||||||
let view = view! {
|
let view = view! {
|
||||||
<div>
|
<div>
|
||||||
|
{move || format!("{:?}", validation_state.get())}
|
||||||
<label
|
<label
|
||||||
for={&control.data.name}
|
for={&control.data.name}
|
||||||
class="block uppercase tracking-wide text-left text-gray-700 text-md font-bold ml-2 mb-1"
|
class="block uppercase tracking-wide text-left text-gray-700 text-md font-bold ml-2 mb-1"
|
||||||
@ -54,22 +54,54 @@ impl FormStyle for TailwindGridFormStyle {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
}.into_view();
|
}.into_view();
|
||||||
(view, read)
|
(view, read.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select<FD: FormData>(&self, control: Control<FD, Self, SelectData>) -> View {
|
fn select(
|
||||||
|
&self,
|
||||||
|
control: ControlRenderData<Self, SelectData>,
|
||||||
|
validation_state: Signal<Result<(), String>>,
|
||||||
|
) -> (View, Signal<String>) {
|
||||||
|
let (read, write) = create_signal(String::new());
|
||||||
|
|
||||||
|
let options_view = control
|
||||||
|
.data
|
||||||
|
.options
|
||||||
|
.into_iter()
|
||||||
|
.map(|value| {
|
||||||
|
// let value = value;
|
||||||
|
let cloned_value = value.clone();
|
||||||
view! {
|
view! {
|
||||||
|
<option
|
||||||
|
value={value}
|
||||||
|
selected=move || read.get() == *cloned_value
|
||||||
|
>
|
||||||
|
*value
|
||||||
|
</option>
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect_view();
|
||||||
|
|
||||||
|
let view = view! {
|
||||||
|
<div>
|
||||||
|
{move || format!("{:?}", validation_state.get())}
|
||||||
<select
|
<select
|
||||||
id=&control.data.name
|
id=&control.data.name
|
||||||
name=control.data.name
|
name=control.data.name
|
||||||
class="block w-full bg-gray-100 border-2 border-gray-300 text-gray-700 py-2 px-4 rounded-lg leading-tight focus:outline-none focus:bg-white focus:border-sky-400"
|
class="block w-full bg-gray-100 border-2 border-gray-300 text-gray-700 py-2 px-4 rounded-lg leading-tight focus:outline-none focus:bg-white focus:border-sky-400"
|
||||||
|
on:change=move |ev| {
|
||||||
|
write.set(event_target_value(&ev));
|
||||||
|
}
|
||||||
>
|
>
|
||||||
{control.data.options.iter().map(|option| view!{<option>{option}</option>}).collect_view()}
|
{options_view}
|
||||||
</select>
|
</select>
|
||||||
}.into_view()
|
</div>
|
||||||
|
}.into_view();
|
||||||
|
|
||||||
|
(view, read.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn submit(&self, control: VanityControl<Self, SubmitData>) -> View {
|
fn submit(&self, control: ControlRenderData<Self, SubmitData>) -> View {
|
||||||
view! {
|
view! {
|
||||||
<input
|
<input
|
||||||
type="submit"
|
type="submit"
|
||||||
@ -80,15 +112,29 @@ impl FormStyle for TailwindGridFormStyle {
|
|||||||
.into_view()
|
.into_view()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn text_area<FD: FormData>(&self, control: Control<FD, Self, TextAreaData>) -> View {
|
fn text_area(
|
||||||
view! {
|
&self,
|
||||||
|
control: ControlRenderData<Self, TextAreaData>,
|
||||||
|
validation_state: Signal<Result<(), String>>,
|
||||||
|
) -> (View, Signal<String>) {
|
||||||
|
let (read, write) = create_signal(String::new());
|
||||||
|
|
||||||
|
let view = view! {
|
||||||
|
<div>
|
||||||
|
{move || format!("{:?}", validation_state.get())}
|
||||||
<textarea
|
<textarea
|
||||||
id=&control.data.name
|
id=&control.data.name
|
||||||
name=control.data.name
|
name=control.data.name
|
||||||
placeholder=control.data.placeholder
|
placeholder=control.data.placeholder
|
||||||
class="block w-full bg-gray-100 border-2 border-gray-300 text-gray-700 py-2 px-4 rounded-lg leading-tight focus:outline-none focus:bg-white focus:border-sky-400"
|
class="block w-full bg-gray-100 border-2 border-gray-300 text-gray-700 py-2 px-4 rounded-lg leading-tight focus:outline-none focus:bg-white focus:border-sky-400"
|
||||||
|
on:change=move |ev| {
|
||||||
|
write.set(event_target_value(&ev));
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
}.into_view()
|
</div>
|
||||||
|
}.into_view();
|
||||||
|
|
||||||
|
(view, read.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn custom_component(&self, view: View) -> View {
|
fn custom_component(&self, view: View) -> View {
|
||||||
|
|||||||
Reference in New Issue
Block a user