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