Skip to content

Commit

Permalink
RFC about using Symfony UX Turbo (#13)
Browse files Browse the repository at this point in the history
* RFC about using Symfony UX Turbo

* review fix
  • Loading branch information
jrmgx authored Jan 2, 2024
1 parent fc85edd commit d8812e2
Showing 1 changed file with 110 additions and 5 deletions.
115 changes: 110 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void
$builder->addDependent('mainFood', ['meal'], function(DependentField $field, string $meal) {
// dynamically add choices based on the meal!
$choices = ['...'];

$field->add(ChoiceType::class, [
'placeholder' => null === $meal ? 'Select a meal first' : sprintf('What is for %s?', $meal->getReadable()),
'choices' => $choices,
Expand Down Expand Up @@ -65,7 +65,7 @@ use Symfonycasts\DynamicForms\DynamicFormBuilder;
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder = new DynamicFormBuilder($builder);

// ...
}
```
Expand Down Expand Up @@ -136,7 +136,7 @@ if it's conditionally added:
{% if form.badRatingNotes is defined %}
{{ form_row(form.badRatingNotes) }}
{% endif %}
<button>Send Feedback</button>
{{ form_end(form) }}
```
Expand All @@ -153,9 +153,114 @@ This library doesn't handle this for you, but here are the 2 main options:
This is the easiest method: by rendering your form inside a live component,
it will automatically re-render when the form changes.

### B) Write custom JavaScript
### B) Use [Symfony UX Turbo](https://symfony.com/bundles/ux-turbo/current/index.html#decomposing-complex-pages-with-turbo-frames)

If you are already using Symfony UX Turbo on your website, you can have a dynamic form running quickly without any JavaScript.

Or you may want to install Symfony UX Turbo, [check out the documentation](https://symfony.com/bundles/ux-turbo/current/index.html#installation).

> [!NOTE]
> 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.
> ie: `Turbo.session.drive = false;`
Simply add a `<turbo-frame>` around your form:

```twig
<turbo-frame id="rating-form">
{{ form(form) }}
</turbo-frame>
```

From here you need two small changes:

First, in your form type:
- 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)
- Add a submit button, so in the controller you can differenciate from an auto-submit versus a user action


```diff
// src/Form/FeedbackForm.php

// ...

class FeedbackForm extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder = new DynamicFormBuilder($builder);

$builder->add('rating', ChoiceType::class, [
'choices' => [
'Select a rating' => null,
'Great' => 5,
'Good' => 4,
'Okay' => 3,
'Bad' => 2,
'Terrible' => 1
],
+ // This will allow the form to auto-submit on value change
+ 'attr' => ['onchange' => 'this.form.submit()'],
]);
+ // This will allow to differenciate between a user submition and an auto-submit
+ $builder->add('submit', SubmitType::class, [
+ 'attr' => ['value' => 'submit'], // Needed for Turbo
+ ]);

$builder->addDependent('badRatingNotes', 'rating', function(DependentField $field, ?int $rating) {
if (null === $rating || $rating >= 3) {
return; // field not needed
}

$field->add(TextareaType::class, [
'label' => 'What went wrong?',
'attr' => ['rows' => 3],
'help' => sprintf('Because you gave a %d rating, we\'d love to know what went wrong.', $rating),
]);
});
}
}
```

Second, in your controller:
- 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)
- Handle the auto-submit by checking if the button has been clicked

```diff
// src/Controller/FeedbackController.php

#[Route('/feedback', name: 'feedback')]
public function feedback(Request $request): Response
{
//...

- $feedbackForm = $this->createForm(FeedbackForm::class);
+ $feedbackForm = $this->createForm(FeedbackForm::class, options: [
+ // This is needed by Turbo Frame, it is not specific to Dependent Symfony Form Fields
+ 'action' => $this->generateUrl('feedback'),
+ ]);
$feedbackForm->handleRequest($request);
if ($feedbackForm->isSubmitted() && $feedbackForm->isValid()) {

+ /** @var SubmitButton $submitButton */
+ $submitButton = $feedbackForm->get('submit');
+ if (!$submitButton->isClicked()) {
+ return $this->render('feedback.html.twig', ['feedbackForm' => $feedbackForm]);
+ }

// Your code here
// ...

return $this->redirectToRoute('home');
}

return $this->render('feedback.html.twig', ['feedbackForm' => $feedbackForm]);
}

```

### C) Write custom JavaScript

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

0 comments on commit d8812e2

Please sign in to comment.