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}