1+ from typing import Any
12import reflex as rx
23
34from reflex_local_auth import LocalAuthState
45
56from .. import routes , style
67from ..components import field_view , navbar
7- from ..models import FieldType , FieldValue , Form , Response
8+ from ..models import Field , FieldType , FieldValue , Form , Response
89
910
1011Missing = object ()
1314class FormEntryState (rx .State ):
1415 form : Form = Form ()
1516 client_token : str = rx .Cookie ("" )
17+ missing_fields : dict [str , bool ] = {}
1618
1719 def _ensure_client_token (self ):
1820 if self .client_token == "" :
1921 self .client_token = self .router .session .client_token
2022 return self .client_token
2123
2224 def load_form (self ):
25+ self .missing_fields = {}
2326 if self .form_id != "" :
2427 self .load_form_by_id (self .form_id )
2528 else :
@@ -29,27 +32,39 @@ def load_form_by_id(self, id_: int):
2932 with rx .session () as session :
3033 self .form = session .get (Form , id_ )
3134
32- def handle_submit (self , form_data ):
35+ def handle_submit (self , form_data : dict [str , Any ]):
36+ self .missing_fields = {}
3337 response = Response (
3438 client_token = self ._ensure_client_token (), form_id = self .form .id
3539 )
3640 for field in self .form .fields :
3741 value = form_data .get (field .name , Missing )
38- if value is not Missing :
42+ if value and value is not Missing :
3943 response .field_values .append (
4044 FieldValue (
4145 field_id = field .id ,
4246 value = value ,
4347 )
4448 )
4549 elif field .type_ == FieldType .checkbox :
50+ field_values = []
4651 for option in field .options :
4752 key = f"{ field .name } ___{ option .value or option .label or option .id } "
4853 value = form_data .get (key , Missing )
4954 if value is not Missing :
50- response . field_values .append (
55+ field_values .append (
5156 FieldValue (field_id = field .id , value = form_data [key ])
5257 )
58+ if field .required and not field_values :
59+ self .missing_fields [field .prompt or field .name ] = True
60+ elif field .required :
61+ self .missing_fields [field .prompt or field .name ] = True
62+ if self .missing_fields :
63+ if len (self .missing_fields ) == 1 :
64+ return rx .toast (
65+ f"Required field '{ tuple (self .missing_fields )[0 ]} ' is missing a response"
66+ )
67+ return rx .toast ("Multiple required fields are missing a response" )
5368 with rx .session () as session :
5469 session .add (response )
5570 session .commit ()
@@ -66,6 +81,25 @@ def authenticated_navbar(title_suffix: str | None = None):
6681 )
6782
6883
84+ def validated_field_view (field : Field ) -> rx .Component :
85+ return field_view (
86+ field ,
87+ rx .form .message (
88+ "This field is required." ,
89+ match = "valueMissing" ,
90+ force_match = FormEntryState .missing_fields [field .name ],
91+ color = rx .color ("tomato" , 10 ),
92+ ),
93+ card_props = {
94+ "--base-card-surface-box-shadow" : rx .cond (
95+ FormEntryState .missing_fields [field .name ],
96+ f"0 0 0 1px { rx .color ('tomato' , 10 )} " ,
97+ "inherit" ,
98+ ),
99+ },
100+ )
101+
102+
69103def form_entry_page ():
70104 return style .layout (
71105 authenticated_navbar (title_suffix = f"Preview { FormEntryState .form .id } " ),
@@ -74,7 +108,7 @@ def form_entry_page():
74108 rx .center (rx .heading (FormEntryState .form .name )),
75109 rx .foreach (
76110 FormEntryState .form .fields ,
77- field_view ,
111+ validated_field_view ,
78112 ),
79113 rx .button ("Submit" , type = "submit" ),
80114 ),
0 commit comments