second round of impl

This commit is contained in:
Mitchell Marino 2024-03-16 15:48:32 -05:00
parent bf6529cc1d
commit fbe746702a
11 changed files with 408 additions and 238 deletions

View File

@ -1,30 +1,37 @@
use leptos::View;
use crate::{ControlBuilder, ControlData, FormBuilder, FormData, FormStyleProvider};
use super::{VanityControl, VanityControlBuilder, VanityControlData};
use crate::{
form::{FormBuilder, FormData},
styles::FormStyle,
};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct HeadingData {
pub(crate) title: String,
}
impl HeadingData {
fn new(title: String) -> Self {
HeadingData { title }
impl VanityControlData for HeadingData {
fn build_control<FD: FormData, FS: FormStyle>(
fs: &FS,
control: VanityControl<FS, Self>,
) -> View {
fs.heading(control)
}
}
impl<FS: FormStyleProvider> ControlData<FS> for HeadingData {
fn build_control(self, fs: &FS, attributes: Vec<FS::StylingAttributes>) -> View {
fs.heading(self, attributes)
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
pub fn heading(
self,
builder: impl Fn(VanityControlBuilder<FS, HeadingData>) -> VanityControl<FS, HeadingData>,
) -> Self {
self.new_vanity(builder)
}
}
impl<FD: FormData, FS: FormStyleProvider> FormBuilder<FD, FS> {
pub fn heading(self, title: impl ToString) -> ControlBuilder<FD, FS, HeadingData> {
ControlBuilder {
fb: self,
parse_fn: None,
style_attributes: Vec::new(),
data: HeadingData::new(title.to_string()),
}
impl<FS: FormStyle> VanityControlBuilder<FS, HeadingData> {
pub fn title(mut self, title: impl ToString) -> Self {
self.data.title = title.to_string();
self
}
}

View File

@ -1,5 +1,119 @@
use crate::{form::FormData, styles::FormStyle};
use leptos::View;
pub mod heading;
pub mod select;
pub mod submit;
pub mod text_area;
pub mod text_input;
pub type ValidationFn<FD> = dyn Fn(&FD) -> Result<(), String> + 'static;
pub type ParseFn<FD, CReturnType> = dyn Fn(CReturnType, &mut FD) -> Result<(), String> + 'static;
pub trait VanityControlData: 'static {
fn build_control<FD: FormData, FS: FormStyle>(
fs: &FS,
control: VanityControl<FS, Self>,
) -> View;
}
pub trait ControlData: 'static {
type ReturnType;
// TODO: this should also return a getter for the data
fn build_control<FD: FormData, FS: FormStyle>(fs: &FS, control: Control<FD, FS, Self>) -> View;
}
pub struct VanityControl<FS: FormStyle + ?Sized, C: VanityControlData + ?Sized> {
pub data: Box<C>,
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> {
style_attributes: Vec<FS::StylingAttributes>,
data: C,
}
impl<FS: FormStyle, C: VanityControlData> VanityControlBuilder<FS, C> {
pub(crate) fn new(data: C) -> Self {
VanityControlBuilder {
data,
style_attributes: Vec::new(),
}
}
pub fn build(self) -> VanityControl<FS, C> {
self.into()
}
}
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),
style: self.style_attributes,
}
}
}
pub struct ControlBuilder<FD: FormData, FS: FormStyle, C: ControlData> {
parse_fn: Box<ParseFn<FD, C::ReturnType>>,
validation_fn: Box<ValidationFn<FD>>,
style_attributes: Vec<FS::StylingAttributes>,
data: C,
}
impl<FD: FormData, FS: FormStyle, C: ControlData> ControlBuilder<FD, FS, C> {
pub(crate) fn new(data: C) -> Self {
ControlBuilder {
data,
parse_fn: Box::new(|_, _| Ok(())),
validation_fn: Box::new(|_| Ok(())),
style_attributes: Vec::new(),
}
}
pub fn build(self) -> Control<FD, FS, C> {
self.into()
}
pub fn parse_fn(
mut self,
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
}
pub fn validation_fn(
mut self,
validation_fn: impl Fn(&FD) -> Result<(), String> + 'static,
) -> Self {
self.validation_fn = Box::new(validation_fn) as Box<ValidationFn<FD>>;
self
}
pub fn style(mut self, attribute: FS::StylingAttributes) -> Self {
self.style_attributes.push(attribute);
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,
}
}
}

