vanity controls can now have optional getters

This commit is contained in:
Mitchell Marino 2024-06-05 17:04:28 -05:00
parent 749ccc272b
commit 9b4bbfdb74
8 changed files with 92 additions and 47 deletions

View File

@ -1,6 +1,6 @@
use super::{ControlRenderData, VanityControlBuilder, VanityControlData};
use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
use leptos::View;
use leptos::{Signal, View};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct GroupData {
@ -8,7 +8,11 @@ pub struct GroupData {
}
impl VanityControlData for GroupData {
fn build_control<FS: FormStyle>(fs: &FS, control: ControlRenderData<FS, Self>) -> View {
fn build_control<FS: FormStyle>(
fs: &FS,
control: ControlRenderData<FS, Self>,
_value_getter: Option<Signal<String>>,
) -> View {
fs.group(control)
}
}
@ -16,13 +20,15 @@ impl VanityControlData for GroupData {
impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
pub fn group(
self,
builder: impl Fn(VanityControlBuilder<FS, GroupData>) -> VanityControlBuilder<FS, GroupData>,
builder: impl Fn(
VanityControlBuilder<FD, FS, GroupData>,
) -> VanityControlBuilder<FD, FS, GroupData>,
) -> Self {
self.new_vanity(builder)
}
}
impl<FS: FormStyle> VanityControlBuilder<FS, GroupData> {
impl<FD: FormToolData, FS: FormStyle> VanityControlBuilder<FD, FS, GroupData> {
pub fn title(mut self, title: impl ToString) -> Self {
self.data.title = Some(title.to_string());
self

View File

@ -8,21 +8,28 @@ pub struct HeadingData {
}
impl VanityControlData for HeadingData {
fn build_control<FS: FormStyle>(fs: &FS, control: ControlRenderData<FS, Self>) -> View {
fn build_control<FS: FormStyle>(
fs: &FS,
control: ControlRenderData<FS, Self>,
_value_getter: Option<leptos::prelude::Signal<String>>,
) -> View {
fs.heading(control)
}
}
// TODO: impl GetterVanityControl
impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
pub fn heading(
self,
builder: impl Fn(VanityControlBuilder<FS, HeadingData>) -> VanityControlBuilder<FS, HeadingData>,
builder: impl Fn(
VanityControlBuilder<FD, FS, HeadingData>,
) -> VanityControlBuilder<FD, FS, HeadingData>,
) -> Self {
self.new_vanity(builder)
}
}
impl<FS: FormStyle> VanityControlBuilder<FS, HeadingData> {
impl<FD: FormToolData, FS: FormStyle> VanityControlBuilder<FD, FS, HeadingData> {
pub fn title(mut self, title: impl ToString) -> Self {
self.data.title = title.to_string();
self

View File

@ -41,8 +41,13 @@ impl<FS, FD, F> RenderFn<FS, FD> for F where
/// A trait for the data needed to render an read-only control.
pub trait VanityControlData: 'static {
/// Builds the control, returning the [`View`] that was built.
fn build_control<FS: FormStyle>(fs: &FS, control: ControlRenderData<FS, Self>) -> View;
fn build_control<FS: FormStyle>(
fs: &FS,
control: ControlRenderData<FS, Self>,
value_getter: Option<Signal<String>>,
) -> View;
}
pub trait GetterVanityControlData: VanityControlData {}
/// A trait for the data needed to render an interactive control.
pub trait ControlData: 'static {
@ -66,29 +71,51 @@ pub struct ControlRenderData<FS: FormStyle + ?Sized, C: ?Sized> {
}
/// The data needed to render a read-only control of type `C`.
pub struct VanityControlBuilder<FS: FormStyle, C: VanityControlData> {
pub struct VanityControlBuilder<FD: FormToolData, FS: FormStyle, C: VanityControlData> {
pub(crate) style_attributes: Vec<FS::StylingAttributes>,
pub(crate) data: C,
pub(crate) getter: Option<Rc<dyn FieldGetter<FD, String>>>,
}
impl<FS: FormStyle, C: VanityControlData> VanityControlBuilder<FS, C> {
pub(crate) struct BuiltVanityControlData<FD: FormToolData, FS: FormStyle, C: VanityControlData> {
pub(crate) render_data: ControlRenderData<FS, C>,
pub(crate) getter: Option<Rc<dyn FieldGetter<FD, String>>>,
}
impl<FD: FormToolData, FS: FormStyle, C: VanityControlData> VanityControlBuilder<FD, FS, C> {
/// Creates a new [`VanityControlBuilder`] with the given [`VanityControlData`].
pub(crate) fn new(data: C) -> Self {
VanityControlBuilder {
data,
style_attributes: Vec::new(),
getter: None,
}
}
/// Builds the builder into the data needed to render the control.
pub(crate) fn build(self) -> ControlRenderData<FS, C> {
ControlRenderData {
data: Box::new(self.data),
style: self.style_attributes,
pub(crate) fn build(self) -> BuiltVanityControlData<FD, FS, C> {
BuiltVanityControlData {
render_data: ControlRenderData {
data: Box::new(self.data),
style: self.style_attributes,
},
getter: self.getter,
}
}
}
impl<FD: FormToolData, FS: FormStyle, C: GetterVanityControlData> VanityControlBuilder<FD, FS, C> {
/// Sets the getter function.
///
/// This function can get a string from the form data to be displayed
///
/// Setting this getter field is NOT required for vanity controls like this one.
pub fn getter(mut self, getter: impl FieldGetter<FD, String>) -> Self {
self.getter = Some(Rc::new(getter));
self
}
}
/// The possibilities for errors when building a control.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
pub enum ControlBuildError {

View File

@ -1,32 +1,29 @@
use leptos::{Signal, View};
use super::{ControlBuilder, ControlData, ControlRenderData};
use super::{ControlRenderData, GetterVanityControlData, VanityControlBuilder, VanityControlData};
use crate::{form::FormToolData, form_builder::FormBuilder, styles::FormStyle};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct OutputData;
impl ControlData for OutputData {
type ReturnType = String;
impl VanityControlData for OutputData {
fn build_control<FS: FormStyle>(
fs: &FS,
control: ControlRenderData<FS, Self>,
value_getter: Signal<Self::ReturnType>,
_value_setter: Box<dyn Fn(Self::ReturnType)>,
_validation_state: Signal<Result<(), String>>,
value_getter: Option<Signal<String>>,
) -> View {
fs.output(control, value_getter)
}
}
impl GetterVanityControlData for OutputData {}
impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
pub fn output<FDT: Clone + PartialEq + 'static>(
pub fn output(
self,
builder: impl Fn(
ControlBuilder<FD, FS, OutputData, FDT>,
) -> ControlBuilder<FD, FS, OutputData, FDT>,
VanityControlBuilder<FD, FS, OutputData>,
) -> VanityControlBuilder<FD, FS, OutputData>,
) -> Self {
self.new_control(builder)
self.new_vanity(builder)
}
}

View File

@ -9,7 +9,11 @@ pub struct SubmitData {
}
impl VanityControlData for SubmitData {
fn build_control<FS: FormStyle>(fs: &FS, control: ControlRenderData<FS, Self>) -> View {
fn build_control<FS: FormStyle>(
fs: &FS,
control: ControlRenderData<FS, Self>,
_value_getter: Option<leptos::prelude::Signal<String>>,
) -> View {
fs.submit(control)
}
}
@ -17,13 +21,15 @@ impl VanityControlData for SubmitData {
impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
pub fn submit(
self,
builder: impl Fn(VanityControlBuilder<FS, SubmitData>) -> VanityControlBuilder<FS, SubmitData>,
builder: impl Fn(
VanityControlBuilder<FD, FS, SubmitData>,
) -> VanityControlBuilder<FD, FS, SubmitData>,
) -> Self {
self.new_vanity(builder)
}
}
impl<FS: FormStyle> VanityControlBuilder<FS, SubmitData> {
impl<FD: FormToolData, FS: FormStyle> VanityControlBuilder<FD, FS, SubmitData> {
pub fn text(mut self, text: impl ToString) -> Self {
self.data.text = text.to_string();
self

View File

@ -1,7 +1,8 @@
use crate::{
controls::{
BuiltControlData, ControlBuilder, ControlData, FieldGetter, FieldSetter, ParseFn, RenderFn,
UnparseFn, ValidationCb, ValidationFn, VanityControlBuilder, VanityControlData,
BuiltControlData, BuiltVanityControlData, ControlBuilder, ControlData, FieldGetter,
FieldSetter, ParseFn, RenderFn, UnparseFn, ValidationCb, ValidationFn,
VanityControlBuilder, VanityControlData,
},
form::{Form, FormToolData, Validator},
styles::FormStyle,
@ -43,7 +44,7 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
pub(crate) fn new_vanity<C: VanityControlData + Default>(
mut self,
builder: impl Fn(VanityControlBuilder<FS, C>) -> VanityControlBuilder<FS, C>,
builder: impl Fn(VanityControlBuilder<FD, FS, C>) -> VanityControlBuilder<FD, FS, C>,
) -> Self {
let vanity_builder = VanityControlBuilder::new(C::default());
let control = builder(vanity_builder);
@ -64,12 +65,16 @@ impl<FD: FormToolData, FS: FormStyle> FormBuilder<FD, FS> {
// TODO: test this from a user context. A user adding a custom defined component.
pub fn add_vanity<C: VanityControlData>(
&mut self,
vanity_control: VanityControlBuilder<FS, C>,
vanity_control: VanityControlBuilder<FD, FS, C>,
) {
let render_data = vanity_control.build();
let BuiltVanityControlData {
render_data,
getter,
} = vanity_control.build();
let render_fn = move |fs: &FS, _| {
let view = VanityControlData::build_control(fs, render_data);
let render_fn = move |fs: &FS, fd: RwSignal<FD>| {
let value_getter = getter.map(|getter| (move || getter(fd.get())).into_signal());
let view = VanityControlData::build_control(fs, render_data, value_getter);
(view, None)
};

View File

@ -18,13 +18,11 @@ impl FormStyle for GridFormStyle {
type StylingAttributes = GridFormStylingAttributes;
fn form_frame(&self, children: View) -> View {
view! { <div class="form_grid">{children}</div> }
.into_view()
view! { <div class="form_grid">{children}</div> }.into_view()
}
fn heading(&self, control: ControlRenderData<Self, HeadingData>) -> View {
view! { <h2 class="form_heading">{&control.data.title}</h2> }
.into_view()
view! { <h2 class="form_heading">{&control.data.title}</h2> }.into_view()
}
fn text_input(
@ -115,7 +113,7 @@ impl FormStyle for GridFormStyle {
fn submit(&self, control: ControlRenderData<Self, SubmitData>) -> View {
view! { <input type="submit" value=control.data.text class="col-span-full form_submit"/> }
.into_view()
.into_view()
}
fn text_area(
@ -153,8 +151,7 @@ impl FormStyle for GridFormStyle {
_control: ControlRenderData<Self, HiddenData>,
value_getter: Signal<String>,
) -> View {
view! { <input prop:value=value_getter style="visibility: hidden"/> }
.into_view()
view! { <input prop:value=value_getter style="visibility: hidden"/> }.into_view()
}
fn radio_buttons(
@ -298,16 +295,16 @@ impl FormStyle for GridFormStyle {
class="form_input"
/>
</div>
}.into_view()
}
.into_view()
}
fn output(
&self,
_control: ControlRenderData<Self, OutputData>,
value_getter: Signal<String>,
value_getter: Option<Signal<String>>,
) -> View {
view! { <div>{move || value_getter.get()}</div> }
.into_view()
view! { <div>{move || value_getter.map(|g| g.get())}</div> }.into_view()
}
fn slider(

View File

@ -70,7 +70,7 @@ pub trait FormStyle: Default + 'static {
fn output(
&self,
control: ControlRenderData<Self, OutputData>,
value_getter: Signal<String>,
value_getter: Option<Signal<String>>,
) -> View;
fn slider(
&self,