rooting_forms/
impl_vec.rs

1use {
2    crate::{
3        css::{
4            css_class_depth,
5            ATTR_LABEL,
6            CSS_CLASS_BUTTON_ICON,
7            CSS_CLASS_BUTTON_ICON_ADD,
8            CSS_CLASS_BUTTON_ICON_DELETE,
9            CSS_CLASS_BUTTON_ICON_MOVE_DOWN,
10            CSS_CLASS_BUTTON_ICON_MOVE_UP,
11            CSS_CLASS_SUBFORM,
12            CSS_CLASS_VEC,
13            CSS_CLASS_VEC_ITEMS,
14            CSS_CLASS_VEC_ITEM_HEADER,
15        },
16        FormElements,
17        FormState,
18        FormWith,
19    },
20    rooting::{
21        el,
22        Container,
23        ContainerEntry,
24        El,
25    },
26    std::{
27        cell::{
28            Cell,
29            RefCell,
30        },
31        marker::PhantomData,
32        rc::Rc,
33    },
34};
35
36struct Item_<T> {
37    root: El,
38    add_index: Rc<Cell<usize>>,
39    add: El,
40    title: El,
41    state: Box<dyn FormState<T>>,
42}
43
44struct Item<C, T>(Rc<Item_<T>>, PhantomData<C>);
45
46impl<C, T> Clone for Item<C, T> {
47    fn clone(&self) -> Self {
48        return Self(self.0.clone(), Default::default());
49    }
50}
51
52impl<C, T: FormWith<C>> ContainerEntry for Item<C, T> {
53    fn el(&self) -> &El {
54        return &self.0.root;
55    }
56}
57
58struct VecFormState<C, T: FormWith<C>> {
59    items: Rc<RefCell<Container<Item<C, T>>>>,
60}
61
62impl<C, T: FormWith<C>> FormState<Vec<T>> for VecFormState<C, T> {
63    fn parse(&self) -> Result<Vec<T>, ()> {
64        let mut out = vec![];
65        let mut ok = true;
66        for i in &*self.items.borrow() {
67            let Ok(v) = i.0.state.parse() else {
68                ok = false;
69                continue;
70            };
71            out.push(v);
72        }
73        if !ok {
74            return Err(());
75        }
76        return Ok(out);
77    }
78}
79
80fn build_add<
81    C: 'static + Clone,
82    T: 'static + FormWith<C>,
83>(
84    context: &C,
85    field: &str,
86    items: &Rc<RefCell<Container<Item<C, T>>>>,
87    index: Rc<Cell<usize>>,
88    depth: usize,
89) -> El {
90    return el("button").classes(&[CSS_CLASS_BUTTON_ICON, CSS_CLASS_BUTTON_ICON_ADD]).on("click", {
91        let field = field.to_string();
92        let items = items.clone();
93        let context = context.clone();
94        move |_| {
95            build_item::<C, T>(&context, &field, &items, None, index.get(), depth);
96        }
97    });
98}
99
100fn build_item<
101    C: 'static + Clone,
102    T: 'static + FormWith<C>,
103>(
104    context: &C,
105    field: &str,
106    items: &Rc<RefCell<rooting::Container<Item<C, T>>>>,
107    from: Option<&T>,
108    initial_index: usize,
109    depth: usize,
110) {
111    let (item_elements, item_state) = T::new_form_with_(context, "Item", from, depth + 1);
112    let subform = el("div").classes(&[CSS_CLASS_SUBFORM, &css_class_depth(depth)]);
113    let add_index = Rc::new(Cell::new(initial_index));
114    let add = build_add(context, field, items, add_index.clone(), depth);
115    let item = Item(Rc::new(Item_ {
116        root: el("div").push(add.clone()).push(subform.clone()),
117        add_index: add_index,
118        add: add,
119        title: el("span"),
120        state: item_state,
121    }), Default::default());
122
123    fn renumber<C, T: FormWith<C>>(field: &str, items: &mut rooting::Container<Item<C, T>>) {
124        for (i, e) in items.iter().enumerate() {
125            let add_help = format!("{} - Add new item", field);
126            e.0.add.ref_attr(ATTR_LABEL, &add_help).ref_attr("title", &add_help);
127            e.0.add_index.set(i);
128        }
129    }
130
131    fn index<C, T: FormWith<C>>(items: &mut rooting::Container<Item<C, T>>, item: &Rc<Item_<T>>) -> usize {
132        return items.iter().enumerate().find_map(|(i, x)| if Rc::ptr_eq(&x.0, item) {
133            return Some(i);
134        } else {
135            return None;
136        }).unwrap();
137    }
138
139    let move_up =
140        el("button")
141            .classes(&[CSS_CLASS_BUTTON_ICON, CSS_CLASS_BUTTON_ICON_MOVE_UP])
142            .attr(ATTR_LABEL, &format!("{} - Move item up", field))
143            .on("click", {
144                let field = field.to_string();
145                let item = Rc::downgrade(&item.0);
146                let items = items.clone();
147                move |_| {
148                    let Some(item) = item.upgrade() else {
149                        return;
150                    };
151                    let mut items = items.as_ref().borrow_mut();
152                    let i = index(&mut *items, &item);
153                    if i == 0 {
154                        return;
155                    }
156                    let item = items.remove(i);
157                    items.insert(i - 1, item);
158                    renumber(&field, &mut *items);
159                }
160            });
161    let move_down =
162        el("button")
163            .classes(&[CSS_CLASS_BUTTON_ICON, CSS_CLASS_BUTTON_ICON_MOVE_DOWN])
164            .attr(ATTR_LABEL, &format!("{} - Move item down", field))
165            .on("click", {
166                let field = field.to_string();
167                let item = Rc::downgrade(&item.0);
168                let items = items.clone();
169                move |_| {
170                    let Some(item) = item.upgrade() else {
171                        return;
172                    };
173                    let mut items = items.as_ref().borrow_mut();
174                    let i = index(&mut *items, &item);
175                    if i + 1 >= items.len() {
176                        return;
177                    }
178                    let item = items.remove(i);
179                    items.insert(i + 1, item);
180                    renumber(&field, &mut *items);
181                }
182            });
183    let delete =
184        el("button")
185            .classes(&[CSS_CLASS_BUTTON_ICON, CSS_CLASS_BUTTON_ICON_DELETE])
186            .attr(ATTR_LABEL, &format!("{} - Delete item", field))
187            .on("click", {
188                let field = field.to_string();
189                let item = Rc::downgrade(&item.0);
190                let items = items.clone();
191                move |_| {
192                    let Some(item) = item.upgrade() else {
193                        return;
194                    };
195                    let mut items = items.as_ref().borrow_mut();
196                    let i = index(&mut *items, &item);
197                    items.remove(i);
198                    renumber(&field, &mut *items);
199                }
200            });
201    subform.ref_push(
202        el("div")
203            .classes(&[CSS_CLASS_VEC_ITEM_HEADER])
204            .extend(vec![item.0.title.clone(), move_up, move_down, delete]),
205    );
206    if let Some(error) = item_elements.error {
207        subform.ref_push(error);
208    }
209    subform.ref_extend(item_elements.elements);
210    items.as_ref().borrow_mut().insert(initial_index, item);
211    renumber(field, &mut *items.borrow_mut());
212}
213
214impl<C: 'static + Clone, T: FormWith<C> + 'static> FormWith<C> for Vec<T> {
215    fn new_form_with_(
216        context: &C,
217        field: &str,
218        from: Option<&Self>,
219        depth: usize,
220    ) -> (FormElements, Box<dyn FormState<Self>>) {
221        let items = Rc::new(RefCell::new(Container::new(el("div").classes(&[CSS_CLASS_VEC_ITEMS]))));
222        if let Some(from) = from {
223            for (i, v) in from.iter().enumerate() {
224                build_item(context, field, &items, Some(v), i, depth);
225            }
226        }
227        let elements =
228            vec![
229                items.as_ref().borrow().el().clone(),
230                build_add(context, field, &items, Rc::new(Cell::new(items.borrow().len())), depth)
231            ];
232        return (crate::FormElements {
233            error: None,
234            elements: vec![el("div").classes(&[CSS_CLASS_VEC]).extend(elements)],
235        }, Box::new(VecFormState { items: items }));
236    }
237}