View File

@ -1,41 +1,42 @@
use leptos::View;
use crate::{ControlBuilder, ControlData, FormBuilder, FormData, FormStyleProvider};
use super::{Control, ControlBuilder, ControlData};
use crate::{
form::{FormBuilder, FormData},
styles::FormStyle,
};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct SelectData {
pub(crate) name: String,
pub(crate) options: Vec<String>,
}
impl SelectData {
fn new(name: String) -> Self {
SelectData {
name,
options: Vec::new(),
}
impl ControlData for SelectData {
type ReturnType = String;
fn build_control<FD: FormData, FS: FormStyle>(fs: &FS, control: Control<FD, FS, Self>) -> View {
fs.select(control)
}
}
impl<FS: FormStyleProvider> ControlData<FS> for SelectData {
fn build_control(self, fs: &FS, attributes: Vec<FS::StylingAttributes>) -> View {
fs.select(self, attributes)
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
pub fn select(
self,
builder: impl Fn(ControlBuilder<FD, FS, SelectData>) -> Control<FD, FS, SelectData>,
) -> Self {
self.new_control(builder)
}
}
impl<FD: FormData, FS: FormStyleProvider> FormBuilder<FD, FS> {
pub fn select(self, name: impl ToString) -> ControlBuilder<FD, FS, SelectData> {
ControlBuilder {
fb: self,
parse_fn: None,
style_attributes: Vec::new(),
data: SelectData::new(name.to_string()),
}
}
}
impl<FD: FormData, FS: FormStyleProvider> ControlBuilder<FD, FS, SelectData> {
impl<FD: FormData, FS: FormStyle> ControlBuilder<FD, FS, SelectData> {
pub fn options(mut self, options: Vec<String>) -> Self {
self.data.options = options;
self
}
pub fn and_option(mut self, option: impl ToString) -> Self {
self.data.options.push(option.to_string());
self
}
}

View File

@ -1,30 +1,37 @@
use leptos::View;
use crate::{ControlBuilder, ControlData, FormBuilder, FormData, FormStyleProvider};
use super::{VanityControl, VanityControlBuilder, VanityControlData};
use crate::{
form::{FormBuilder, FormData},
styles::FormStyle,
};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct SubmitData {
pub(crate) text: String,
}
impl SubmitData {
fn new(text: String) -> Self {
SubmitData { text }
impl VanityControlData for SubmitData {
fn build_control<FD: FormData, FS: FormStyle>(
fs: &FS,
control: VanityControl<FS, Self>,
) -> View {
fs.submit(control)
}
}
impl<FS: FormStyleProvider> ControlData<FS> for SubmitData {
fn build_control(self, fs: &FS, attributes: Vec<FS::StylingAttributes>) -> View {
fs.submit(self, attributes)
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
pub fn submit(
self,
builder: impl Fn(VanityControlBuilder<FS, SubmitData>) -> VanityControl<FS, SubmitData>,
) -> Self {
self.new_vanity(builder)
}
}
impl<FD: FormData, FS: FormStyleProvider> FormBuilder<FD, FS> {
pub fn submit(self, text: impl ToString) -> ControlBuilder<FD, FS, SubmitData> {
ControlBuilder {
fb: self,
parse_fn: None,
style_attributes: Vec::new(),
data: SubmitData::new(text.to_string()),
}
impl<FS: FormStyle> VanityControlBuilder<FS, SubmitData> {
pub fn text(mut self, text: impl ToString) -> Self {
self.data.text = text.to_string();
self
}
}

View File

@ -1,39 +1,35 @@
use leptos::View;
use crate::{ControlBuilder, ControlData, FormBuilder, FormData, FormStyleProvider};
use super::{Control, ControlBuilder, ControlData};
use crate::{
form::{FormBuilder, FormData},
styles::FormStyle,
};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct TextAreaData {
pub(crate) name: String,
pub(crate) placeholder: Option<String>,
}
impl TextAreaData {
fn new(name: String) -> Self {
TextAreaData {
name,
placeholder: None,
}
impl ControlData for TextAreaData {
type ReturnType = String;
fn build_control<FD: FormData, FS: FormStyle>(fs: &FS, control: Control<FD, FS, Self>) -> View {
fs.text_area(control)
}
}
impl<FS: FormStyleProvider> ControlData<FS> for TextAreaData {
fn build_control(self, fs: &FS, attributes: Vec<FS::StylingAttributes>) -> View {
fs.text_area(self, attributes)
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
pub fn text_area(
self,
builder: impl Fn(ControlBuilder<FD, FS, TextAreaData>) -> Control<FD, FS, TextAreaData>,
) -> Self {
self.new_control(builder)
}
}
impl<FD: FormData, FS: FormStyleProvider> FormBuilder<FD, FS> {
pub fn text_area(self, name: impl ToString) -> ControlBuilder<FD, FS, TextAreaData> {
ControlBuilder {
fb: self,
parse_fn: None,
style_attributes: Vec::new(),
data: TextAreaData::new(name.to_string()),
}
}
}
impl<FD: FormData, FS: FormStyleProvider> ControlBuilder<FD, FS, TextAreaData> {
impl<FD: FormData, FS: FormStyle> ControlBuilder<FD, FS, TextAreaData> {
pub fn placeholder(mut self, placeholder: impl ToString) -> Self {
self.data.placeholder = Some(placeholder.to_string());
self

View File

@ -1,45 +1,50 @@
use leptos::View;
use crate::{ControlBuilder, ControlData, FormBuilder, FormData, FormStyleProvider};
use super::{Control, ControlBuilder, ControlData};
use crate::{
form::{FormBuilder, FormData},
styles::FormStyle,
};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct TextInputData {
pub(crate) name: String,
pub(crate) placeholder: Option<String>,
pub(crate) label: Option<String>,
pub(crate) initital_text: String,
pub(crate) initial_text: String,
pub(crate) input_type: &'static str,
}
impl TextInputData {
fn new(name: String) -> Self {
impl Default for TextInputData {
fn default() -> Self {
TextInputData {
name,
name: String::new(),
placeholder: None,
label: None,
initital_text: String::new(),
initial_text: String::new(),
input_type: "input",
}
}
}
impl<FS: FormStyleProvider> ControlData<FS> for TextInputData {
fn build_control(self, fs: &FS, attributes: Vec<FS::StylingAttributes>) -> View {
fs.text_input(self, attributes)
impl ControlData for TextInputData {
type ReturnType = String;
fn build_control<FD: FormData, FS: FormStyle>(fs: &FS, control: Control<FD, FS, Self>) -> View {
fs.text_input(control)
}
}
impl<FD: FormData, FS: FormStyleProvider> FormBuilder<FD, FS> {
pub fn text_input(self, name: impl ToString) -> ControlBuilder<FD, FS, TextInputData> {
ControlBuilder {
fb: self,
parse_fn: None,
style_attributes: Vec::new(),
data: TextInputData::new(name.to_string()),
}
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
pub fn text_input(
self,
builder: impl Fn(ControlBuilder<FD, FS, TextInputData>) -> Control<FD, FS, TextInputData>,
) -> Self {
self.new_control(builder)
}
}
impl<FD: FormData, FS: FormStyleProvider> ControlBuilder<FD, FS, TextInputData> {
impl<FD: FormData, FS: FormStyle> ControlBuilder<FD, FS, TextInputData> {
pub fn placeholder(mut self, placeholder: impl ToString) -> Self {
self.data.placeholder = Some(placeholder.to_string());
self
@ -51,7 +56,7 @@ impl<FD: FormData, FS: FormStyleProvider> ControlBuilder<FD, FS, TextInputData>
}
pub fn initial_text(mut self, text: impl ToString) -> Self {
self.data.initital_text = text.to_string();
self.data.initial_text = text.to_string();
self
}

68
src/form.rs Normal file
View File

@ -0,0 +1,68 @@
use crate::{
controls::{
Control, ControlBuilder, ControlData, ValidationFn, VanityControl, VanityControlBuilder,
VanityControlData,
},
styles::FormStyle,
};
use leptos::{IntoView, View};
use std::marker::PhantomData;
pub struct FormBuilder<FD: FormData, FS: FormStyle> {
_fd: PhantomData<FD>,
fs: FS,
validations: Vec<Box<ValidationFn<FD>>>,
views: Vec<View>,
}
impl<FD: FormData, FS: FormStyle> FormBuilder<FD, FS> {
pub fn new(form_style: FS) -> FormBuilder<FD, FS> {
FormBuilder {
_fd: PhantomData::default(),
fs: form_style,
validations: Vec::new(),
views: Vec::new(),
}
}
pub(crate) fn new_vanity<C: VanityControlData + Default>(
mut self,
builder: impl Fn(VanityControlBuilder<FS, C>) -> VanityControl<FS, C>,
) -> Self {
let vanity_builder = VanityControlBuilder::new(C::default());
let control = builder(vanity_builder);
self.add_vanity(control);
self
}
pub(crate) fn new_control<C: ControlData + Default>(
mut self,
builder: impl Fn(ControlBuilder<FD, FS, C>) -> Control<FD, FS, C>,
) -> Self {
let control_builder = ControlBuilder::new(C::default());
let control = builder(control_builder);
self.add_control(control);
self
}
fn add_vanity<C: VanityControlData>(&mut self, vanity_control: VanityControl<FS, C>) {
let view = VanityControlData::build_control::<FD, FS>(&self.fs, vanity_control);
self.views.push(view);
}
fn add_control<C: ControlData>(&mut self, control: Control<FD, FS, C>) {
let view = ControlData::build_control(&self.fs, control);
self.views.push(view);
}
// TODO: this should return a Form object
// The Form should have `form_view()`, and `validate(&FD)` functions.
pub fn build(self) -> View {
self.views.into_view()
}
}
pub trait FormData: Default {
// TODO: this should return a Form Object
fn create_form() -> View;
}

View File

@ -1,59 +1,3 @@
use controls::{
heading::HeadingData, select::SelectData, submit::SubmitData, text_area::TextAreaData,
text_input::TextInputData,
};
use leptos::*;
use std::marker::PhantomData;
mod controls;
mod provider_impl;
pub trait FormStyleProvider {
type StylingAttributes;
fn heading(&self, data: HeadingData, attributes: Vec<Self::StylingAttributes>) -> View;
fn text_input(&self, data: TextInputData, attributes: Vec<Self::StylingAttributes>) -> View;
fn select(&self, data: SelectData, attributes: Vec<Self::StylingAttributes>) -> View;
fn submit(&self, data: SubmitData, attributes: Vec<Self::StylingAttributes>) -> View;
fn text_area(&self, data: TextAreaData, attributes: Vec<Self::StylingAttributes>) -> View;
fn custom_component(&self, component: View) -> View;
// TODO: add group
}
pub trait ControlData<FS: FormStyleProvider>: 'static {
fn build_control(self, fs: &FS, attributes: Vec<FS::StylingAttributes>) -> View;
}
pub struct FormBuilder<FD: FormData, FS: FormStyleProvider> {
_fd: PhantomData<FD>,
fs: FS,
controls: Vec<(Box<dyn ControlData<FS>>, Vec<FS::StylingAttributes>)>,
}
pub trait FormData {}
pub struct ControlBuilder<FD: FormData, FS: FormStyleProvider, C: ControlData<FS>> {
fb: FormBuilder<FD, FS>,
parse_fn: Option<Box<dyn Fn(&str, &mut FD)>>,
style_attributes: Vec<FS::StylingAttributes>,
data: C,
}
impl<FD: FormData, FS: FormStyleProvider, C: ControlData<FS>> ControlBuilder<FD, FS, C> {
pub fn parse_fn(mut self, parse_fn: Box<dyn Fn(&str, &mut FD)>) -> Self {
self.parse_fn = Some(parse_fn);
self
}
pub fn style(mut self, attribute: FS::StylingAttributes) -> Self {
self.style_attributes.push(attribute);
self
}
pub fn end(mut self) -> FormBuilder<FD, FS> {
self.fb
.controls
.push((Box::new(self.data), self.style_attributes));
self.fb
}
}
pub mod controls;
pub mod form;
pub mod styles;

View File

@ -1,86 +0,0 @@
use crate::{
controls::{select::SelectData, submit::SubmitData, text_area::TextAreaData},
FormStyleProvider, HeadingData, TextInputData,
};
use leptos::*;
pub enum TailwindStylingAttibutes {
Width(u32),
}
pub struct TailwindFormStyleProvider;
impl FormStyleProvider for TailwindFormStyleProvider {
type StylingAttributes = TailwindFormStyleProvider;
// fn label(&self, data: LabelData) -> View {
// view! {
// }.into_view()
// }
fn heading(&self, data: HeadingData, attributes: Vec<Self::StylingAttributes>) -> View {
view! {
<h2 class="text-xl py-2 text-center font-bold text-gray-700 border-b-2 border-gray-800/60 mb-8">
{&data.title}
</h2>
}
.into_view()
}
fn text_input(&self, data: TextInputData, attributes: Vec<Self::StylingAttributes>) -> View {
view! {
<label
for={&data.name}
class="block uppercase tracking-wide text-left text-gray-700 text-md font-bold ml-2 mb-1"
>
{data.label.as_ref()}
</label>
<input
// TODO:
// type=text_type
id=&data.name
name=data.name
// placeholder=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"
/>
}.into_view()
}
fn select(&self, data: SelectData, attributes: Vec<Self::StylingAttributes>) -> View {
view! {
<select
id=&data.name
name=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"
>
{data.options}
</select>
}.into_view()
}
fn submit(&self, data: SubmitData, attributes: Vec<Self::StylingAttributes>) -> View {
view! {
<input
type="submit"
value=data.text
class="col-span-full rounded-2xl bg-sky-700 text-white font-bold hover:cursor-pointer hover:bg-sky-600 px-5 py-3 mx-auto mt-4"
/>
}
.into_view()
}
fn text_area(&self, data: TextAreaData, attributes: Vec<Self::StylingAttributes>) -> View {
view! {
<textarea
id=&data.name
name=data.name
placeholder=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"
/>
}.into_view()
}
fn custom_component(&self, component: View) -> View {
component
}
}

25
src/styles/mod.rs Normal file
View File

@ -0,0 +1,25 @@
mod tw_grid;
pub use tw_grid::{TailwindGridFormStyle, TailwindGridStylingAttributes};
use crate::{
controls::{
heading::HeadingData, select::SelectData, submit::SubmitData, text_area::TextAreaData,
text_input::TextInputData, Control, VanityControl,
},
form::FormData,
};
use leptos::View;
pub trait FormStyle: 'static {
type StylingAttributes;
// TODO: add form frame
fn heading(&self, control: VanityControl<Self, HeadingData>) -> View;
fn text_input<FD: FormData>(&self, control: Control<FD, Self, TextInputData>) -> View;
fn select<FD: FormData>(&self, control: Control<FD, Self, SelectData>) -> View;
fn submit(&self, control: VanityControl<Self, SubmitData>) -> View;
fn text_area<FD: FormData>(&self, control: Control<FD, Self, TextAreaData>) -> View;
fn custom_component(&self, view: View) -> View;
// TODO: add group
}

89
src/styles/tw_grid.rs Normal file
View File

@ -0,0 +1,89 @@
use super::FormStyle;
use crate::{
controls::{
heading::HeadingData, select::SelectData, submit::SubmitData, text_area::TextAreaData,
text_input::TextInputData, Control, VanityControl,
},
form::FormData,
};
use leptos::CollectView;
use leptos::{view, IntoView, View};
pub enum TailwindGridStylingAttributes {
Width(u32),
}
pub struct TailwindGridFormStyle;
impl FormStyle for TailwindGridFormStyle {
type StylingAttributes = TailwindGridStylingAttributes;
fn heading(&self, control: VanityControl<Self, HeadingData>) -> View {
view! {
<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}
</h2>
}
.into_view()
}
fn text_input<FD: FormData>(&self, control: Control<FD, Self, TextInputData>) -> View {
view! {
<div>
<label
for={&control.data.name}
class="block uppercase tracking-wide text-left text-gray-700 text-md font-bold ml-2 mb-1"
>
{control.data.label.as_ref()}
</label>
<input
// TODO:
type=control.data.input_type
id=&control.data.name
name=control.data.name
placeholder=control.data.placeholder
value=control.data.initial_text
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"
/>
</div>
}.into_view()
}
fn select<FD: FormData>(&self, control: Control<FD, Self, SelectData>) -> View {
view! {
<select
id=&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"
>
{control.data.options.iter().map(|option| view!{<option>{option}</option>}).collect_view()}
</select>
}.into_view()
}
fn submit(&self, control: VanityControl<Self, SubmitData>) -> View {
view! {
<input
type="submit"
value=control.data.text
class="col-span-full rounded-2xl bg-sky-700 text-white font-bold hover:cursor-pointer hover:bg-sky-600 px-5 py-3 mx-auto mt-4"
/>
}
.into_view()
}
fn text_area<FD: FormData>(&self, control: Control<FD, Self, TextAreaData>) -> View {
view! {
<textarea
id=&control.data.name
name=control.data.name
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"
/>
}.into_view()
}
fn custom_component(&self, view: View) -> View {
view
}
}