pub struct FormState { /* private fields */ }Expand description
Form state managing multiple fields.
Implementations§
Source§impl FormState
impl FormState
Sourcepub fn new() -> Self
pub fn new() -> Self
Create a new empty form state.
Examples found in repository?
examples/22_forms.rs (line 32)
21 fn render(&self, cx: Scope) -> View {
22 let show_help = state!(cx, || false);
23
24 // F1 toggles help
25 cx.use_command(
26 KeyBinding::key(KeyCode::F(1)),
27 with!(show_help => move || show_help.update(|v| *v = !*v)),
28 );
29
30 // Create form state with validated fields
31 let form = state!(cx, || {
32 FormState::new()
33 .field(
34 FieldBuilder::new("email")
35 .required()
36 .email()
37 .error_message("Please enter a valid email address")
38 .build(),
39 )
40 .field(
41 FieldBuilder::new("password")
42 .required()
43 .min_length(8)
44 .error_message("Password must be at least 8 characters")
45 .build(),
46 )
47 .field(
48 FieldBuilder::new("username")
49 .required()
50 .min_length(3)
51 .max_length(20)
52 .custom(|v| {
53 if v.contains(' ') {
54 Some("Username cannot contain spaces".to_string())
55 } else if !v.chars().all(|c| c.is_alphanumeric() || c == '_') {
56 Some(
57 "Username can only contain letters, numbers, and underscores"
58 .to_string(),
59 )
60 } else {
61 None
62 }
63 })
64 .build(),
65 )
66 .field(
67 FieldBuilder::new("age")
68 .integer()
69 .custom(|v| {
70 if v.is_empty() {
71 return None; // Optional field
72 }
73 match v.parse::<i32>() {
74 Ok(age) if age < 0 => Some("Age cannot be negative".to_string()),
75 Ok(age) if age > 150 => {
76 Some("Please enter a valid age".to_string())
77 }
78 Ok(_) => None,
79 Err(_) => Some("Please enter a valid number".to_string()),
80 }
81 })
82 .build(),
83 )
84 });
85
86 let submit_message = state!(cx, String::new);
87
88 // Get current field values and errors
89 let email = form.get().get_value("email");
90 let password = form.get().get_value("password");
91 let username = form.get().get_value("username");
92 let age = form.get().get_value("age");
93
94 let email_error = form.get().get_error("email");
95 let password_error = form.get().get_error("password");
96 let username_error = form.get().get_error("username");
97 let age_error = form.get().get_error("age");
98
99 // Handlers
100 let on_submit = with!(form, submit_message => move || {
101 if form.get().validate() {
102 let values = form.get().values();
103 submit_message.set(format!(
104 "Form submitted! Email: {}, Username: {}",
105 values.get("email").unwrap_or(&String::new()),
106 values.get("username").unwrap_or(&String::new())
107 ));
108 } else {
109 submit_message.set("Please fix the errors above".to_string());
110 }
111 });
112
113 let on_reset = with!(form, submit_message => move || {
114 form.get().reset();
115 submit_message.set(String::new());
116 });
117
118 View::vstack()
119 .spacing(1)
120 .child(
121 // Header
122 View::boxed()
123 .border(true)
124 .padding(1)
125 .child(
126 View::vstack()
127 .child(View::styled_text("Form Validation Demo").bold().build())
128 .child(
129 View::styled_text("Tab between fields, type to enter values")
130 .dim()
131 .build(),
132 )
133 .build(),
134 )
135 .build(),
136 )
137 .child(
138 // Form
139 View::boxed()
140 .flex(1)
141 .border(true)
142 .padding(1)
143 .child(
144 View::form()
145 .spacing(1)
146 .child(
147 View::form_field("email")
148 .label("Email Address *")
149 .value(email.clone())
150 .placeholder("you@example.com")
151 .error(email_error)
152 .on_change(with!(form => move |v: String| {
153 form.get().set_value("email", v);
154 }))
155 .on_blur(with!(form => move || {
156 form.get().touch("email");
157 }))
158 .build(),
159 )
160 .child(
161 View::form_field("username")
162 .label("Username *")
163 .value(username.clone())
164 .placeholder("johndoe")
165 .error(username_error)
166 .on_change(with!(form => move |v: String| {
167 form.get().set_value("username", v);
168 }))
169 .on_blur(with!(form => move || {
170 form.get().touch("username");
171 }))
172 .build(),
173 )
174 .child(
175 View::form_field("password")
176 .label("Password * (min 8 chars)")
177 .value(password.clone())
178 .placeholder("Enter password")
179 .password(true)
180 .error(password_error)
181 .on_change(with!(form => move |v: String| {
182 form.get().set_value("password", v);
183 }))
184 .on_blur(with!(form => move || {
185 form.get().touch("password");
186 }))
187 .build(),
188 )
189 .child(
190 View::form_field("age")
191 .label("Age (optional)")
192 .value(age.clone())
193 .placeholder("25")
194 .error(age_error)
195 .on_change(with!(form => move |v: String| {
196 form.get().set_value("age", v);
197 }))
198 .on_blur(with!(form => move || {
199 form.get().touch("age");
200 }))
201 .build(),
202 )
203 .build(),
204 )
205 .build(),
206 )
207 .child(
208 // Actions
209 View::hstack()
210 .spacing(2)
211 .child(View::button().label("Submit").on_press(on_submit).build())
212 .child(View::button().label("Reset").on_press(on_reset).build())
213 .build(),
214 )
215 .child(
216 // Status
217 View::boxed()
218 .border(true)
219 .padding(1)
220 .child(
221 View::vstack()
222 .child(View::text(if submit_message.get().is_empty() {
223 "Fill in the form and click Submit".to_string()
224 } else {
225 submit_message.get()
226 }))
227 .child(
228 View::styled_text(format!(
229 "Form valid: {}",
230 if form.get().is_valid() { "Yes" } else { "No" }
231 ))
232 .color(if form.get().is_valid() {
233 Color::Green
234 } else {
235 Color::Red
236 })
237 .build(),
238 )
239 .build(),
240 )
241 .build(),
242 )
243 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
244 .child(
245 View::modal()
246 .visible(show_help.get())
247 .title("Example 22: Forms")
248 .on_dismiss(with!(show_help => move || show_help.set(false)))
249 .child(
250 View::vstack()
251 .child(View::styled_text("What you're seeing").bold().build())
252 .child(View::text("• Declarative form validation"))
253 .child(View::text("• Required, email, length validators"))
254 .child(View::text("• Custom validation functions"))
255 .child(View::gap(1))
256 .child(View::styled_text("Key concepts").bold().build())
257 .child(View::text("• FormState manages all fields"))
258 .child(View::text("• FieldBuilder defines validation"))
259 .child(View::text("• View::form_field() renders inputs"))
260 .child(View::text("• on_blur triggers validation"))
261 .child(View::gap(1))
262 .child(View::styled_text("Try this").bold().build())
263 .child(View::text("• Enter invalid email, see error"))
264 .child(View::text("• Try short password (<8 chars)"))
265 .child(View::text("• Username with spaces shows error"))
266 .child(View::gap(1))
267 .child(View::styled_text("Next up").bold().build())
268 .child(View::text("→ 23_modal: modal dialogs"))
269 .child(View::gap(1))
270 .child(View::styled_text("Press Escape to close").dim().build())
271 .build(),
272 )
273 .build(),
274 )
275 .build()
276 }Sourcepub fn field(self, field: FormField) -> Self
pub fn field(self, field: FormField) -> Self
Add a field to the form.
Examples found in repository?
examples/22_forms.rs (lines 33-39)
21 fn render(&self, cx: Scope) -> View {
22 let show_help = state!(cx, || false);
23
24 // F1 toggles help
25 cx.use_command(
26 KeyBinding::key(KeyCode::F(1)),
27 with!(show_help => move || show_help.update(|v| *v = !*v)),
28 );
29
30 // Create form state with validated fields
31 let form = state!(cx, || {
32 FormState::new()
33 .field(
34 FieldBuilder::new("email")
35 .required()
36 .email()
37 .error_message("Please enter a valid email address")
38 .build(),
39 )
40 .field(
41 FieldBuilder::new("password")
42 .required()
43 .min_length(8)
44 .error_message("Password must be at least 8 characters")
45 .build(),
46 )
47 .field(
48 FieldBuilder::new("username")
49 .required()
50 .min_length(3)
51 .max_length(20)
52 .custom(|v| {
53 if v.contains(' ') {
54 Some("Username cannot contain spaces".to_string())
55 } else if !v.chars().all(|c| c.is_alphanumeric() || c == '_') {
56 Some(
57 "Username can only contain letters, numbers, and underscores"
58 .to_string(),
59 )
60 } else {
61 None
62 }
63 })
64 .build(),
65 )
66 .field(
67 FieldBuilder::new("age")
68 .integer()
69 .custom(|v| {
70 if v.is_empty() {
71 return None; // Optional field
72 }
73 match v.parse::<i32>() {
74 Ok(age) if age < 0 => Some("Age cannot be negative".to_string()),
75 Ok(age) if age > 150 => {
76 Some("Please enter a valid age".to_string())
77 }
78 Ok(_) => None,
79 Err(_) => Some("Please enter a valid number".to_string()),
80 }
81 })
82 .build(),
83 )
84 });
85
86 let submit_message = state!(cx, String::new);
87
88 // Get current field values and errors
89 let email = form.get().get_value("email");
90 let password = form.get().get_value("password");
91 let username = form.get().get_value("username");
92 let age = form.get().get_value("age");
93
94 let email_error = form.get().get_error("email");
95 let password_error = form.get().get_error("password");
96 let username_error = form.get().get_error("username");
97 let age_error = form.get().get_error("age");
98
99 // Handlers
100 let on_submit = with!(form, submit_message => move || {
101 if form.get().validate() {
102 let values = form.get().values();
103 submit_message.set(format!(
104 "Form submitted! Email: {}, Username: {}",
105 values.get("email").unwrap_or(&String::new()),
106 values.get("username").unwrap_or(&String::new())
107 ));
108 } else {
109 submit_message.set("Please fix the errors above".to_string());
110 }
111 });
112
113 let on_reset = with!(form, submit_message => move || {
114 form.get().reset();
115 submit_message.set(String::new());
116 });
117
118 View::vstack()
119 .spacing(1)
120 .child(
121 // Header
122 View::boxed()
123 .border(true)
124 .padding(1)
125 .child(
126 View::vstack()
127 .child(View::styled_text("Form Validation Demo").bold().build())
128 .child(
129 View::styled_text("Tab between fields, type to enter values")
130 .dim()
131 .build(),
132 )
133 .build(),
134 )
135 .build(),
136 )
137 .child(
138 // Form
139 View::boxed()
140 .flex(1)
141 .border(true)
142 .padding(1)
143 .child(
144 View::form()
145 .spacing(1)
146 .child(
147 View::form_field("email")
148 .label("Email Address *")
149 .value(email.clone())
150 .placeholder("you@example.com")
151 .error(email_error)
152 .on_change(with!(form => move |v: String| {
153 form.get().set_value("email", v);
154 }))
155 .on_blur(with!(form => move || {
156 form.get().touch("email");
157 }))
158 .build(),
159 )
160 .child(
161 View::form_field("username")
162 .label("Username *")
163 .value(username.clone())
164 .placeholder("johndoe")
165 .error(username_error)
166 .on_change(with!(form => move |v: String| {
167 form.get().set_value("username", v);
168 }))
169 .on_blur(with!(form => move || {
170 form.get().touch("username");
171 }))
172 .build(),
173 )
174 .child(
175 View::form_field("password")
176 .label("Password * (min 8 chars)")
177 .value(password.clone())
178 .placeholder("Enter password")
179 .password(true)
180 .error(password_error)
181 .on_change(with!(form => move |v: String| {
182 form.get().set_value("password", v);
183 }))
184 .on_blur(with!(form => move || {
185 form.get().touch("password");
186 }))
187 .build(),
188 )
189 .child(
190 View::form_field("age")
191 .label("Age (optional)")
192 .value(age.clone())
193 .placeholder("25")
194 .error(age_error)
195 .on_change(with!(form => move |v: String| {
196 form.get().set_value("age", v);
197 }))
198 .on_blur(with!(form => move || {
199 form.get().touch("age");
200 }))
201 .build(),
202 )
203 .build(),
204 )
205 .build(),
206 )
207 .child(
208 // Actions
209 View::hstack()
210 .spacing(2)
211 .child(View::button().label("Submit").on_press(on_submit).build())
212 .child(View::button().label("Reset").on_press(on_reset).build())
213 .build(),
214 )
215 .child(
216 // Status
217 View::boxed()
218 .border(true)
219 .padding(1)
220 .child(
221 View::vstack()
222 .child(View::text(if submit_message.get().is_empty() {
223 "Fill in the form and click Submit".to_string()
224 } else {
225 submit_message.get()
226 }))
227 .child(
228 View::styled_text(format!(
229 "Form valid: {}",
230 if form.get().is_valid() { "Yes" } else { "No" }
231 ))
232 .color(if form.get().is_valid() {
233 Color::Green
234 } else {
235 Color::Red
236 })
237 .build(),
238 )
239 .build(),
240 )
241 .build(),
242 )
243 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
244 .child(
245 View::modal()
246 .visible(show_help.get())
247 .title("Example 22: Forms")
248 .on_dismiss(with!(show_help => move || show_help.set(false)))
249 .child(
250 View::vstack()
251 .child(View::styled_text("What you're seeing").bold().build())
252 .child(View::text("• Declarative form validation"))
253 .child(View::text("• Required, email, length validators"))
254 .child(View::text("• Custom validation functions"))
255 .child(View::gap(1))
256 .child(View::styled_text("Key concepts").bold().build())
257 .child(View::text("• FormState manages all fields"))
258 .child(View::text("• FieldBuilder defines validation"))
259 .child(View::text("• View::form_field() renders inputs"))
260 .child(View::text("• on_blur triggers validation"))
261 .child(View::gap(1))
262 .child(View::styled_text("Try this").bold().build())
263 .child(View::text("• Enter invalid email, see error"))
264 .child(View::text("• Try short password (<8 chars)"))
265 .child(View::text("• Username with spaces shows error"))
266 .child(View::gap(1))
267 .child(View::styled_text("Next up").bold().build())
268 .child(View::text("→ 23_modal: modal dialogs"))
269 .child(View::gap(1))
270 .child(View::styled_text("Press Escape to close").dim().build())
271 .build(),
272 )
273 .build(),
274 )
275 .build()
276 }Sourcepub fn get_value(&self, name: &str) -> String
pub fn get_value(&self, name: &str) -> String
Get the value of a field.
Examples found in repository?
examples/22_forms.rs (line 89)
21 fn render(&self, cx: Scope) -> View {
22 let show_help = state!(cx, || false);
23
24 // F1 toggles help
25 cx.use_command(
26 KeyBinding::key(KeyCode::F(1)),
27 with!(show_help => move || show_help.update(|v| *v = !*v)),
28 );
29
30 // Create form state with validated fields
31 let form = state!(cx, || {
32 FormState::new()
33 .field(
34 FieldBuilder::new("email")
35 .required()
36 .email()
37 .error_message("Please enter a valid email address")
38 .build(),
39 )
40 .field(
41 FieldBuilder::new("password")
42 .required()
43 .min_length(8)
44 .error_message("Password must be at least 8 characters")
45 .build(),
46 )
47 .field(
48 FieldBuilder::new("username")
49 .required()
50 .min_length(3)
51 .max_length(20)
52 .custom(|v| {
53 if v.contains(' ') {
54 Some("Username cannot contain spaces".to_string())
55 } else if !v.chars().all(|c| c.is_alphanumeric() || c == '_') {
56 Some(
57 "Username can only contain letters, numbers, and underscores"
58 .to_string(),
59 )
60 } else {
61 None
62 }
63 })
64 .build(),
65 )
66 .field(
67 FieldBuilder::new("age")
68 .integer()
69 .custom(|v| {
70 if v.is_empty() {
71 return None; // Optional field
72 }
73 match v.parse::<i32>() {
74 Ok(age) if age < 0 => Some("Age cannot be negative".to_string()),
75 Ok(age) if age > 150 => {
76 Some("Please enter a valid age".to_string())
77 }
78 Ok(_) => None,
79 Err(_) => Some("Please enter a valid number".to_string()),
80 }
81 })
82 .build(),
83 )
84 });
85
86 let submit_message = state!(cx, String::new);
87
88 // Get current field values and errors
89 let email = form.get().get_value("email");
90 let password = form.get().get_value("password");
91 let username = form.get().get_value("username");
92 let age = form.get().get_value("age");
93
94 let email_error = form.get().get_error("email");
95 let password_error = form.get().get_error("password");
96 let username_error = form.get().get_error("username");
97 let age_error = form.get().get_error("age");
98
99 // Handlers
100 let on_submit = with!(form, submit_message => move || {
101 if form.get().validate() {
102 let values = form.get().values();
103 submit_message.set(format!(
104 "Form submitted! Email: {}, Username: {}",
105 values.get("email").unwrap_or(&String::new()),
106 values.get("username").unwrap_or(&String::new())
107 ));
108 } else {
109 submit_message.set("Please fix the errors above".to_string());
110 }
111 });
112
113 let on_reset = with!(form, submit_message => move || {
114 form.get().reset();
115 submit_message.set(String::new());
116 });
117
118 View::vstack()
119 .spacing(1)
120 .child(
121 // Header
122 View::boxed()
123 .border(true)
124 .padding(1)
125 .child(
126 View::vstack()
127 .child(View::styled_text("Form Validation Demo").bold().build())
128 .child(
129 View::styled_text("Tab between fields, type to enter values")
130 .dim()
131 .build(),
132 )
133 .build(),
134 )
135 .build(),
136 )
137 .child(
138 // Form
139 View::boxed()
140 .flex(1)
141 .border(true)
142 .padding(1)
143 .child(
144 View::form()
145 .spacing(1)
146 .child(
147 View::form_field("email")
148 .label("Email Address *")
149 .value(email.clone())
150 .placeholder("you@example.com")
151 .error(email_error)
152 .on_change(with!(form => move |v: String| {
153 form.get().set_value("email", v);
154 }))
155 .on_blur(with!(form => move || {
156 form.get().touch("email");
157 }))
158 .build(),
159 )
160 .child(
161 View::form_field("username")
162 .label("Username *")
163 .value(username.clone())
164 .placeholder("johndoe")
165 .error(username_error)
166 .on_change(with!(form => move |v: String| {
167 form.get().set_value("username", v);
168 }))
169 .on_blur(with!(form => move || {
170 form.get().touch("username");
171 }))
172 .build(),
173 )
174 .child(
175 View::form_field("password")
176 .label("Password * (min 8 chars)")
177 .value(password.clone())
178 .placeholder("Enter password")
179 .password(true)
180 .error(password_error)
181 .on_change(with!(form => move |v: String| {
182 form.get().set_value("password", v);
183 }))
184 .on_blur(with!(form => move || {
185 form.get().touch("password");
186 }))
187 .build(),
188 )
189 .child(
190 View::form_field("age")
191 .label("Age (optional)")
192 .value(age.clone())
193 .placeholder("25")
194 .error(age_error)
195 .on_change(with!(form => move |v: String| {
196 form.get().set_value("age", v);
197 }))
198 .on_blur(with!(form => move || {
199 form.get().touch("age");
200 }))
201 .build(),
202 )
203 .build(),
204 )
205 .build(),
206 )
207 .child(
208 // Actions
209 View::hstack()
210 .spacing(2)
211 .child(View::button().label("Submit").on_press(on_submit).build())
212 .child(View::button().label("Reset").on_press(on_reset).build())
213 .build(),
214 )
215 .child(
216 // Status
217 View::boxed()
218 .border(true)
219 .padding(1)
220 .child(
221 View::vstack()
222 .child(View::text(if submit_message.get().is_empty() {
223 "Fill in the form and click Submit".to_string()
224 } else {
225 submit_message.get()
226 }))
227 .child(
228 View::styled_text(format!(
229 "Form valid: {}",
230 if form.get().is_valid() { "Yes" } else { "No" }
231 ))
232 .color(if form.get().is_valid() {
233 Color::Green
234 } else {
235 Color::Red
236 })
237 .build(),
238 )
239 .build(),
240 )
241 .build(),
242 )
243 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
244 .child(
245 View::modal()
246 .visible(show_help.get())
247 .title("Example 22: Forms")
248 .on_dismiss(with!(show_help => move || show_help.set(false)))
249 .child(
250 View::vstack()
251 .child(View::styled_text("What you're seeing").bold().build())
252 .child(View::text("• Declarative form validation"))
253 .child(View::text("• Required, email, length validators"))
254 .child(View::text("• Custom validation functions"))
255 .child(View::gap(1))
256 .child(View::styled_text("Key concepts").bold().build())
257 .child(View::text("• FormState manages all fields"))
258 .child(View::text("• FieldBuilder defines validation"))
259 .child(View::text("• View::form_field() renders inputs"))
260 .child(View::text("• on_blur triggers validation"))
261 .child(View::gap(1))
262 .child(View::styled_text("Try this").bold().build())
263 .child(View::text("• Enter invalid email, see error"))
264 .child(View::text("• Try short password (<8 chars)"))
265 .child(View::text("• Username with spaces shows error"))
266 .child(View::gap(1))
267 .child(View::styled_text("Next up").bold().build())
268 .child(View::text("→ 23_modal: modal dialogs"))
269 .child(View::gap(1))
270 .child(View::styled_text("Press Escape to close").dim().build())
271 .build(),
272 )
273 .build(),
274 )
275 .build()
276 }Sourcepub fn set_value(&self, name: &str, value: String)
pub fn set_value(&self, name: &str, value: String)
Set the value of a field.
Examples found in repository?
examples/22_forms.rs (line 153)
21 fn render(&self, cx: Scope) -> View {
22 let show_help = state!(cx, || false);
23
24 // F1 toggles help
25 cx.use_command(
26 KeyBinding::key(KeyCode::F(1)),
27 with!(show_help => move || show_help.update(|v| *v = !*v)),
28 );
29
30 // Create form state with validated fields
31 let form = state!(cx, || {
32 FormState::new()
33 .field(
34 FieldBuilder::new("email")
35 .required()
36 .email()
37 .error_message("Please enter a valid email address")
38 .build(),
39 )
40 .field(
41 FieldBuilder::new("password")
42 .required()
43 .min_length(8)
44 .error_message("Password must be at least 8 characters")
45 .build(),
46 )
47 .field(
48 FieldBuilder::new("username")
49 .required()
50 .min_length(3)
51 .max_length(20)
52 .custom(|v| {
53 if v.contains(' ') {
54 Some("Username cannot contain spaces".to_string())
55 } else if !v.chars().all(|c| c.is_alphanumeric() || c == '_') {
56 Some(
57 "Username can only contain letters, numbers, and underscores"
58 .to_string(),
59 )
60 } else {
61 None
62 }
63 })
64 .build(),
65 )
66 .field(
67 FieldBuilder::new("age")
68 .integer()
69 .custom(|v| {
70 if v.is_empty() {
71 return None; // Optional field
72 }
73 match v.parse::<i32>() {
74 Ok(age) if age < 0 => Some("Age cannot be negative".to_string()),
75 Ok(age) if age > 150 => {
76 Some("Please enter a valid age".to_string())
77 }
78 Ok(_) => None,
79 Err(_) => Some("Please enter a valid number".to_string()),
80 }
81 })
82 .build(),
83 )
84 });
85
86 let submit_message = state!(cx, String::new);
87
88 // Get current field values and errors
89 let email = form.get().get_value("email");
90 let password = form.get().get_value("password");
91 let username = form.get().get_value("username");
92 let age = form.get().get_value("age");
93
94 let email_error = form.get().get_error("email");
95 let password_error = form.get().get_error("password");
96 let username_error = form.get().get_error("username");
97 let age_error = form.get().get_error("age");
98
99 // Handlers
100 let on_submit = with!(form, submit_message => move || {
101 if form.get().validate() {
102 let values = form.get().values();
103 submit_message.set(format!(
104 "Form submitted! Email: {}, Username: {}",
105 values.get("email").unwrap_or(&String::new()),
106 values.get("username").unwrap_or(&String::new())
107 ));
108 } else {
109 submit_message.set("Please fix the errors above".to_string());
110 }
111 });
112
113 let on_reset = with!(form, submit_message => move || {
114 form.get().reset();
115 submit_message.set(String::new());
116 });
117
118 View::vstack()
119 .spacing(1)
120 .child(
121 // Header
122 View::boxed()
123 .border(true)
124 .padding(1)
125 .child(
126 View::vstack()
127 .child(View::styled_text("Form Validation Demo").bold().build())
128 .child(
129 View::styled_text("Tab between fields, type to enter values")
130 .dim()
131 .build(),
132 )
133 .build(),
134 )
135 .build(),
136 )
137 .child(
138 // Form
139 View::boxed()
140 .flex(1)
141 .border(true)
142 .padding(1)
143 .child(
144 View::form()
145 .spacing(1)
146 .child(
147 View::form_field("email")
148 .label("Email Address *")
149 .value(email.clone())
150 .placeholder("you@example.com")
151 .error(email_error)
152 .on_change(with!(form => move |v: String| {
153 form.get().set_value("email", v);
154 }))
155 .on_blur(with!(form => move || {
156 form.get().touch("email");
157 }))
158 .build(),
159 )
160 .child(
161 View::form_field("username")
162 .label("Username *")
163 .value(username.clone())
164 .placeholder("johndoe")
165 .error(username_error)
166 .on_change(with!(form => move |v: String| {
167 form.get().set_value("username", v);
168 }))
169 .on_blur(with!(form => move || {
170 form.get().touch("username");
171 }))
172 .build(),
173 )
174 .child(
175 View::form_field("password")
176 .label("Password * (min 8 chars)")
177 .value(password.clone())
178 .placeholder("Enter password")
179 .password(true)
180 .error(password_error)
181 .on_change(with!(form => move |v: String| {
182 form.get().set_value("password", v);
183 }))
184 .on_blur(with!(form => move || {
185 form.get().touch("password");
186 }))
187 .build(),
188 )
189 .child(
190 View::form_field("age")
191 .label("Age (optional)")
192 .value(age.clone())
193 .placeholder("25")
194 .error(age_error)
195 .on_change(with!(form => move |v: String| {
196 form.get().set_value("age", v);
197 }))
198 .on_blur(with!(form => move || {
199 form.get().touch("age");
200 }))
201 .build(),
202 )
203 .build(),
204 )
205 .build(),
206 )
207 .child(
208 // Actions
209 View::hstack()
210 .spacing(2)
211 .child(View::button().label("Submit").on_press(on_submit).build())
212 .child(View::button().label("Reset").on_press(on_reset).build())
213 .build(),
214 )
215 .child(
216 // Status
217 View::boxed()
218 .border(true)
219 .padding(1)
220 .child(
221 View::vstack()
222 .child(View::text(if submit_message.get().is_empty() {
223 "Fill in the form and click Submit".to_string()
224 } else {
225 submit_message.get()
226 }))
227 .child(
228 View::styled_text(format!(
229 "Form valid: {}",
230 if form.get().is_valid() { "Yes" } else { "No" }
231 ))
232 .color(if form.get().is_valid() {
233 Color::Green
234 } else {
235 Color::Red
236 })
237 .build(),
238 )
239 .build(),
240 )
241 .build(),
242 )
243 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
244 .child(
245 View::modal()
246 .visible(show_help.get())
247 .title("Example 22: Forms")
248 .on_dismiss(with!(show_help => move || show_help.set(false)))
249 .child(
250 View::vstack()
251 .child(View::styled_text("What you're seeing").bold().build())
252 .child(View::text("• Declarative form validation"))
253 .child(View::text("• Required, email, length validators"))
254 .child(View::text("• Custom validation functions"))
255 .child(View::gap(1))
256 .child(View::styled_text("Key concepts").bold().build())
257 .child(View::text("• FormState manages all fields"))
258 .child(View::text("• FieldBuilder defines validation"))
259 .child(View::text("• View::form_field() renders inputs"))
260 .child(View::text("• on_blur triggers validation"))
261 .child(View::gap(1))
262 .child(View::styled_text("Try this").bold().build())
263 .child(View::text("• Enter invalid email, see error"))
264 .child(View::text("• Try short password (<8 chars)"))
265 .child(View::text("• Username with spaces shows error"))
266 .child(View::gap(1))
267 .child(View::styled_text("Next up").bold().build())
268 .child(View::text("→ 23_modal: modal dialogs"))
269 .child(View::gap(1))
270 .child(View::styled_text("Press Escape to close").dim().build())
271 .build(),
272 )
273 .build(),
274 )
275 .build()
276 }Sourcepub fn get_error(&self, name: &str) -> Option<String>
pub fn get_error(&self, name: &str) -> Option<String>
Get the error for a field.
Examples found in repository?
examples/22_forms.rs (line 94)
21 fn render(&self, cx: Scope) -> View {
22 let show_help = state!(cx, || false);
23
24 // F1 toggles help
25 cx.use_command(
26 KeyBinding::key(KeyCode::F(1)),
27 with!(show_help => move || show_help.update(|v| *v = !*v)),
28 );
29
30 // Create form state with validated fields
31 let form = state!(cx, || {
32 FormState::new()
33 .field(
34 FieldBuilder::new("email")
35 .required()
36 .email()
37 .error_message("Please enter a valid email address")
38 .build(),
39 )
40 .field(
41 FieldBuilder::new("password")
42 .required()
43 .min_length(8)
44 .error_message("Password must be at least 8 characters")
45 .build(),
46 )
47 .field(
48 FieldBuilder::new("username")
49 .required()
50 .min_length(3)
51 .max_length(20)
52 .custom(|v| {
53 if v.contains(' ') {
54 Some("Username cannot contain spaces".to_string())
55 } else if !v.chars().all(|c| c.is_alphanumeric() || c == '_') {
56 Some(
57 "Username can only contain letters, numbers, and underscores"
58 .to_string(),
59 )
60 } else {
61 None
62 }
63 })
64 .build(),
65 )
66 .field(
67 FieldBuilder::new("age")
68 .integer()
69 .custom(|v| {
70 if v.is_empty() {
71 return None; // Optional field
72 }
73 match v.parse::<i32>() {
74 Ok(age) if age < 0 => Some("Age cannot be negative".to_string()),
75 Ok(age) if age > 150 => {
76 Some("Please enter a valid age".to_string())
77 }
78 Ok(_) => None,
79 Err(_) => Some("Please enter a valid number".to_string()),
80 }
81 })
82 .build(),
83 )
84 });
85
86 let submit_message = state!(cx, String::new);
87
88 // Get current field values and errors
89 let email = form.get().get_value("email");
90 let password = form.get().get_value("password");
91 let username = form.get().get_value("username");
92 let age = form.get().get_value("age");
93
94 let email_error = form.get().get_error("email");
95 let password_error = form.get().get_error("password");
96 let username_error = form.get().get_error("username");
97 let age_error = form.get().get_error("age");
98
99 // Handlers
100 let on_submit = with!(form, submit_message => move || {
101 if form.get().validate() {
102 let values = form.get().values();
103 submit_message.set(format!(
104 "Form submitted! Email: {}, Username: {}",
105 values.get("email").unwrap_or(&String::new()),
106 values.get("username").unwrap_or(&String::new())
107 ));
108 } else {
109 submit_message.set("Please fix the errors above".to_string());
110 }
111 });
112
113 let on_reset = with!(form, submit_message => move || {
114 form.get().reset();
115 submit_message.set(String::new());
116 });
117
118 View::vstack()
119 .spacing(1)
120 .child(
121 // Header
122 View::boxed()
123 .border(true)
124 .padding(1)
125 .child(
126 View::vstack()
127 .child(View::styled_text("Form Validation Demo").bold().build())
128 .child(
129 View::styled_text("Tab between fields, type to enter values")
130 .dim()
131 .build(),
132 )
133 .build(),
134 )
135 .build(),
136 )
137 .child(
138 // Form
139 View::boxed()
140 .flex(1)
141 .border(true)
142 .padding(1)
143 .child(
144 View::form()
145 .spacing(1)
146 .child(
147 View::form_field("email")
148 .label("Email Address *")
149 .value(email.clone())
150 .placeholder("you@example.com")
151 .error(email_error)
152 .on_change(with!(form => move |v: String| {
153 form.get().set_value("email", v);
154 }))
155 .on_blur(with!(form => move || {
156 form.get().touch("email");
157 }))
158 .build(),
159 )
160 .child(
161 View::form_field("username")
162 .label("Username *")
163 .value(username.clone())
164 .placeholder("johndoe")
165 .error(username_error)
166 .on_change(with!(form => move |v: String| {
167 form.get().set_value("username", v);
168 }))
169 .on_blur(with!(form => move || {
170 form.get().touch("username");
171 }))
172 .build(),
173 )
174 .child(
175 View::form_field("password")
176 .label("Password * (min 8 chars)")
177 .value(password.clone())
178 .placeholder("Enter password")
179 .password(true)
180 .error(password_error)
181 .on_change(with!(form => move |v: String| {
182 form.get().set_value("password", v);
183 }))
184 .on_blur(with!(form => move || {
185 form.get().touch("password");
186 }))
187 .build(),
188 )
189 .child(
190 View::form_field("age")
191 .label("Age (optional)")
192 .value(age.clone())
193 .placeholder("25")
194 .error(age_error)
195 .on_change(with!(form => move |v: String| {
196 form.get().set_value("age", v);
197 }))
198 .on_blur(with!(form => move || {
199 form.get().touch("age");
200 }))
201 .build(),
202 )
203 .build(),
204 )
205 .build(),
206 )
207 .child(
208 // Actions
209 View::hstack()
210 .spacing(2)
211 .child(View::button().label("Submit").on_press(on_submit).build())
212 .child(View::button().label("Reset").on_press(on_reset).build())
213 .build(),
214 )
215 .child(
216 // Status
217 View::boxed()
218 .border(true)
219 .padding(1)
220 .child(
221 View::vstack()
222 .child(View::text(if submit_message.get().is_empty() {
223 "Fill in the form and click Submit".to_string()
224 } else {
225 submit_message.get()
226 }))
227 .child(
228 View::styled_text(format!(
229 "Form valid: {}",
230 if form.get().is_valid() { "Yes" } else { "No" }
231 ))
232 .color(if form.get().is_valid() {
233 Color::Green
234 } else {
235 Color::Red
236 })
237 .build(),
238 )
239 .build(),
240 )
241 .build(),
242 )
243 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
244 .child(
245 View::modal()
246 .visible(show_help.get())
247 .title("Example 22: Forms")
248 .on_dismiss(with!(show_help => move || show_help.set(false)))
249 .child(
250 View::vstack()
251 .child(View::styled_text("What you're seeing").bold().build())
252 .child(View::text("• Declarative form validation"))
253 .child(View::text("• Required, email, length validators"))
254 .child(View::text("• Custom validation functions"))
255 .child(View::gap(1))
256 .child(View::styled_text("Key concepts").bold().build())
257 .child(View::text("• FormState manages all fields"))
258 .child(View::text("• FieldBuilder defines validation"))
259 .child(View::text("• View::form_field() renders inputs"))
260 .child(View::text("• on_blur triggers validation"))
261 .child(View::gap(1))
262 .child(View::styled_text("Try this").bold().build())
263 .child(View::text("• Enter invalid email, see error"))
264 .child(View::text("• Try short password (<8 chars)"))
265 .child(View::text("• Username with spaces shows error"))
266 .child(View::gap(1))
267 .child(View::styled_text("Next up").bold().build())
268 .child(View::text("→ 23_modal: modal dialogs"))
269 .child(View::gap(1))
270 .child(View::styled_text("Press Escape to close").dim().build())
271 .build(),
272 )
273 .build(),
274 )
275 .build()
276 }Sourcepub fn is_touched(&self, name: &str) -> bool
pub fn is_touched(&self, name: &str) -> bool
Check if a field has been touched.
Sourcepub fn touch(&self, name: &str)
pub fn touch(&self, name: &str)
Mark a field as touched.
Examples found in repository?
examples/22_forms.rs (line 156)
21 fn render(&self, cx: Scope) -> View {
22 let show_help = state!(cx, || false);
23
24 // F1 toggles help
25 cx.use_command(
26 KeyBinding::key(KeyCode::F(1)),
27 with!(show_help => move || show_help.update(|v| *v = !*v)),
28 );
29
30 // Create form state with validated fields
31 let form = state!(cx, || {
32 FormState::new()
33 .field(
34 FieldBuilder::new("email")
35 .required()
36 .email()
37 .error_message("Please enter a valid email address")
38 .build(),
39 )
40 .field(
41 FieldBuilder::new("password")
42 .required()
43 .min_length(8)
44 .error_message("Password must be at least 8 characters")
45 .build(),
46 )
47 .field(
48 FieldBuilder::new("username")
49 .required()
50 .min_length(3)
51 .max_length(20)
52 .custom(|v| {
53 if v.contains(' ') {
54 Some("Username cannot contain spaces".to_string())
55 } else if !v.chars().all(|c| c.is_alphanumeric() || c == '_') {
56 Some(
57 "Username can only contain letters, numbers, and underscores"
58 .to_string(),
59 )
60 } else {
61 None
62 }
63 })
64 .build(),
65 )
66 .field(
67 FieldBuilder::new("age")
68 .integer()
69 .custom(|v| {
70 if v.is_empty() {
71 return None; // Optional field
72 }
73 match v.parse::<i32>() {
74 Ok(age) if age < 0 => Some("Age cannot be negative".to_string()),
75 Ok(age) if age > 150 => {
76 Some("Please enter a valid age".to_string())
77 }
78 Ok(_) => None,
79 Err(_) => Some("Please enter a valid number".to_string()),
80 }
81 })
82 .build(),
83 )
84 });
85
86 let submit_message = state!(cx, String::new);
87
88 // Get current field values and errors
89 let email = form.get().get_value("email");
90 let password = form.get().get_value("password");
91 let username = form.get().get_value("username");
92 let age = form.get().get_value("age");
93
94 let email_error = form.get().get_error("email");
95 let password_error = form.get().get_error("password");
96 let username_error = form.get().get_error("username");
97 let age_error = form.get().get_error("age");
98
99 // Handlers
100 let on_submit = with!(form, submit_message => move || {
101 if form.get().validate() {
102 let values = form.get().values();
103 submit_message.set(format!(
104 "Form submitted! Email: {}, Username: {}",
105 values.get("email").unwrap_or(&String::new()),
106 values.get("username").unwrap_or(&String::new())
107 ));
108 } else {
109 submit_message.set("Please fix the errors above".to_string());
110 }
111 });
112
113 let on_reset = with!(form, submit_message => move || {
114 form.get().reset();
115 submit_message.set(String::new());
116 });
117
118 View::vstack()
119 .spacing(1)
120 .child(
121 // Header
122 View::boxed()
123 .border(true)
124 .padding(1)
125 .child(
126 View::vstack()
127 .child(View::styled_text("Form Validation Demo").bold().build())
128 .child(
129 View::styled_text("Tab between fields, type to enter values")
130 .dim()
131 .build(),
132 )
133 .build(),
134 )
135 .build(),
136 )
137 .child(
138 // Form
139 View::boxed()
140 .flex(1)
141 .border(true)
142 .padding(1)
143 .child(
144 View::form()
145 .spacing(1)
146 .child(
147 View::form_field("email")
148 .label("Email Address *")
149 .value(email.clone())
150 .placeholder("you@example.com")
151 .error(email_error)
152 .on_change(with!(form => move |v: String| {
153 form.get().set_value("email", v);
154 }))
155 .on_blur(with!(form => move || {
156 form.get().touch("email");
157 }))
158 .build(),
159 )
160 .child(
161 View::form_field("username")
162 .label("Username *")
163 .value(username.clone())
164 .placeholder("johndoe")
165 .error(username_error)
166 .on_change(with!(form => move |v: String| {
167 form.get().set_value("username", v);
168 }))
169 .on_blur(with!(form => move || {
170 form.get().touch("username");
171 }))
172 .build(),
173 )
174 .child(
175 View::form_field("password")
176 .label("Password * (min 8 chars)")
177 .value(password.clone())
178 .placeholder("Enter password")
179 .password(true)
180 .error(password_error)
181 .on_change(with!(form => move |v: String| {
182 form.get().set_value("password", v);
183 }))
184 .on_blur(with!(form => move || {
185 form.get().touch("password");
186 }))
187 .build(),
188 )
189 .child(
190 View::form_field("age")
191 .label("Age (optional)")
192 .value(age.clone())
193 .placeholder("25")
194 .error(age_error)
195 .on_change(with!(form => move |v: String| {
196 form.get().set_value("age", v);
197 }))
198 .on_blur(with!(form => move || {
199 form.get().touch("age");
200 }))
201 .build(),
202 )
203 .build(),
204 )
205 .build(),
206 )
207 .child(
208 // Actions
209 View::hstack()
210 .spacing(2)
211 .child(View::button().label("Submit").on_press(on_submit).build())
212 .child(View::button().label("Reset").on_press(on_reset).build())
213 .build(),
214 )
215 .child(
216 // Status
217 View::boxed()
218 .border(true)
219 .padding(1)
220 .child(
221 View::vstack()
222 .child(View::text(if submit_message.get().is_empty() {
223 "Fill in the form and click Submit".to_string()
224 } else {
225 submit_message.get()
226 }))
227 .child(
228 View::styled_text(format!(
229 "Form valid: {}",
230 if form.get().is_valid() { "Yes" } else { "No" }
231 ))
232 .color(if form.get().is_valid() {
233 Color::Green
234 } else {
235 Color::Red
236 })
237 .build(),
238 )
239 .build(),
240 )
241 .build(),
242 )
243 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
244 .child(
245 View::modal()
246 .visible(show_help.get())
247 .title("Example 22: Forms")
248 .on_dismiss(with!(show_help => move || show_help.set(false)))
249 .child(
250 View::vstack()
251 .child(View::styled_text("What you're seeing").bold().build())
252 .child(View::text("• Declarative form validation"))
253 .child(View::text("• Required, email, length validators"))
254 .child(View::text("• Custom validation functions"))
255 .child(View::gap(1))
256 .child(View::styled_text("Key concepts").bold().build())
257 .child(View::text("• FormState manages all fields"))
258 .child(View::text("• FieldBuilder defines validation"))
259 .child(View::text("• View::form_field() renders inputs"))
260 .child(View::text("• on_blur triggers validation"))
261 .child(View::gap(1))
262 .child(View::styled_text("Try this").bold().build())
263 .child(View::text("• Enter invalid email, see error"))
264 .child(View::text("• Try short password (<8 chars)"))
265 .child(View::text("• Username with spaces shows error"))
266 .child(View::gap(1))
267 .child(View::styled_text("Next up").bold().build())
268 .child(View::text("→ 23_modal: modal dialogs"))
269 .child(View::gap(1))
270 .child(View::styled_text("Press Escape to close").dim().build())
271 .build(),
272 )
273 .build(),
274 )
275 .build()
276 }Sourcepub fn validate(&self) -> bool
pub fn validate(&self) -> bool
Validate all fields and return whether the form is valid.
Examples found in repository?
examples/22_forms.rs (line 101)
21 fn render(&self, cx: Scope) -> View {
22 let show_help = state!(cx, || false);
23
24 // F1 toggles help
25 cx.use_command(
26 KeyBinding::key(KeyCode::F(1)),
27 with!(show_help => move || show_help.update(|v| *v = !*v)),
28 );
29
30 // Create form state with validated fields
31 let form = state!(cx, || {
32 FormState::new()
33 .field(
34 FieldBuilder::new("email")
35 .required()
36 .email()
37 .error_message("Please enter a valid email address")
38 .build(),
39 )
40 .field(
41 FieldBuilder::new("password")
42 .required()
43 .min_length(8)
44 .error_message("Password must be at least 8 characters")
45 .build(),
46 )
47 .field(
48 FieldBuilder::new("username")
49 .required()
50 .min_length(3)
51 .max_length(20)
52 .custom(|v| {
53 if v.contains(' ') {
54 Some("Username cannot contain spaces".to_string())
55 } else if !v.chars().all(|c| c.is_alphanumeric() || c == '_') {
56 Some(
57 "Username can only contain letters, numbers, and underscores"
58 .to_string(),
59 )
60 } else {
61 None
62 }
63 })
64 .build(),
65 )
66 .field(
67 FieldBuilder::new("age")
68 .integer()
69 .custom(|v| {
70 if v.is_empty() {
71 return None; // Optional field
72 }
73 match v.parse::<i32>() {
74 Ok(age) if age < 0 => Some("Age cannot be negative".to_string()),
75 Ok(age) if age > 150 => {
76 Some("Please enter a valid age".to_string())
77 }
78 Ok(_) => None,
79 Err(_) => Some("Please enter a valid number".to_string()),
80 }
81 })
82 .build(),
83 )
84 });
85
86 let submit_message = state!(cx, String::new);
87
88 // Get current field values and errors
89 let email = form.get().get_value("email");
90 let password = form.get().get_value("password");
91 let username = form.get().get_value("username");
92 let age = form.get().get_value("age");
93
94 let email_error = form.get().get_error("email");
95 let password_error = form.get().get_error("password");
96 let username_error = form.get().get_error("username");
97 let age_error = form.get().get_error("age");
98
99 // Handlers
100 let on_submit = with!(form, submit_message => move || {
101 if form.get().validate() {
102 let values = form.get().values();
103 submit_message.set(format!(
104 "Form submitted! Email: {}, Username: {}",
105 values.get("email").unwrap_or(&String::new()),
106 values.get("username").unwrap_or(&String::new())
107 ));
108 } else {
109 submit_message.set("Please fix the errors above".to_string());
110 }
111 });
112
113 let on_reset = with!(form, submit_message => move || {
114 form.get().reset();
115 submit_message.set(String::new());
116 });
117
118 View::vstack()
119 .spacing(1)
120 .child(
121 // Header
122 View::boxed()
123 .border(true)
124 .padding(1)
125 .child(
126 View::vstack()
127 .child(View::styled_text("Form Validation Demo").bold().build())
128 .child(
129 View::styled_text("Tab between fields, type to enter values")
130 .dim()
131 .build(),
132 )
133 .build(),
134 )
135 .build(),
136 )
137 .child(
138 // Form
139 View::boxed()
140 .flex(1)
141 .border(true)
142 .padding(1)
143 .child(
144 View::form()
145 .spacing(1)
146 .child(
147 View::form_field("email")
148 .label("Email Address *")
149 .value(email.clone())
150 .placeholder("you@example.com")
151 .error(email_error)
152 .on_change(with!(form => move |v: String| {
153 form.get().set_value("email", v);
154 }))
155 .on_blur(with!(form => move || {
156 form.get().touch("email");
157 }))
158 .build(),
159 )
160 .child(
161 View::form_field("username")
162 .label("Username *")
163 .value(username.clone())
164 .placeholder("johndoe")
165 .error(username_error)
166 .on_change(with!(form => move |v: String| {
167 form.get().set_value("username", v);
168 }))
169 .on_blur(with!(form => move || {
170 form.get().touch("username");
171 }))
172 .build(),
173 )
174 .child(
175 View::form_field("password")
176 .label("Password * (min 8 chars)")
177 .value(password.clone())
178 .placeholder("Enter password")
179 .password(true)
180 .error(password_error)
181 .on_change(with!(form => move |v: String| {
182 form.get().set_value("password", v);
183 }))
184 .on_blur(with!(form => move || {
185 form.get().touch("password");
186 }))
187 .build(),
188 )
189 .child(
190 View::form_field("age")
191 .label("Age (optional)")
192 .value(age.clone())
193 .placeholder("25")
194 .error(age_error)
195 .on_change(with!(form => move |v: String| {
196 form.get().set_value("age", v);
197 }))
198 .on_blur(with!(form => move || {
199 form.get().touch("age");
200 }))
201 .build(),
202 )
203 .build(),
204 )
205 .build(),
206 )
207 .child(
208 // Actions
209 View::hstack()
210 .spacing(2)
211 .child(View::button().label("Submit").on_press(on_submit).build())
212 .child(View::button().label("Reset").on_press(on_reset).build())
213 .build(),
214 )
215 .child(
216 // Status
217 View::boxed()
218 .border(true)
219 .padding(1)
220 .child(
221 View::vstack()
222 .child(View::text(if submit_message.get().is_empty() {
223 "Fill in the form and click Submit".to_string()
224 } else {
225 submit_message.get()
226 }))
227 .child(
228 View::styled_text(format!(
229 "Form valid: {}",
230 if form.get().is_valid() { "Yes" } else { "No" }
231 ))
232 .color(if form.get().is_valid() {
233 Color::Green
234 } else {
235 Color::Red
236 })
237 .build(),
238 )
239 .build(),
240 )
241 .build(),
242 )
243 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
244 .child(
245 View::modal()
246 .visible(show_help.get())
247 .title("Example 22: Forms")
248 .on_dismiss(with!(show_help => move || show_help.set(false)))
249 .child(
250 View::vstack()
251 .child(View::styled_text("What you're seeing").bold().build())
252 .child(View::text("• Declarative form validation"))
253 .child(View::text("• Required, email, length validators"))
254 .child(View::text("• Custom validation functions"))
255 .child(View::gap(1))
256 .child(View::styled_text("Key concepts").bold().build())
257 .child(View::text("• FormState manages all fields"))
258 .child(View::text("• FieldBuilder defines validation"))
259 .child(View::text("• View::form_field() renders inputs"))
260 .child(View::text("• on_blur triggers validation"))
261 .child(View::gap(1))
262 .child(View::styled_text("Try this").bold().build())
263 .child(View::text("• Enter invalid email, see error"))
264 .child(View::text("• Try short password (<8 chars)"))
265 .child(View::text("• Username with spaces shows error"))
266 .child(View::gap(1))
267 .child(View::styled_text("Next up").bold().build())
268 .child(View::text("→ 23_modal: modal dialogs"))
269 .child(View::gap(1))
270 .child(View::styled_text("Press Escape to close").dim().build())
271 .build(),
272 )
273 .build(),
274 )
275 .build()
276 }Sourcepub fn is_valid(&self) -> bool
pub fn is_valid(&self) -> bool
Check if the entire form is valid (all fields pass validation).
Examples found in repository?
examples/22_forms.rs (line 230)
21 fn render(&self, cx: Scope) -> View {
22 let show_help = state!(cx, || false);
23
24 // F1 toggles help
25 cx.use_command(
26 KeyBinding::key(KeyCode::F(1)),
27 with!(show_help => move || show_help.update(|v| *v = !*v)),
28 );
29
30 // Create form state with validated fields
31 let form = state!(cx, || {
32 FormState::new()
33 .field(
34 FieldBuilder::new("email")
35 .required()
36 .email()
37 .error_message("Please enter a valid email address")
38 .build(),
39 )
40 .field(
41 FieldBuilder::new("password")
42 .required()
43 .min_length(8)
44 .error_message("Password must be at least 8 characters")
45 .build(),
46 )
47 .field(
48 FieldBuilder::new("username")
49 .required()
50 .min_length(3)
51 .max_length(20)
52 .custom(|v| {
53 if v.contains(' ') {
54 Some("Username cannot contain spaces".to_string())
55 } else if !v.chars().all(|c| c.is_alphanumeric() || c == '_') {
56 Some(
57 "Username can only contain letters, numbers, and underscores"
58 .to_string(),
59 )
60 } else {
61 None
62 }
63 })
64 .build(),
65 )
66 .field(
67 FieldBuilder::new("age")
68 .integer()
69 .custom(|v| {
70 if v.is_empty() {
71 return None; // Optional field
72 }
73 match v.parse::<i32>() {
74 Ok(age) if age < 0 => Some("Age cannot be negative".to_string()),
75 Ok(age) if age > 150 => {
76 Some("Please enter a valid age".to_string())
77 }
78 Ok(_) => None,
79 Err(_) => Some("Please enter a valid number".to_string()),
80 }
81 })
82 .build(),
83 )
84 });
85
86 let submit_message = state!(cx, String::new);
87
88 // Get current field values and errors
89 let email = form.get().get_value("email");
90 let password = form.get().get_value("password");
91 let username = form.get().get_value("username");
92 let age = form.get().get_value("age");
93
94 let email_error = form.get().get_error("email");
95 let password_error = form.get().get_error("password");
96 let username_error = form.get().get_error("username");
97 let age_error = form.get().get_error("age");
98
99 // Handlers
100 let on_submit = with!(form, submit_message => move || {
101 if form.get().validate() {
102 let values = form.get().values();
103 submit_message.set(format!(
104 "Form submitted! Email: {}, Username: {}",
105 values.get("email").unwrap_or(&String::new()),
106 values.get("username").unwrap_or(&String::new())
107 ));
108 } else {
109 submit_message.set("Please fix the errors above".to_string());
110 }
111 });
112
113 let on_reset = with!(form, submit_message => move || {
114 form.get().reset();
115 submit_message.set(String::new());
116 });
117
118 View::vstack()
119 .spacing(1)
120 .child(
121 // Header
122 View::boxed()
123 .border(true)
124 .padding(1)
125 .child(
126 View::vstack()
127 .child(View::styled_text("Form Validation Demo").bold().build())
128 .child(
129 View::styled_text("Tab between fields, type to enter values")
130 .dim()
131 .build(),
132 )
133 .build(),
134 )
135 .build(),
136 )
137 .child(
138 // Form
139 View::boxed()
140 .flex(1)
141 .border(true)
142 .padding(1)
143 .child(
144 View::form()
145 .spacing(1)
146 .child(
147 View::form_field("email")
148 .label("Email Address *")
149 .value(email.clone())
150 .placeholder("you@example.com")
151 .error(email_error)
152 .on_change(with!(form => move |v: String| {
153 form.get().set_value("email", v);
154 }))
155 .on_blur(with!(form => move || {
156 form.get().touch("email");
157 }))
158 .build(),
159 )
160 .child(
161 View::form_field("username")
162 .label("Username *")
163 .value(username.clone())
164 .placeholder("johndoe")
165 .error(username_error)
166 .on_change(with!(form => move |v: String| {
167 form.get().set_value("username", v);
168 }))
169 .on_blur(with!(form => move || {
170 form.get().touch("username");
171 }))
172 .build(),
173 )
174 .child(
175 View::form_field("password")
176 .label("Password * (min 8 chars)")
177 .value(password.clone())
178 .placeholder("Enter password")
179 .password(true)
180 .error(password_error)
181 .on_change(with!(form => move |v: String| {
182 form.get().set_value("password", v);
183 }))
184 .on_blur(with!(form => move || {
185 form.get().touch("password");
186 }))
187 .build(),
188 )
189 .child(
190 View::form_field("age")
191 .label("Age (optional)")
192 .value(age.clone())
193 .placeholder("25")
194 .error(age_error)
195 .on_change(with!(form => move |v: String| {
196 form.get().set_value("age", v);
197 }))
198 .on_blur(with!(form => move || {
199 form.get().touch("age");
200 }))
201 .build(),
202 )
203 .build(),
204 )
205 .build(),
206 )
207 .child(
208 // Actions
209 View::hstack()
210 .spacing(2)
211 .child(View::button().label("Submit").on_press(on_submit).build())
212 .child(View::button().label("Reset").on_press(on_reset).build())
213 .build(),
214 )
215 .child(
216 // Status
217 View::boxed()
218 .border(true)
219 .padding(1)
220 .child(
221 View::vstack()
222 .child(View::text(if submit_message.get().is_empty() {
223 "Fill in the form and click Submit".to_string()
224 } else {
225 submit_message.get()
226 }))
227 .child(
228 View::styled_text(format!(
229 "Form valid: {}",
230 if form.get().is_valid() { "Yes" } else { "No" }
231 ))
232 .color(if form.get().is_valid() {
233 Color::Green
234 } else {
235 Color::Red
236 })
237 .build(),
238 )
239 .build(),
240 )
241 .build(),
242 )
243 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
244 .child(
245 View::modal()
246 .visible(show_help.get())
247 .title("Example 22: Forms")
248 .on_dismiss(with!(show_help => move || show_help.set(false)))
249 .child(
250 View::vstack()
251 .child(View::styled_text("What you're seeing").bold().build())
252 .child(View::text("• Declarative form validation"))
253 .child(View::text("• Required, email, length validators"))
254 .child(View::text("• Custom validation functions"))
255 .child(View::gap(1))
256 .child(View::styled_text("Key concepts").bold().build())
257 .child(View::text("• FormState manages all fields"))
258 .child(View::text("• FieldBuilder defines validation"))
259 .child(View::text("• View::form_field() renders inputs"))
260 .child(View::text("• on_blur triggers validation"))
261 .child(View::gap(1))
262 .child(View::styled_text("Try this").bold().build())
263 .child(View::text("• Enter invalid email, see error"))
264 .child(View::text("• Try short password (<8 chars)"))
265 .child(View::text("• Username with spaces shows error"))
266 .child(View::gap(1))
267 .child(View::styled_text("Next up").bold().build())
268 .child(View::text("→ 23_modal: modal dialogs"))
269 .child(View::gap(1))
270 .child(View::styled_text("Press Escape to close").dim().build())
271 .build(),
272 )
273 .build(),
274 )
275 .build()
276 }Sourcepub fn values(&self) -> HashMap<String, String>
pub fn values(&self) -> HashMap<String, String>
Get all field values as a HashMap.
Examples found in repository?
examples/22_forms.rs (line 102)
21 fn render(&self, cx: Scope) -> View {
22 let show_help = state!(cx, || false);
23
24 // F1 toggles help
25 cx.use_command(
26 KeyBinding::key(KeyCode::F(1)),
27 with!(show_help => move || show_help.update(|v| *v = !*v)),
28 );
29
30 // Create form state with validated fields
31 let form = state!(cx, || {
32 FormState::new()
33 .field(
34 FieldBuilder::new("email")
35 .required()
36 .email()
37 .error_message("Please enter a valid email address")
38 .build(),
39 )
40 .field(
41 FieldBuilder::new("password")
42 .required()
43 .min_length(8)
44 .error_message("Password must be at least 8 characters")
45 .build(),
46 )
47 .field(
48 FieldBuilder::new("username")
49 .required()
50 .min_length(3)
51 .max_length(20)
52 .custom(|v| {
53 if v.contains(' ') {
54 Some("Username cannot contain spaces".to_string())
55 } else if !v.chars().all(|c| c.is_alphanumeric() || c == '_') {
56 Some(
57 "Username can only contain letters, numbers, and underscores"
58 .to_string(),
59 )
60 } else {
61 None
62 }
63 })
64 .build(),
65 )
66 .field(
67 FieldBuilder::new("age")
68 .integer()
69 .custom(|v| {
70 if v.is_empty() {
71 return None; // Optional field
72 }
73 match v.parse::<i32>() {
74 Ok(age) if age < 0 => Some("Age cannot be negative".to_string()),
75 Ok(age) if age > 150 => {
76 Some("Please enter a valid age".to_string())
77 }
78 Ok(_) => None,
79 Err(_) => Some("Please enter a valid number".to_string()),
80 }
81 })
82 .build(),
83 )
84 });
85
86 let submit_message = state!(cx, String::new);
87
88 // Get current field values and errors
89 let email = form.get().get_value("email");
90 let password = form.get().get_value("password");
91 let username = form.get().get_value("username");
92 let age = form.get().get_value("age");
93
94 let email_error = form.get().get_error("email");
95 let password_error = form.get().get_error("password");
96 let username_error = form.get().get_error("username");
97 let age_error = form.get().get_error("age");
98
99 // Handlers
100 let on_submit = with!(form, submit_message => move || {
101 if form.get().validate() {
102 let values = form.get().values();
103 submit_message.set(format!(
104 "Form submitted! Email: {}, Username: {}",
105 values.get("email").unwrap_or(&String::new()),
106 values.get("username").unwrap_or(&String::new())
107 ));
108 } else {
109 submit_message.set("Please fix the errors above".to_string());
110 }
111 });
112
113 let on_reset = with!(form, submit_message => move || {
114 form.get().reset();
115 submit_message.set(String::new());
116 });
117
118 View::vstack()
119 .spacing(1)
120 .child(
121 // Header
122 View::boxed()
123 .border(true)
124 .padding(1)
125 .child(
126 View::vstack()
127 .child(View::styled_text("Form Validation Demo").bold().build())
128 .child(
129 View::styled_text("Tab between fields, type to enter values")
130 .dim()
131 .build(),
132 )
133 .build(),
134 )
135 .build(),
136 )
137 .child(
138 // Form
139 View::boxed()
140 .flex(1)
141 .border(true)
142 .padding(1)
143 .child(
144 View::form()
145 .spacing(1)
146 .child(
147 View::form_field("email")
148 .label("Email Address *")
149 .value(email.clone())
150 .placeholder("you@example.com")
151 .error(email_error)
152 .on_change(with!(form => move |v: String| {
153 form.get().set_value("email", v);
154 }))
155 .on_blur(with!(form => move || {
156 form.get().touch("email");
157 }))
158 .build(),
159 )
160 .child(
161 View::form_field("username")
162 .label("Username *")
163 .value(username.clone())
164 .placeholder("johndoe")
165 .error(username_error)
166 .on_change(with!(form => move |v: String| {
167 form.get().set_value("username", v);
168 }))
169 .on_blur(with!(form => move || {
170 form.get().touch("username");
171 }))
172 .build(),
173 )
174 .child(
175 View::form_field("password")
176 .label("Password * (min 8 chars)")
177 .value(password.clone())
178 .placeholder("Enter password")
179 .password(true)
180 .error(password_error)
181 .on_change(with!(form => move |v: String| {
182 form.get().set_value("password", v);
183 }))
184 .on_blur(with!(form => move || {
185 form.get().touch("password");
186 }))
187 .build(),
188 )
189 .child(
190 View::form_field("age")
191 .label("Age (optional)")
192 .value(age.clone())
193 .placeholder("25")
194 .error(age_error)
195 .on_change(with!(form => move |v: String| {
196 form.get().set_value("age", v);
197 }))
198 .on_blur(with!(form => move || {
199 form.get().touch("age");
200 }))
201 .build(),
202 )
203 .build(),
204 )
205 .build(),
206 )
207 .child(
208 // Actions
209 View::hstack()
210 .spacing(2)
211 .child(View::button().label("Submit").on_press(on_submit).build())
212 .child(View::button().label("Reset").on_press(on_reset).build())
213 .build(),
214 )
215 .child(
216 // Status
217 View::boxed()
218 .border(true)
219 .padding(1)
220 .child(
221 View::vstack()
222 .child(View::text(if submit_message.get().is_empty() {
223 "Fill in the form and click Submit".to_string()
224 } else {
225 submit_message.get()
226 }))
227 .child(
228 View::styled_text(format!(
229 "Form valid: {}",
230 if form.get().is_valid() { "Yes" } else { "No" }
231 ))
232 .color(if form.get().is_valid() {
233 Color::Green
234 } else {
235 Color::Red
236 })
237 .build(),
238 )
239 .build(),
240 )
241 .build(),
242 )
243 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
244 .child(
245 View::modal()
246 .visible(show_help.get())
247 .title("Example 22: Forms")
248 .on_dismiss(with!(show_help => move || show_help.set(false)))
249 .child(
250 View::vstack()
251 .child(View::styled_text("What you're seeing").bold().build())
252 .child(View::text("• Declarative form validation"))
253 .child(View::text("• Required, email, length validators"))
254 .child(View::text("• Custom validation functions"))
255 .child(View::gap(1))
256 .child(View::styled_text("Key concepts").bold().build())
257 .child(View::text("• FormState manages all fields"))
258 .child(View::text("• FieldBuilder defines validation"))
259 .child(View::text("• View::form_field() renders inputs"))
260 .child(View::text("• on_blur triggers validation"))
261 .child(View::gap(1))
262 .child(View::styled_text("Try this").bold().build())
263 .child(View::text("• Enter invalid email, see error"))
264 .child(View::text("• Try short password (<8 chars)"))
265 .child(View::text("• Username with spaces shows error"))
266 .child(View::gap(1))
267 .child(View::styled_text("Next up").bold().build())
268 .child(View::text("→ 23_modal: modal dialogs"))
269 .child(View::gap(1))
270 .child(View::styled_text("Press Escape to close").dim().build())
271 .build(),
272 )
273 .build(),
274 )
275 .build()
276 }Sourcepub fn field_names(&self) -> Vec<String>
pub fn field_names(&self) -> Vec<String>
Get field names in order.
Sourcepub fn reset(&self)
pub fn reset(&self)
Reset all fields to empty and clear errors.
Examples found in repository?
examples/22_forms.rs (line 114)
21 fn render(&self, cx: Scope) -> View {
22 let show_help = state!(cx, || false);
23
24 // F1 toggles help
25 cx.use_command(
26 KeyBinding::key(KeyCode::F(1)),
27 with!(show_help => move || show_help.update(|v| *v = !*v)),
28 );
29
30 // Create form state with validated fields
31 let form = state!(cx, || {
32 FormState::new()
33 .field(
34 FieldBuilder::new("email")
35 .required()
36 .email()
37 .error_message("Please enter a valid email address")
38 .build(),
39 )
40 .field(
41 FieldBuilder::new("password")
42 .required()
43 .min_length(8)
44 .error_message("Password must be at least 8 characters")
45 .build(),
46 )
47 .field(
48 FieldBuilder::new("username")
49 .required()
50 .min_length(3)
51 .max_length(20)
52 .custom(|v| {
53 if v.contains(' ') {
54 Some("Username cannot contain spaces".to_string())
55 } else if !v.chars().all(|c| c.is_alphanumeric() || c == '_') {
56 Some(
57 "Username can only contain letters, numbers, and underscores"
58 .to_string(),
59 )
60 } else {
61 None
62 }
63 })
64 .build(),
65 )
66 .field(
67 FieldBuilder::new("age")
68 .integer()
69 .custom(|v| {
70 if v.is_empty() {
71 return None; // Optional field
72 }
73 match v.parse::<i32>() {
74 Ok(age) if age < 0 => Some("Age cannot be negative".to_string()),
75 Ok(age) if age > 150 => {
76 Some("Please enter a valid age".to_string())
77 }
78 Ok(_) => None,
79 Err(_) => Some("Please enter a valid number".to_string()),
80 }
81 })
82 .build(),
83 )
84 });
85
86 let submit_message = state!(cx, String::new);
87
88 // Get current field values and errors
89 let email = form.get().get_value("email");
90 let password = form.get().get_value("password");
91 let username = form.get().get_value("username");
92 let age = form.get().get_value("age");
93
94 let email_error = form.get().get_error("email");
95 let password_error = form.get().get_error("password");
96 let username_error = form.get().get_error("username");
97 let age_error = form.get().get_error("age");
98
99 // Handlers
100 let on_submit = with!(form, submit_message => move || {
101 if form.get().validate() {
102 let values = form.get().values();
103 submit_message.set(format!(
104 "Form submitted! Email: {}, Username: {}",
105 values.get("email").unwrap_or(&String::new()),
106 values.get("username").unwrap_or(&String::new())
107 ));
108 } else {
109 submit_message.set("Please fix the errors above".to_string());
110 }
111 });
112
113 let on_reset = with!(form, submit_message => move || {
114 form.get().reset();
115 submit_message.set(String::new());
116 });
117
118 View::vstack()
119 .spacing(1)
120 .child(
121 // Header
122 View::boxed()
123 .border(true)
124 .padding(1)
125 .child(
126 View::vstack()
127 .child(View::styled_text("Form Validation Demo").bold().build())
128 .child(
129 View::styled_text("Tab between fields, type to enter values")
130 .dim()
131 .build(),
132 )
133 .build(),
134 )
135 .build(),
136 )
137 .child(
138 // Form
139 View::boxed()
140 .flex(1)
141 .border(true)
142 .padding(1)
143 .child(
144 View::form()
145 .spacing(1)
146 .child(
147 View::form_field("email")
148 .label("Email Address *")
149 .value(email.clone())
150 .placeholder("you@example.com")
151 .error(email_error)
152 .on_change(with!(form => move |v: String| {
153 form.get().set_value("email", v);
154 }))
155 .on_blur(with!(form => move || {
156 form.get().touch("email");
157 }))
158 .build(),
159 )
160 .child(
161 View::form_field("username")
162 .label("Username *")
163 .value(username.clone())
164 .placeholder("johndoe")
165 .error(username_error)
166 .on_change(with!(form => move |v: String| {
167 form.get().set_value("username", v);
168 }))
169 .on_blur(with!(form => move || {
170 form.get().touch("username");
171 }))
172 .build(),
173 )
174 .child(
175 View::form_field("password")
176 .label("Password * (min 8 chars)")
177 .value(password.clone())
178 .placeholder("Enter password")
179 .password(true)
180 .error(password_error)
181 .on_change(with!(form => move |v: String| {
182 form.get().set_value("password", v);
183 }))
184 .on_blur(with!(form => move || {
185 form.get().touch("password");
186 }))
187 .build(),
188 )
189 .child(
190 View::form_field("age")
191 .label("Age (optional)")
192 .value(age.clone())
193 .placeholder("25")
194 .error(age_error)
195 .on_change(with!(form => move |v: String| {
196 form.get().set_value("age", v);
197 }))
198 .on_blur(with!(form => move || {
199 form.get().touch("age");
200 }))
201 .build(),
202 )
203 .build(),
204 )
205 .build(),
206 )
207 .child(
208 // Actions
209 View::hstack()
210 .spacing(2)
211 .child(View::button().label("Submit").on_press(on_submit).build())
212 .child(View::button().label("Reset").on_press(on_reset).build())
213 .build(),
214 )
215 .child(
216 // Status
217 View::boxed()
218 .border(true)
219 .padding(1)
220 .child(
221 View::vstack()
222 .child(View::text(if submit_message.get().is_empty() {
223 "Fill in the form and click Submit".to_string()
224 } else {
225 submit_message.get()
226 }))
227 .child(
228 View::styled_text(format!(
229 "Form valid: {}",
230 if form.get().is_valid() { "Yes" } else { "No" }
231 ))
232 .color(if form.get().is_valid() {
233 Color::Green
234 } else {
235 Color::Red
236 })
237 .build(),
238 )
239 .build(),
240 )
241 .build(),
242 )
243 .child(View::styled_text("F1 help • Ctrl+Q quit").dim().build())
244 .child(
245 View::modal()
246 .visible(show_help.get())
247 .title("Example 22: Forms")
248 .on_dismiss(with!(show_help => move || show_help.set(false)))
249 .child(
250 View::vstack()
251 .child(View::styled_text("What you're seeing").bold().build())
252 .child(View::text("• Declarative form validation"))
253 .child(View::text("• Required, email, length validators"))
254 .child(View::text("• Custom validation functions"))
255 .child(View::gap(1))
256 .child(View::styled_text("Key concepts").bold().build())
257 .child(View::text("• FormState manages all fields"))
258 .child(View::text("• FieldBuilder defines validation"))
259 .child(View::text("• View::form_field() renders inputs"))
260 .child(View::text("• on_blur triggers validation"))
261 .child(View::gap(1))
262 .child(View::styled_text("Try this").bold().build())
263 .child(View::text("• Enter invalid email, see error"))
264 .child(View::text("• Try short password (<8 chars)"))
265 .child(View::text("• Username with spaces shows error"))
266 .child(View::gap(1))
267 .child(View::styled_text("Next up").bold().build())
268 .child(View::text("→ 23_modal: modal dialogs"))
269 .child(View::gap(1))
270 .child(View::styled_text("Press Escape to close").dim().build())
271 .build(),
272 )
273 .build(),
274 )
275 .build()
276 }Trait Implementations§
Auto Trait Implementations§
impl Freeze for FormState
impl !RefUnwindSafe for FormState
impl !Send for FormState
impl !Sync for FormState
impl Unpin for FormState
impl !UnwindSafe for FormState
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Mutably borrows from an owned value. Read more
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Downcast for Twhere
T: Any,
impl<T> Downcast for Twhere
T: Any,
Source§fn into_any(self: Box<T>) -> Box<dyn Any>
fn into_any(self: Box<T>) -> Box<dyn Any>
Convert
Box<dyn Trait> (where Trait: Downcast) to Box<dyn Any>. Box<dyn Any> can
then be further downcast into Box<ConcreteType> where ConcreteType implements Trait.Source§fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
fn into_any_rc(self: Rc<T>) -> Rc<dyn Any>
Convert
Rc<Trait> (where Trait: Downcast) to Rc<Any>. Rc<Any> can then be
further downcast into Rc<ConcreteType> where ConcreteType implements Trait.Source§fn as_any(&self) -> &(dyn Any + 'static)
fn as_any(&self) -> &(dyn Any + 'static)
Convert
&Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &Any’s vtable from &Trait’s.Source§fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
fn as_any_mut(&mut self) -> &mut (dyn Any + 'static)
Convert
&mut Trait (where Trait: Downcast) to &Any. This is needed since Rust cannot
generate &mut Any’s vtable from &mut Trait’s.