pub struct FormFieldBuilder { /* private fields */ }Expand description
Builder for FormField views.
Implementations§
Source§impl FormFieldBuilder
impl FormFieldBuilder
pub fn new(name: impl Into<String>) -> Self
Sourcepub fn label(self, label: impl Into<String>) -> Self
pub fn label(self, label: impl Into<String>) -> Self
Set the display label.
Examples found in repository?
examples/22_forms.rs (line 148)
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 value(self, value: impl Into<String>) -> Self
pub fn value(self, value: impl Into<String>) -> Self
Set the current value.
Examples found in repository?
examples/22_forms.rs (line 149)
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 placeholder(self, placeholder: impl Into<String>) -> Self
pub fn placeholder(self, placeholder: impl Into<String>) -> Self
Set the placeholder text.
Examples found in repository?
examples/22_forms.rs (line 150)
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 error(self, error: Option<String>) -> Self
pub fn error(self, error: Option<String>) -> Self
Set the error message.
Examples found in repository?
examples/22_forms.rs (line 151)
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 password(self, password: bool) -> Self
pub fn password(self, password: bool) -> Self
Set whether this is a password field.
Examples found in repository?
examples/22_forms.rs (line 179)
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 on_change(self, callback: impl Fn(String) + 'static) -> Self
pub fn on_change(self, callback: impl Fn(String) + 'static) -> Self
Set the change callback.
Examples found in repository?
examples/22_forms.rs (lines 152-154)
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 on_blur(self, callback: impl Fn() + 'static) -> Self
pub fn on_blur(self, callback: impl Fn() + 'static) -> Self
Set the blur callback.
Examples found in repository?
examples/22_forms.rs (lines 155-157)
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 build(self) -> View
pub fn build(self) -> View
Examples found in repository?
examples/22_forms.rs (line 158)
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§
Source§impl Default for FormFieldBuilder
impl Default for FormFieldBuilder
Source§fn default() -> FormFieldBuilder
fn default() -> FormFieldBuilder
Returns the “default value” for a type. Read more
Auto Trait Implementations§
impl Freeze for FormFieldBuilder
impl !RefUnwindSafe for FormFieldBuilder
impl !Send for FormFieldBuilder
impl !Sync for FormFieldBuilder
impl Unpin for FormFieldBuilder
impl !UnwindSafe for FormFieldBuilder
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> 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.