pub struct FormBuilder { /* private fields */ }Expand description
Builder for Form views.
Implementations§
Source§impl FormBuilder
impl FormBuilder
pub fn new() -> Self
Sourcepub fn child(self, view: View) -> Self
pub fn child(self, view: View) -> Self
Add a child view (typically a FormField).
Examples found in repository?
examples/22_forms.rs (lines 146-159)
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 spacing(self, spacing: u16) -> Self
pub fn spacing(self, spacing: u16) -> Self
Set spacing between children.
Examples found in repository?
examples/22_forms.rs (line 145)
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_submit(
self,
callback: impl Fn(HashMap<String, String>) + 'static,
) -> Self
pub fn on_submit( self, callback: impl Fn(HashMap<String, String>) + 'static, ) -> Self
Set the submit callback.
Sourcepub fn build(self) -> View
pub fn build(self) -> View
Examples found in repository?
examples/22_forms.rs (line 203)
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 FormBuilder
impl Default for FormBuilder
Source§fn default() -> FormBuilder
fn default() -> FormBuilder
Returns the “default value” for a type. Read more
Auto Trait Implementations§
impl Freeze for FormBuilder
impl !RefUnwindSafe for FormBuilder
impl !Send for FormBuilder
impl !Sync for FormBuilder
impl Unpin for FormBuilder
impl !UnwindSafe for FormBuilder
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.