symfonycasts / dynamic-forms
为 Symfony 表单添加动态/相关字段
Requires
- php: >=8.1
- symfony/form: ^5.4|^6.3|^7.0
Requires (Dev)
- phpstan/phpstan: ^1.11
- symfony/framework-bundle: ^6.3|^7.0
- symfony/options-resolver: ^5.4|^6.3|^7.0
- symfony/phpunit-bridge: ^5.4|^6.3|^7.0
- symfony/twig-bundle: ^5.4|^6.3|^7.0
- twig/twig: ^2.15|^3.0
- zenstruck/browser: ^1.4
This package is auto-updated.
Last update: 2024-08-30 20:00:00 UTC
README
注意:此包目前处于实验性阶段。它似乎运行得很好 - 但表单很复杂!如果您发现了一个错误,请打开一个问题!
你是否曾经有过一个依赖于另一个字段的表单字段?
您可以在 带有 LiveComponent 的 Symfony UX 演示 中找到。
- 只有当另一个字段设置为特定值时才显示字段;
- 根据另一个字段的值更改字段的选项;
- 有多层次依赖(例如,字段 A 依赖于字段 B,字段 B 依赖于字段 C)。
public function buildForm(FormBuilderInterface $builder, array $options): void { $builder = new DynamicFormBuilder($builder); $builder->add('meal', ChoiceType::class, [ 'choices' => [ 'Breakfast' => 'breakfast', 'Lunch' => 'lunch', 'Dinner' => 'dinner', ], ]); $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, 'disabled' => null === $meal, ]); });
安装
使用以下命令安装软件包
composer require symfonycasts/dynamic-forms
完成 - 你可以开始构建动态表单了!
使用方法
设置相关字段分为两部分
PHP 中的使用
首先,将您的 FormBuilderInterface
包装在 DynamicFormBuilder
中
use Symfonycasts\DynamicForms\DynamicFormBuilder; // ... public function buildForm(FormBuilderInterface $builder, array $options): void { $builder = new DynamicFormBuilder($builder); // ... }
DynamicFormBuilder
有与 FormBuilderInterface
相同的方法,还有一个额外的方法:addDependent()
。如果一个字段依赖于另一个字段,请使用此方法而不是 add()
// src/Form/FeedbackForm.php // ... use Symfonycasts\DynamicForms\DependentField; use Symfonycasts\DynamicForms\DynamicFormBuilder; 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 ], ]); $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), ]); }); } }
addDependent()
方法接受 3 个参数
- 要添加的字段的名称;
- 该字段所依赖的字段的名称(或名称);
- 一个回调函数,当表单提交时将被调用。此回调函数将
DependentField
对象作为第一个参数,然后是每个相关字段的值作为后续参数。
幕后,这是通过注册几个表单事件监听器来实现的。当表单首次创建(使用初始数据)时,将执行回调,然后在表单提交时再次执行。这意味着回调可能被调用多次。
渲染字段的方式相同 - 只确保如果它是条件添加的,则确保字段存在
{{ form_start(form) }} {{ form_row(form.rating) }} {% if form.badRatingNotes is defined %} {{ form_row(form.badRatingNotes) }} {% endif %} <button>Send Feedback</button> {{ form_end(form) }}
更新前端
在上一个示例中,当 rating
字段更改时,需要重新渲染表单(或表单的一部分),以便可以添加 badRatingNotes
字段。
此库不会为您处理此操作,但这里有两个主要选项
A) 使用 Live Components
这是最简单的方法:通过在 live component 中渲染您的表单,当表单更改时,它会自动重新渲染。
B) 使用 Symfony UX Turbo
如果您已经在您的网站上使用了 Symfony UX Turbo,则可以快速运行动态表单而无需任何 JavaScript。
或者,您可能想要安装 Symfony UX Turbo,查看文档。
注意
您只需要有 Turbo Frame,如果不需要或不想使用它,可以禁用 Turbo Drive。即:Turbo.session.drive = false;
只需将 <turbo-frame>
添加到您的表单中
<turbo-frame id="rating-form"> {{ form(form) }} </turbo-frame>
从这里开始,您需要做两个小的更改
首先,在您的表单类型
- 您需要在选择字段上添加一个属性,以便在更改时自动提交表单(可能需要根据您的表单进行调整,如果更复杂的话)
- 添加一个提交按钮,这样在控制器中您可以区分自动提交和用户操作
// 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.requestSubmit()'], ]); + // 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), ]); }); } }
其次,在您的控制器中
- 指定表单的操作,这是 Turbo Frame 所需要的
- 通过检查按钮是否被点击来处理自动提交
// 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) 编写自定义 JavaScript
如果您没有使用 Live Components 或 Turbo Frames,您需要编写一些自定义 JavaScript 来监听 rating
字段上的 change
事件,然后通过 AJAX 调用来重新渲染表单。AJAX 调用应将表单提交到其通常的端点(或任何可以提交表单的端点),获取 HTML 响应,提取需要重新渲染的部分,然后替换页面上的 HTML。
这是一个不简单的工作,这个库可能还有改进的空间来使这个过程更容易。如果您有想法,请提交一个 issue!