1use crate::{Data, FormData, QueryString, QueryStringPart};
2use leptos::*;
3use serde::{de::DeserializeOwned, Serialize};
4use std::{collections::HashMap, ops::Deref, str::FromStr, sync::atomic::AtomicU64};
5
6static VERSION: VersionProvider = VersionProvider::new();
7pub type Version = u64;
8
9struct VersionProvider(AtomicU64);
10
11impl VersionProvider {
12 const fn new() -> Self {
13 Self(AtomicU64::new(0))
14 }
15
16 fn next(&self) -> Version {
17 self.0.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
18 }
19}
20
21#[derive(Debug, Clone, Copy)]
22pub struct BaseGroupContext(GroupContext);
23
24impl BaseGroupContext {
25 pub(crate) fn new() -> Self {
26 let group = GroupContext(RwSignal::new(GroupData {
27 inputs: HashMap::new(),
28 order: Vec::new(),
29 qs: QueryString::default(),
30 disabled: false,
31 label: None,
32 }));
33
34 BaseGroupContext(group)
35 }
36
37 pub fn to_group_context(self) -> GroupContext {
38 self.0
39 }
40}
41
42impl Deref for BaseGroupContext {
43 type Target = GroupContext;
44
45 fn deref(&self) -> &Self::Target {
46 &self.0
47 }
48}
49
50
51#[derive(Debug, Clone, Copy, PartialEq, Eq)]
52pub struct InputContext(RwSignal<InputData>);
53
54#[derive(Debug, Clone)]
55struct InputData {
56 error: bool,
57 disabled: bool,
58 validate: Version,
59 creation: Version,
60 qs: QueryString,
61 label: Option<TextProp>,
62}
63
64impl InputContext {
65 pub fn new(bind: QueryStringPart) -> Self {
66 let context = expect_context::<GroupContext>();
67 Self::new_with_context(bind, context)
68 }
69
70 pub fn add_label(&self, label: TextProp) {
71 self.0.update(|input| {
72 input.label = Some(label);
73 });
74 }
75
76 pub fn label(&self) -> Option<TextProp> {
77 self.0.get_untracked().label
78 }
79
80 fn new_with_context(bind: QueryStringPart, context: GroupContext) -> Self {
81 let version = VERSION.next();
82
83 let input = InputContext(RwSignal::new(InputData {
84 error: false,
85 disabled: context.disabled().get_untracked(),
86 qs: context.qs().add(bind),
87 creation: version,
88 validate: 0,
89 label: None,
90 }));
91
92 context.register_input(bind, input);
93 on_cleanup(move || {
94 context.deregister(&bind);
95 });
96
97 input
98 }
99
100 pub fn set_error(&self, has_error: bool) {
101 self.0.update(|input| {
102 input.error = has_error;
103 });
104 }
105
106 pub fn error(&self) -> Signal<bool> {
107 let self_signal = self.0;
108 Memo::new(move |_| self_signal.get().error).into()
109 }
110
111 pub fn set_disabled(&self, disabled: bool) {
112 self.0.update(|input| {
113 input.disabled = disabled;
114 });
115 }
116
117 pub fn disabled(&self) -> Signal<bool> {
118 let self_signal = self.0;
119 Memo::new(move |_| self_signal.get().disabled).into()
120 }
121
122 pub fn validate(&self) {
123 self.0.update(|input| {
124 input.validate = VERSION.next();
125 });
126 }
127
128 pub fn validate_signal(&self) -> Signal<bool> {
129 let self_signal = self.0;
130 Memo::new(move |_| self_signal.get().validate > self_signal.get().creation).into()
131 }
132
133 pub fn qs(&self) -> QueryString {
134 self.0.get_untracked().qs
135 }
136
137 pub fn raw_value(&self) -> Signal<String> {
138 let qs = self.qs();
139 Signal::derive(move || {
140 expect_context::<FormData>()
141 .get(qs)
142 .get()
143 .map(|data| data.into_input().unwrap().raw().to_owned())
144 .unwrap_or_default()
145 })
146 }
147
148 pub fn set_raw_value<T: ToString>(&self, value: T) {
149 let qs = self.qs();
150 expect_context::<FormData>().set(qs, Data::new_input(value.to_string()));
151 }
152
153 pub fn value<T: FromStr>(&self) -> Signal<Result<T, T::Err>> {
154 let self_signal = self.raw_value();
155 Signal::derive(move || {
156 T::from_str(&self_signal.get())
157 })
158 }
159
160 pub fn set_value<T: ToString>(&self, value: T) {
161 self.set_raw_value(value);
162 }
163
164 pub fn get(&self, qs: QueryString) -> Signal<Option<Node>> {
165 let self_copy = *self;
166 Memo::new(move |_| {
167 if qs.is_empty() {
168 Some(Node::Input(self_copy))
169 } else {
170 panic!("invalid query string");
171 }
172 }).into()
173 }
174}
175
176#[derive(Debug, Clone, Copy, PartialEq, Eq)]
177pub struct GroupContext(RwSignal<GroupData>);
178
179#[derive(Debug, Clone)]
180struct GroupData {
181 inputs: HashMap<QueryStringPart, Node>,
182 order: Vec<QueryStringPart>,
183 qs: QueryString,
184 disabled: bool,
185 label: Option<TextProp>,
186}
187
188impl GroupContext {
189 pub fn new(bind: QueryStringPart) -> Self {
190 let context = expect_context::<GroupContext>();
191 Self::new_with_context(bind, context)
192 }
193
194 pub fn add_label(&self, label: TextProp) {
195 self.0.update(|data| {
196 data.label = Some(label);
197 });
198 }
199
200 pub fn label(&self) -> Option<TextProp> {
201 self.0.get_untracked().label
202 }
203
204 fn new_with_context(bind: QueryStringPart, context: GroupContext) -> Self {
205
206 let group = GroupContext(RwSignal::new(GroupData {
207 inputs: HashMap::new(),
208 order: Vec::new(),
209 qs: context.qs().add(bind),
210 disabled: false,
211 label: None,
212 }));
213
214 context.register_group(bind, group);
215 on_cleanup(move || {
216 context.deregister(&bind);
217 });
218
219 group
220 }
221
222 pub fn disabled(&self) -> Signal<bool> {
223 let self_signal = self.0;
224 Signal::derive(move || self_signal.get().disabled)
225 }
226
227 pub fn nodes(&self) -> Vec<Node> {
228 self.0.get().order.iter().map(|qs| {
229 self.0.get().inputs.get(qs).unwrap().clone()
230 }).collect()
231 }
232
233 pub fn qs(&self) -> QueryString {
234 self.0.get_untracked().qs
235 }
236
237 fn register_input(&self, qs: QueryStringPart, input: InputContext) {
238 self.0.update(|data| {
239 data.inputs.insert(qs, Node::Input(input));
240 data.order.push(qs);
241 });
242 }
243
244 fn register_group(&self, qs: QueryStringPart, group: GroupContext) {
245 self.0.update(|data| {
246 data.inputs.insert(qs, Node::Group(group));
247 data.order.push(qs);
248 });
249 }
250
251 pub fn deregister(&self, qs: &QueryStringPart) {
252 self.0.update(|data| {
253 data.inputs.remove(qs);
254 data.order.retain(|k| k != qs);
255 });
256 }
257
258 pub fn set_disabled(&self, disabled: bool) {
259 logging::log!("set disabled: {}", disabled);
260 self.0.update(|data| {
261 data.disabled = disabled;
262 });
263 self.0.get_untracked().inputs.values().for_each(|node| {
264 match node {
265 Node::Input(input) => input.set_disabled(disabled),
266 Node::Group(group) => group.set_disabled(disabled),
267 }
268 });
269 }
270
271 pub fn error(&self) -> Signal<bool> {
272 let self_signal = self.0;
273 Signal::derive(move || self_signal.get().inputs.values().any(|node| {
274 match node {
275 Node::Input(input) => input.error().get(),
276 Node::Group(group) => group.error().get(),
277 }
278 }))
279 }
280
281 pub fn validate(&self) {
282 self.0.get_untracked().inputs.values().for_each(|node| {
283 match node {
284 Node::Input(input) => input.validate(),
285 Node::Group(group) => group.validate(),
286 }
287 });
288 }
289
290 pub fn len(&self) -> Signal<Option<usize>> {
291 let qs = self.qs();
292 Signal::derive(move || {
293 expect_context::<FormData>().get(qs).get().map(|d| d.as_group().unwrap().len())
294 })
295 }
296
297 pub fn raw_value(&self) -> Signal<Data> {
298 let qs = self.qs();
299 Signal::derive(move || {
300 expect_context::<FormData>().get(qs).get().unwrap()
301 })
302 }
303
304 pub fn set_raw_value(&self, data: Data) {
305 let qs = self.qs();
306 expect_context::<FormData>().set(qs, data);
307 }
308
309
310 pub fn value<T: DeserializeOwned + PartialEq>(&self) -> Signal<Option<T>> {
311 let data = self.raw_value();
312
313 Memo::new(move |_| {
314 let deserialized = data.get().to::<T>();
315
316 if cfg!(debug_assertions) {
317 if let Err(err) = &deserialized {
318 logging::warn!("deserialization error, this may indicated that your form data is not matching your bindings: {:?}", err);
319 }
320 }
321
322 Some(deserialized.ok()?)
323 }).into()
324 }
325
326 pub fn set_value<T: Serialize>(&self, value: &T) {
327 self.set_raw_value(Data::from(value));
328 }
329
330 pub fn get(&self, qs: QueryString) -> Signal<Option<Node>> {
331 let self_copy = *self;
332 Memo::new(move |_| {
333 let mut qs = qs.iter();
334 let (head, tail) = (qs.next(), qs.collect());
335 if let Some(head) = head {
336 self_copy.0.get().inputs.get(&head)?.get(tail).get()
337 } else {
338 Some(Node::Group(self_copy))
339 }
340 }).into()
341 }
342}
343
344#[derive(Debug, Clone, Copy, PartialEq, Eq)]
345pub enum Node {
346 Input(InputContext),
347 Group(GroupContext),
348}
349
350impl Node {
351 pub fn qs(&self) -> QueryString {
352 match self {
353 Node::Input(input) => input.qs(),
354 Node::Group(group) => group.qs(),
355 }
356 }
357
358 pub fn as_group(&self) -> Option<&GroupContext> {
359 match self {
360 Node::Input(_) => None,
361 Node::Group(group) => Some(group),
362 }
363 }
364
365 pub fn as_input(&self) -> Option<&InputContext> {
366 match self {
367 Node::Input(input) => Some(input),
368 Node::Group(_) => None,
369 }
370 }
371
372 pub fn into_group(self) -> GroupContext {
373 match self {
374 Node::Input(_) => panic!("expected group, got input"),
375 Node::Group(group) => group,
376 }
377 }
378
379 pub fn into_input(self) -> InputContext {
380 match self {
381 Node::Input(input) => input,
382 Node::Group(_) => panic!("expected input, got group"),
383 }
384 }
385
386 pub fn set_disabled(&self, disabled: bool) {
387 match self {
388 Node::Input(input) => input.set_disabled(disabled),
389 Node::Group(group) => group.set_disabled(disabled),
390 }
391 }
392
393 pub fn validate(&self) {
394 match self {
395 Node::Input(input) => input.validate(),
396 Node::Group(group) => group.validate(),
397 }
398 }
399
400 pub fn get(&self, qs: QueryString) -> Signal<Option<Node>> {
401 match self {
402 Node::Input(input) => input.get(qs),
403 Node::Group(group) => group.get(qs),
404 }
405 }
406}
407
408pub fn node(qs: QueryString) -> Signal<Option<Node>> {
409 let group = expect_context::<BaseGroupContext>();
410 group.get(qs)
411}
412
413#[macro_export]
414macro_rules! node {
415 ( $key:ident $( [ $tail:tt ] )* ) => {
416 node!(@part($crate::QueryString::default().add_key(stringify!($key))) $( [ $tail ] )*)
417 };
418 ( .. $( [ $tail:tt ] )* ) => {
419 node!(@part(leptos::expect_context::<$crate::GroupContext>().qs()) $( [ $tail ] )*)
420 };
421 ( @part($part:expr) [ $index:literal ] $( [ $tail:tt ] )* ) => {
422 node!(@part($part.add_index($index))$( [ $tail ] )*)
423 };
424 ( @part($part:expr) [ $key:ident ] $( [ $tail:tt ] )* ) => {
425 node!(@part($part.add_key(stringify!($key))) $( [ $tail ] )*)
426 };
427 ( @part($part:expr) ) => {
428 node($part)
429 };
430}
431
432#[macro_export]
433macro_rules! group {
434 ( $($key:ident)? $( [ $tail:tt ] )* ) => {
435 Signal::derive(move || Some(node!( $($key)? $( [ $tail ] )* ).get()?.into_group()))
436 };
437 ( .. $( [ $tail:tt ] )* ) => {
438 Signal::derive(move || Some(node!( .. $( [ $tail ] )* ).get()?.into_group()))
439 };
440}
441
442#[macro_export]
443macro_rules! input {
444 ( $($key:ident)? $( [ $tail:tt ] )* ) => {
445 Signal::derive(move || Some(node!( $($key)? $( [ $tail ] )* ).get()?.into_input()))
446 };
447 ( .. $( [ $tail:tt ] )* ) => {
448 Signal::derive(move || Some(node!( .. $( [ $tail ] )* ).get()?.into_input()))
449 };
450}
451
452#[macro_export]
453macro_rules! value {
454 ( $($key:ident)? $( [ $tail:tt ] )* as $ty:ty ) => {
455 Signal::derive(move || Some(input!( $($key)? $( [ $tail ] )* ).get()?.value::<$ty>().get().ok()?))
456 };
457 ( .. $( [ $tail:tt ] )* as $ty:ty ) => {
458 Signal::derive(move || Some(input!( .. $( [ $tail ] )* ).get()?.value::<$ty>().get().ok()?))
459 };
460}
461
462#[macro_export]
463macro_rules! values {
464 ( $($key:ident)? $( [ $tail:tt ] )* ) => {
465 Signal::derive(move || Some(group!( $($key)? $( [ $tail ] )* ).get()?.raw_value().get()))
466 };
467 ( .. $( [ $tail:tt ] )* ) => {
468 Signal::derive(move || Some(group!( .. $( [ $tail ] )* ).get()?.raw_value().get()))
469 };
470}
471
472#[cfg(test)]
473mod tests {
474 use crate::qs;
475 use super::*;
476
477 #[test]
478 fn test_node_macro() {
479 let _ = leptos::create_runtime();
480
481 let form_data = FormData::new();
482 let group = BaseGroupContext::new();
483 provide_context(form_data);
484 provide_context(group);
485 provide_context(group.to_group_context());
486
487 let group = GroupContext::new(QueryStringPart::from("a"));
488 let input = InputContext::new_with_context(QueryStringPart::from("b"), group);
489 input.set_raw_value("test");
490
491 let node = node!(a[b]);
492 assert_eq!(node.get_untracked().unwrap().as_input().unwrap().qs(), qs!(a[b]));
493 }
494
495 #[test]
496 fn test_input_macro() {
497 let _ = leptos::create_runtime();
498
499 let form_data = FormData::new();
500 let group = BaseGroupContext::new();
501 provide_context(form_data);
502 provide_context(group);
503 provide_context(group.to_group_context());
504
505 let group = GroupContext::new(QueryStringPart::from("a"));
506 let input = InputContext::new_with_context(QueryStringPart::from("b"), group);
507 input.set_raw_value("test");
508
509 let node = input!(a[b]);
510 assert_eq!(node.get_untracked().unwrap().qs(), qs!(a[b]));
511 }
512
513 #[test]
514 fn test_group_macro() {
515 let _ = leptos::create_runtime();
516
517 let form_data = FormData::new();
518 let group = BaseGroupContext::new();
519 provide_context(form_data);
520 provide_context(group);
521 provide_context(group.to_group_context());
522
523 let group = GroupContext::new(QueryStringPart::from("a"));
524 let input = InputContext::new_with_context(QueryStringPart::from("b"), group);
525 input.set_raw_value("test");
526
527 let node = group!(a);
528 assert_eq!(node.get_untracked().unwrap().nodes().len(), 1);
529 }
530
531 #[test]
532 fn test_value_macro() {
533 let _ = leptos::create_runtime();
534
535 let form_data = FormData::new();
536 let group = BaseGroupContext::new();
537 provide_context(form_data);
538 provide_context(group);
539 provide_context(group.to_group_context());
540
541 let group = GroupContext::new(QueryStringPart::from("a"));
542 let input = InputContext::new_with_context(QueryStringPart::from("b"), group);
543 input.set_raw_value("test");
544
545 let value = value!(a[b] as String);
546 assert_eq!(value.get_untracked().unwrap(), "test");
547 }
548
549 #[test]
550 fn test_input() {
551 let _ = leptos::create_runtime();
552
553 let form_data = FormData::new();
554 let group = BaseGroupContext::new();
555 provide_context(form_data);
556 provide_context(group.to_group_context());
557
558 let input = InputContext::new(QueryStringPart::from("a"));
559 input.set_raw_value("test");
560
561 assert_eq!(form_data.get(qs!(a)).get_untracked().unwrap().as_input().unwrap().raw(), "test");
562 }
563
564 #[test]
565 fn test_input_group() {
566 let _ = leptos::create_runtime();
567
568 let form_data = FormData::new();
569 let group = BaseGroupContext::new();
570 provide_context(form_data);
571 provide_context(group.to_group_context());
572
573 let group = GroupContext::new(QueryStringPart::from("a"));
574 let input = InputContext::new_with_context(QueryStringPart::from("b"), group);
575 input.set_raw_value("test");
576
577 assert_eq!(form_data.get(qs!(a[b])).get_untracked().unwrap().as_input().unwrap().raw(), "test");
578 }
579
580 #[test]
581 fn test_input_group_modify() {
582 let _ = leptos::create_runtime();
583
584 let form_data = FormData::new();
585 let group = BaseGroupContext::new();
586 provide_context(form_data);
587 provide_context(group.to_group_context());
588
589 let group = GroupContext::new(QueryStringPart::from("a"));
590 let input = InputContext::new_with_context(QueryStringPart::from("b"), group);
591 input.set_raw_value("test");
592
593 assert_eq!(form_data.get(qs!(a[b])).get_untracked().unwrap().as_input().unwrap().raw(), "test");
594
595 input.set_raw_value("test2");
596 assert_eq!(form_data.get(qs!(a[b])).get_untracked().unwrap().as_input().unwrap().raw(), "test2");
597 }
598
599 #[test]
600 fn test_input_group_validate() {
601 let _ = leptos::create_runtime();
602
603 let form_data = FormData::new();
604 let group = BaseGroupContext::new();
605 provide_context(form_data);
606 provide_context(group.to_group_context());
607
608 let group = GroupContext::new(QueryStringPart::from("a"));
609 let input1 = InputContext::new_with_context(QueryStringPart::from("b"), group);
610 let input2 = InputContext::new_with_context(QueryStringPart::from("c"), group);
611 group.validate();
612 let input3 = InputContext::new_with_context(QueryStringPart::from("d"), group);
613
614 assert_eq!(input1.validate_signal().get_untracked(), true);
615 assert_eq!(input2.validate_signal().get_untracked(), true);
616 assert_eq!(input3.validate_signal().get_untracked(), false);
617 }
618
619 #[test]
620 fn test_input_group_disable() {
621 let _ = leptos::create_runtime();
622
623 let form_data = FormData::new();
624 let group = BaseGroupContext::new();
625 provide_context(form_data);
626 provide_context(group.to_group_context());
627
628 let group = GroupContext::new(QueryStringPart::from("a"));
629 let input1 = InputContext::new_with_context(QueryStringPart::from("b"), group);
630 let input2 = InputContext::new_with_context(QueryStringPart::from("c"), group);
631 group.set_disabled(true);
632 let input3 = InputContext::new_with_context(QueryStringPart::from("d"), group);
633
634 assert_eq!(input1.disabled().get_untracked(), true);
635 assert_eq!(input2.disabled().get_untracked(), true);
636 assert_eq!(input3.disabled().get_untracked(), true);
637 }
638
639 #[test]
640 fn test_input_group_error() {
641 let _ = leptos::create_runtime();
642
643 let form_data = FormData::new();
644 let group = BaseGroupContext::new();
645 provide_context(form_data);
646 provide_context(group.to_group_context());
647
648 let group = GroupContext::new(QueryStringPart::from("a"));
649 let _input1 = InputContext::new_with_context(QueryStringPart::from("b"), group);
650 assert_eq!(group.error().get_untracked(), false);
651
652 let input2 = InputContext::new_with_context(QueryStringPart::from("c"), group);
653 input2.set_error(true);
654 let _input3 = InputContext::new_with_context(QueryStringPart::from("d"), group);
655
656 assert_eq!(group.error().get_untracked(), true);
657 }
658}