Skip to content

Commit 6bd3866

Browse files
committed
Adding README and allowing a string arg for the dependencies
1 parent c3da95c commit 6bd3866

File tree

2 files changed

+165
-3
lines changed

2 files changed

+165
-3
lines changed

README.md

Lines changed: 162 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,163 @@
1-
# Dynamic Symfony Forms! Woh!
1+
# Dynamic / Dependent Symfony Form Fields
22

3-
TODO
3+
Ever have a form field that depends on another?
4+
5+
* Show a field only if another field is set to a specific value;
6+
* Change the options of a field based on the value of another field;
7+
* Have multiple-level dependencies (e.g. field A depends on field B
8+
which depends on field C).
9+
10+
```
11+
public function buildForm(FormBuilderInterface $builder, array $options): void
12+
{
13+
$builder = new DynamicFormBuilder($builder);
14+
15+
$builder->add('meal', ChoiceType::class, [
16+
'choices' => [
17+
'Breakfast' => 'breakfast',
18+
'Lunch' => 'lunch',
19+
'Dinner' => 'dinner',
20+
],
21+
]);
22+
23+
$builder->addDependent('mainFood', ['meal'], function(DependentField $field, string $meal) {
24+
// dynamically add choices based on the meal!
25+
$choices = ['...'];
26+
27+
$field->add(ChoiceType::class, [
28+
'placeholder' => null === $meal ? 'Select a meal first' : sprintf('What is for %s?', $meal->getReadable()),
29+
'choices' => $choices,
30+
'disabled' => null === $meal,
31+
]);
32+
});
33+
```
34+
35+
## Installation
36+
37+
Install the package with:
38+
39+
```bash
40+
composer require symfonycasts/dynamic-forms
41+
```
42+
43+
Done - you're ready to build dynamic forms!
44+
45+
## Usage
46+
47+
Setting up a dependent field is two parts:
48+
49+
1. [Usage in PHP](#usage-in-php) - set up your Symfony form to handle
50+
the dynamic fields;
51+
2. [Updating the Frontend](#updating-the-frontend) - adding code to your
52+
frontend so that when one field changes, part of the form is re-rendered.
53+
54+
## Usage in PHP
55+
56+
Start by wrapping your `FormBuilderInterface` with a `DynamicFormBuilder`:
57+
58+
```php
59+
use Symfonycasts\DynamicForms\DynamicFormBuilder;
60+
// ...
61+
62+
public function buildForm(FormBuilderInterface $builder, array $options): void
63+
{
64+
$builder = new DynamicFormBuilder($builder);
65+
66+
// ...
67+
}
68+
```
69+
70+
`DynamicFormBuilder` has all the same methods as `FormBuilderInterface` plus
71+
one extra: `addDependent()`. If a field depends on another, use this method
72+
instead of `add()`
73+
74+
```php
75+
// src/Form/FeedbackForm.php
76+
77+
// ...
78+
use Symfonycasts\DynamicForms\DependentField;
79+
use Symfonycasts\DynamicForms\DynamicFormBuilder;
80+
81+
class FeedbackForm extends AbstractType
82+
{
83+
public function buildForm(FormBuilderInterface $builder, array $options)
84+
{
85+
$builder = new DynamicFormBuilder($builder);
86+
87+
$builder->add('rating', ChoiceType::class, [
88+
'choices' => [
89+
'Select a rating' => null,
90+
'Great' => 5,
91+
'Good' => 4,
92+
'Okay' => 3,
93+
'Bad' => 2,
94+
'Terrible' => 1
95+
],
96+
]);
97+
98+
$builder->addDependent('badRatingNotes', 'rating', function(DependentField $field, ?int $rating) {
99+
if (null === $rating || $rating >= 3) {
100+
return; // field not needed
101+
}
102+
103+
$field->add(TextareaType::class, [
104+
'label' => 'What went wrong?',
105+
'attr' => ['rows' => 3],
106+
'help' => sprintf('Because you gave a %d rating, we\'d love to know what went wrong.', $rating),
107+
]);
108+
});
109+
}
110+
}
111+
```
112+
113+
The `addDependent()` method takes 3 arguments:
114+
115+
1. The name of the field to add;
116+
2. The name (or names) of the field that this field depends on;
117+
3. A callback that will be called when the form is submitted. This callback
118+
receives a `DependentField` object as the first argument then the
119+
value of each dependent field as the next arguments.
120+
121+
Behind the scenes, this works by registering several form event listeners.
122+
The callback be executed when the form is first created (using the initial
123+
data) and then again when the form is submitted. This means that the callback
124+
may be called multiple times.
125+
126+
Rendering the field is the same - just be sure to make sure the field exists
127+
if it's conditionally added:
128+
129+
```twig
130+
{{ form_start(form) }}
131+
{{ form_row(form.rating) }}
132+
133+
{% if form.badRatingNotes is defined %}
134+
{{ form_row(form.badRatingNotes) }}
135+
{% endif %}
136+
137+
<button>Send Feedback</button>
138+
{{ form_end(form) }}
139+
```
140+
141+
## Updating the Frontend
142+
143+
In the previous example, when the `rating` field changes, the form (or part of
144+
the form) needs to be re-rendered so the `badRatingNotes` field can be added.
145+
146+
This library doesn't handle this for you, but here are the 2 main options:
147+
148+
### A) Use [Live Components](https://symfony.com/bundles/ux-live-component/current/index.html)
149+
150+
This is the easiest method: by rendering your form inside a live component,
151+
it will automatically re-render when the form changes.
152+
153+
### B) Write custom JavaScript
154+
155+
If you're not using Live Components, you'll need to write some custom
156+
JavaScript to listen to the `change` event on the `rating` field and then
157+
make an AJAX call to re-render the form. The AJAX call should submit the
158+
form to its usual endpoint (or any endpoint that will submit the form), take
159+
the HTML response, extract the parts that need to be re-rendered and then replace
160+
the HTML on the page.
161+
162+
This is a non-trivial task and there may be room for improvement in this
163+
library to make this easier. If you have ideas, please open an issue!

src/DynamicFormBuilder.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,10 @@ public function __construct(private FormBuilderInterface $builder)
7777
}, -1);
7878
}
7979

80-
public function addDependent(string $name, array $dependencies, callable $callback): self
80+
public function addDependent(string $name, string|array $dependencies, callable $callback): self
8181
{
82+
$dependencies = (array) $dependencies;
83+
8284
$this->dependentFieldConfigs[] = new DependentFieldConfig($name, $dependencies, $callback);
8385

8486
return $this;

0 commit comments

Comments
 (0)