symfonycasts/dynamic-forms

为 Symfony 表单添加动态/相关字段

v0.1.2 2024-02-20 11:55 UTC

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

完成 - 你可以开始构建动态表单了!

使用方法

设置相关字段分为两部分

  1. PHP 中的使用 - 设置您的 Symfony 表单以处理动态字段;
  2. 更新前端 - 向前端添加代码,以便当字段更改时,表单的一部分会重新渲染。

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 个参数

  1. 要添加的字段的名称;
  2. 该字段所依赖的字段的名称(或名称);
  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),
            ]);
        });
    }
}

其次,在您的控制器中

// 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!