Skip to content

Commit 29b008b

Browse files
author
Juan Pablo Manson
committed
Add dynamic form features: conditional visibility, dependent options, and multi-step wizard support
1 parent c056208 commit 29b008b

1 file changed

Lines changed: 153 additions & 0 deletions

File tree

README.md

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,159 @@ print(result["errors"][0]["message"]) # "El campo name es requerido"
246246

247247
See [`examples/i18n_usage.py`](examples/i18n_usage.py) for a full working example.
248248

249+
## Dynamic Forms
250+
251+
### Conditional Visibility
252+
253+
Fields can be shown or hidden based on the value of other fields using `visible_when`. This is metadata that your frontend can use for dynamic UI, and the backend can respect during validation.
254+
255+
```python
256+
from codeforms import Form, TextField, SelectField, SelectOption, VisibilityRule
257+
258+
form = Form(
259+
name="address",
260+
fields=[
261+
SelectField(
262+
name="country",
263+
label="Country",
264+
required=True,
265+
options=[
266+
SelectOption(value="US", label="United States"),
267+
SelectOption(value="AR", label="Argentina"),
268+
],
269+
),
270+
TextField(
271+
name="state",
272+
label="State",
273+
required=True,
274+
visible_when=[
275+
VisibilityRule(field="country", operator="equals", value="US"),
276+
],
277+
),
278+
TextField(
279+
name="province",
280+
label="Province",
281+
required=True,
282+
visible_when=[
283+
VisibilityRule(field="country", operator="equals", value="AR"),
284+
],
285+
),
286+
],
287+
)
288+
```
289+
290+
Supported operators: `equals`, `not_equals`, `in`, `not_in`, `gt`, `lt`, `is_empty`, `is_not_empty`.
291+
292+
#### Dynamic Validation
293+
294+
Use `validate_form_data_dynamic()` to validate only the fields that are currently visible:
295+
296+
```python
297+
from codeforms import validate_form_data_dynamic
298+
299+
result = validate_form_data_dynamic(
300+
form,
301+
{"country": "US", "state": "California"},
302+
respect_visibility=True,
303+
)
304+
print(result["success"]) # True — "province" is hidden, so not required
305+
```
306+
307+
The legacy `validate_form_data()` function is unchanged and always validates all fields regardless of visibility.
308+
309+
#### Checking Visible Fields
310+
311+
```python
312+
visible = form.get_visible_fields({"country": "US"})
313+
print([f.name for f in visible]) # ["country", "state"]
314+
```
315+
316+
See [`examples/conditional_visibility.py`](examples/conditional_visibility.py) for a full working example.
317+
318+
### Dependent Options
319+
320+
Use `DependentOptionsConfig` to define option sets that change based on another field's value:
321+
322+
```python
323+
from codeforms import SelectField, SelectOption, DependentOptionsConfig
324+
325+
city_field = SelectField(
326+
name="city",
327+
label="City",
328+
options=[ # all possible options (for static HTML rendering)
329+
SelectOption(value="nyc", label="New York City"),
330+
SelectOption(value="bsas", label="Buenos Aires"),
331+
],
332+
dependent_options=DependentOptionsConfig(
333+
depends_on="country",
334+
options_map={
335+
"US": [SelectOption(value="nyc", label="New York City")],
336+
"AR": [SelectOption(value="bsas", label="Buenos Aires")],
337+
},
338+
),
339+
)
340+
```
341+
342+
The `dependent_options` metadata serializes to JSON for your frontend to consume. See [`examples/dependent_options.py`](examples/dependent_options.py).
343+
344+
## Multi-Step Wizard Forms
345+
346+
Use `FormStep` to split a form into multiple steps. Each step contains its own fields and can be validated independently.
347+
348+
```python
349+
from codeforms import Form, FormStep, TextField, EmailField, CheckboxField
350+
351+
form = Form(
352+
name="registration",
353+
content=[
354+
FormStep(
355+
title="Personal Information",
356+
description="Tell us about yourself",
357+
content=[
358+
TextField(name="name", label="Name", required=True),
359+
EmailField(name="email", label="Email", required=True),
360+
],
361+
),
362+
FormStep(
363+
title="Confirmation",
364+
content=[
365+
CheckboxField(name="terms", label="I accept the terms", required=True),
366+
],
367+
validation_mode="on_submit",
368+
),
369+
],
370+
)
371+
```
372+
373+
### Step Validation
374+
375+
```python
376+
# Validate a single step
377+
result = form.validate_step(0, {"name": "John", "email": "john@example.com"})
378+
print(result["success"]) # True
379+
380+
# Validate all steps at once
381+
result = form.validate_all_steps({
382+
"name": "John",
383+
"email": "john@example.com",
384+
"terms": True,
385+
})
386+
print(result["success"]) # True
387+
```
388+
389+
### Wizard Helpers
390+
391+
```python
392+
steps = form.get_steps() # List[FormStep]
393+
fields = form.fields # Flat list of all fields across all steps
394+
```
395+
396+
### HTML Export
397+
398+
Wizard forms export with `data-wizard="true"` on the `<form>` tag. Each step renders as a `<section data-step="true">` (not `<fieldset>`), so you can wire up your own step navigation in JavaScript.
399+
400+
See [`examples/wizard_form.py`](examples/wizard_form.py) for a full working example.
401+
249402
## Custom Field Types
250403

251404
You can create your own field types by subclassing `FormFieldBase` and registering them with `register_field_type()`. Custom fields integrate seamlessly with forms, JSON serialization, validation, and HTML export.

0 commit comments

Comments
 (0)