Skip to content

Commit d8812e2

Browse files
authored
RFC about using Symfony UX Turbo (#13)
* RFC about using Symfony UX Turbo * review fix
1 parent fc85edd commit d8812e2

File tree

1 file changed

+110
-5
lines changed

1 file changed

+110
-5
lines changed

README.md

Lines changed: 110 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
2626
$builder->addDependent('mainFood', ['meal'], function(DependentField $field, string $meal) {
2727
// dynamically add choices based on the meal!
2828
$choices = ['...'];
29-
29+
3030
$field->add(ChoiceType::class, [
3131
'placeholder' => null === $meal ? 'Select a meal first' : sprintf('What is for %s?', $meal->getReadable()),
3232
'choices' => $choices,
@@ -65,7 +65,7 @@ use Symfonycasts\DynamicForms\DynamicFormBuilder;
6565
public function buildForm(FormBuilderInterface $builder, array $options): void
6666
{
6767
$builder = new DynamicFormBuilder($builder);
68-
68+
6969
// ...
7070
}
7171
```
@@ -136,7 +136,7 @@ if it's conditionally added:
136136
{% if form.badRatingNotes is defined %}
137137
{{ form_row(form.badRatingNotes) }}
138138
{% endif %}
139-
139+
140140
<button>Send Feedback</button>
141141
{{ form_end(form) }}
142142
```
@@ -153,9 +153,114 @@ This library doesn't handle this for you, but here are the 2 main options:
153153
This is the easiest method: by rendering your form inside a live component,
154154
it will automatically re-render when the form changes.
155155

156-
### B) Write custom JavaScript
156+
### B) Use [Symfony UX Turbo](https://symfony.com/bundles/ux-turbo/current/index.html#decomposing-complex-pages-with-turbo-frames)
157+
158+
If you are already using Symfony UX Turbo on your website, you can have a dynamic form running quickly without any JavaScript.
159+
160+
Or you may want to install Symfony UX Turbo, [check out the documentation](https://symfony.com/bundles/ux-turbo/current/index.html#installation).
161+
162+
> [!NOTE]
163+
> You only need to have Turbo Frame, you can disable Turbo Drive if you do not use it, or do not want to use it.
164+
> ie: `Turbo.session.drive = false;`
165+
166+
Simply add a `<turbo-frame>` around your form:
167+
168+
```twig
169+
<turbo-frame id="rating-form">
170+
{{ form(form) }}
171+
</turbo-frame>
172+
```
173+
174+
From here you need two small changes:
175+
176+
First, in your form type:
177+
- You need to add an attribute on the choice field, so it auto-submits the form when changed (may need to be adapted to your own form if more complex)
178+
- Add a submit button, so in the controller you can differenciate from an auto-submit versus a user action
179+
180+
181+
```diff
182+
// src/Form/FeedbackForm.php
183+
184+
// ...
185+
186+
class FeedbackForm extends AbstractType
187+
{
188+
public function buildForm(FormBuilderInterface $builder, array $options)
189+
{
190+
$builder = new DynamicFormBuilder($builder);
191+
192+
$builder->add('rating', ChoiceType::class, [
193+
'choices' => [
194+
'Select a rating' => null,
195+
'Great' => 5,
196+
'Good' => 4,
197+
'Okay' => 3,
198+
'Bad' => 2,
199+
'Terrible' => 1
200+
],
201+
+ // This will allow the form to auto-submit on value change
202+
+ 'attr' => ['onchange' => 'this.form.submit()'],
203+
]);
204+
+ // This will allow to differenciate between a user submition and an auto-submit
205+
+ $builder->add('submit', SubmitType::class, [
206+
+ 'attr' => ['value' => 'submit'], // Needed for Turbo
207+
+ ]);
208+
209+
$builder->addDependent('badRatingNotes', 'rating', function(DependentField $field, ?int $rating) {
210+
if (null === $rating || $rating >= 3) {
211+
return; // field not needed
212+
}
213+
214+
$field->add(TextareaType::class, [
215+
'label' => 'What went wrong?',
216+
'attr' => ['rows' => 3],
217+
'help' => sprintf('Because you gave a %d rating, we\'d love to know what went wrong.', $rating),
218+
]);
219+
});
220+
}
221+
}
222+
```
223+
224+
Second, in your controller:
225+
- Specify the action on your form, [this is needed for Turbo Frame](https://symfony.com/bundles/ux-turbo/current/index.html#3-form-response-code-changes)
226+
- Handle the auto-submit by checking if the button has been clicked
227+
228+
```diff
229+
// src/Controller/FeedbackController.php
230+
231+
#[Route('/feedback', name: 'feedback')]
232+
public function feedback(Request $request): Response
233+
{
234+
//...
235+
236+
- $feedbackForm = $this->createForm(FeedbackForm::class);
237+
+ $feedbackForm = $this->createForm(FeedbackForm::class, options: [
238+
+ // This is needed by Turbo Frame, it is not specific to Dependent Symfony Form Fields
239+
+ 'action' => $this->generateUrl('feedback'),
240+
+ ]);
241+
$feedbackForm->handleRequest($request);
242+
if ($feedbackForm->isSubmitted() && $feedbackForm->isValid()) {
243+
244+
+ /** @var SubmitButton $submitButton */
245+
+ $submitButton = $feedbackForm->get('submit');
246+
+ if (!$submitButton->isClicked()) {
247+
+ return $this->render('feedback.html.twig', ['feedbackForm' => $feedbackForm]);
248+
+ }
249+
250+
// Your code here
251+
// ...
252+
253+
return $this->redirectToRoute('home');
254+
}
255+
256+
return $this->render('feedback.html.twig', ['feedbackForm' => $feedbackForm]);
257+
}
258+
259+
```
260+
261+
### C) Write custom JavaScript
157262

158-
If you're not using Live Components, you'll need to write some custom
263+
If you're not using Live Components, nor Turbo Frames, you'll need to write some custom
159264
JavaScript to listen to the `change` event on the `rating` field and then
160265
make an AJAX call to re-render the form. The AJAX call should submit the
161266
form to its usual endpoint (or any endpoint that will submit the form), take

0 commit comments

Comments
 (0)