Skip to main content

tilo_app/framework/
form.rs

1//! Forms: a verb's parameters filled in before it is invoked.
2//!
3//! A form is a local instance of a verb's parameter struct: never synced,
4//! never undoable, invisible to collaborators. Its fields bind to controls
5//! with the same paths a record's do (`cx.input(form.paths().url)`),
6//! and its button submits it as one invocation of the verb.
7
8use std::any::Any;
9use std::collections::BTreeMap;
10use std::marker::PhantomData;
11use std::sync::{Arc, Mutex, PoisonError};
12
13use serde::Serialize;
14use tilo_actions::ActionBinding;
15use tilo_resource::{Invoke, Params, Path, Resource, Segment};
16use ui_core::LocalState;
17
18use super::controls::Actions;
19use super::dispatch::Submit;
20
21/// One field of a form, whatever its type.
22trait Slot: Send + Sync {
23    fn json(&self) -> Result<serde_json::Value, String>;
24    fn clear(&self);
25    fn any(&self) -> &dyn Any;
26}
27
28struct Typed<V: Send + Sync + 'static>(LocalState<V>);
29
30impl<V> Slot for Typed<V>
31where
32    V: Serialize + Default + Send + Sync + 'static,
33{
34    fn json(&self) -> Result<serde_json::Value, String> {
35        self.0
36            .with(|value| serde_json::to_value(value))
37            .map_err(|error| error.to_string())
38    }
39
40    fn clear(&self) {
41        self.0.set(V::default());
42    }
43
44    fn any(&self) -> &dyn Any {
45        &self.0
46    }
47}
48
49type Slots = Arc<Mutex<BTreeMap<String, Box<dyn Slot>>>>;
50
51/// The parameters of the verb `P` as they are being filled in
52/// ([`ViewCx::form`](super::ViewCx::form)). Clones share the values.
53pub struct Form<P: Params> {
54    slots: Slots,
55    marker: PhantomData<fn() -> P>,
56}
57
58impl<P: Params> Clone for Form<P> {
59    fn clone(&self) -> Self {
60        Self {
61            slots: Arc::clone(&self.slots),
62            marker: PhantomData,
63        }
64    }
65}
66
67impl<P: Params> Form<P> {
68    pub(crate) fn new() -> Self {
69        Self {
70            slots: Arc::default(),
71            marker: PhantomData,
72        }
73    }
74
75    /// The value of the field at `path`, as local state a control edits.
76    pub(crate) fn field<V>(&self, path: &Path<P, V>) -> LocalState<V>
77    where
78        V: Serialize + Default + Send + Sync + 'static,
79    {
80        let key = match path.segments() {
81            [Segment::Field(key)] => key.clone(),
82            other => other
83                .iter()
84                .map(ToString::to_string)
85                .collect::<Vec<_>>()
86                .join("."),
87        };
88        let mut slots = self.slots.lock().unwrap_or_else(PoisonError::into_inner);
89        if let Some(state) = slots
90            .get(&key)
91            .and_then(|slot| slot.any().downcast_ref::<LocalState<V>>())
92        {
93            return state.clone();
94        }
95        let state = LocalState::new(V::default());
96        slots.insert(key, Box::new(Typed(state.clone())));
97        state
98    }
99
100    /// The paths of the verb's parameters, which controls bind to:
101    /// `cx.input(form.paths().url)`.
102    #[must_use]
103    pub fn paths(&self) -> P::Paths {
104        P::paths()
105    }
106
107    /// The parameters as filled in now.
108    ///
109    /// # Errors
110    /// Returns why they are not a valid `P` (a required field never set).
111    pub fn values(&self) -> Result<P, String> {
112        read::<P>(&self.slots)
113    }
114
115    /// Empty every field again.
116    pub fn clear(&self) {
117        clear(&self.slots);
118    }
119
120    /// The form's submission, for a button (`cx.button("add", form.submit())`)
121    /// or a dispatcher: one invocation of the verb with the parameters as
122    /// filled in when it is pressed. The form empties once it is sent.
123    #[must_use]
124    pub fn submit(&self) -> FormSubmit<P> {
125        FormSubmit {
126            slots: Arc::clone(&self.slots),
127            marker: PhantomData,
128        }
129    }
130}
131
132fn read<P: Params>(slots: &Slots) -> Result<P, String> {
133    let fields = slots
134        .lock()
135        .unwrap_or_else(PoisonError::into_inner)
136        .iter()
137        .map(|(key, slot)| Ok((key.clone(), slot.json()?)))
138        .collect::<Result<serde_json::Map<_, _>, String>>()?;
139    serde_json::from_value(serde_json::Value::Object(fields)).map_err(|error| error.to_string())
140}
141
142fn clear(slots: &Slots) {
143    for slot in slots
144        .lock()
145        .unwrap_or_else(PoisonError::into_inner)
146        .values()
147    {
148        slot.clear();
149    }
150}
151
152/// A form's submission ([`Form::submit`]).
153pub struct FormSubmit<P: Params> {
154    slots: Slots,
155    marker: PhantomData<fn() -> P>,
156}
157
158impl<P> Submit<P::Resource> for FormSubmit<P>
159where
160    P: Params,
161    P::Resource: Resource,
162{
163    fn action_id(&self) -> &'static str {
164        P::ACTION.id
165    }
166
167    fn into_invoke(self) -> Result<Invoke<P::Resource>, String> {
168        let params = read::<P>(&self.slots)?;
169        clear(&self.slots);
170        Ok(P::ACTION.with(params))
171    }
172
173    fn control_binding(self, actions: &Actions<P::Resource>) -> Option<ActionBinding<()>> {
174        let slots = self.slots;
175        actions.deferred_binding(P::ACTION.id, move || {
176            let params = read::<P>(&slots)?;
177            let arguments = P::ACTION.arguments(&params)?;
178            clear(&slots);
179            Ok(arguments)
180        })
181    }
182}
183
184/// Where a bound control's value lives: a field of the record, or a field
185/// of a form. See [`ViewCx::input`](super::ViewCx::input).
186pub enum Attached<V> {
187    /// A field of the record: shown from the state, changed by field edits.
188    Record {
189        id: String,
190        state: LocalState<V>,
191        binding: Option<Box<ActionBinding<V>>>,
192    },
193    /// A field of a form: the control edits the local value itself.
194    Local { id: String, state: LocalState<V> },
195}
196
197/// Marks the [`Bindable`](super::Bindable) implementation for a record's field.
198#[doc(hidden)]
199pub enum RecordField {}
200
201/// Marks the [`Bindable`](super::Bindable) implementation for a form's field.
202#[doc(hidden)]
203pub enum FormField {}
204
205/// The forms of one view scope, by parameter type.
206#[derive(Clone, Default)]
207pub(crate) struct Forms(
208    std::rc::Rc<std::cell::RefCell<std::collections::HashMap<std::any::TypeId, Box<dyn Any>>>>,
209);
210
211impl Forms {
212    pub(crate) fn get<P: Params>(&self) -> Form<P> {
213        let key = std::any::TypeId::of::<P>();
214        let mut forms = self.0.borrow_mut();
215        if let Some(form) = forms
216            .get(&key)
217            .and_then(|form| form.downcast_ref::<Form<P>>())
218        {
219            return form.clone();
220        }
221        let form = Form::<P>::new();
222        forms.insert(key, Box::new(form.clone()));
223        form
224    }
225}