跳到内容

Autocomplete <select>

编辑此页

将您的 EntityTypeChoiceType任何 <select> 元素转换为 Ajax 驱动的自动完成智能 UI 控件(利用 Tom Select

Demo of an autocomplete-enabled select element

安装

注意

在开始之前,请确保您已在您的应用中配置了 StimulusBundle

使用 Composer 和 Symfony Flex 安装捆绑包

1
$ composer require symfony/ux-autocomplete

如果您正在使用 WebpackEncore,请安装您的 assets 并重启 Encore(如果您正在使用 AssetMapper,则不需要)

1
2
$ npm install --force
$ npm run watch

在表单中使用(不使用 Ajax)

如果您正在使用 Symfony Form,任何 ChoiceTypeEntityType 都可以通过添加 autocomplete 选项转换为 Tom Select 驱动的 UI 控件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// src/Form/AnyForm.php
// ...

class AnyForm extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('food', EntityType::class, [
                'class' => Food::class,
                'placeholder' => 'What should we eat?',
+                'autocomplete' => true,
            ])

            ->add('portionSize', ChoiceType::class, [
                'choices' => [
                    'Choose a portion size' => '',
                    'small' => 's',
                    'medium' => 'm',
                    'large' => 'l',
                    'extra large' => 'xl',
                    'all you can eat' => '∞',
                ],
+                'autocomplete' => true,
            ])
        ;
    }
}

这就是您所需要的全部!当您刷新时,Autocomplete Stimulus 控制器会将您的 <select> 元素转换为智能 UI 控件

Screenshot of a Food select with Tom Select

在表单中使用(使用 Ajax)

在前面的示例中,自动完成发生在“本地”:所有选项都加载到页面上并用于搜索。

如果您正在使用具有许多可能选项的 EntityType,则更好的选择是通过 AJAX 加载选项。 这也允许您搜索比“显示”文本更多的字段。

要将您的字段转换为 Ajax 驱动的自动完成,您需要创建一个新的“表单类型”类来表示您的字段。 如果您安装了 MakerBundle,您可以运行

1
$ php bin/console make:autocomplete-field

或者,手动创建字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// src/Form/FoodAutocompleteField.php
// ...

use Symfony\Component\Form\AbstractType;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\UX\Autocomplete\Form\AsEntityAutocompleteField;
use Symfony\UX\Autocomplete\Form\BaseEntityAutocompleteType;

#[AsEntityAutocompleteField]
class FoodAutocompleteField extends AbstractType
{
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults([
            'class' => Food::class,
            'placeholder' => 'What should we eat?',

            // choose which fields to use in the search
            // if not passed, *all* fields are used
            //'searchable_fields' => ['name'],

            // if the autocomplete endpoint needs to be secured
            //'security' => 'ROLE_FOOD_ADMIN',

            // ... any other normal EntityType options
            // e.g. query_builder, choice_label
        ]);
    }

    public function getParent(): string
    {
        return BaseEntityAutocompleteType::class;
    }
}

2.13

BaseEntityAutocompleteTypeParentEntityAutocompleteType 的新替代品。

有 3 个重要事项

  1. 该类需要 #[AsEntityAutocompleteField] 属性,以便自动完成系统注意到它。
  2. getParent() 方法必须返回 BaseEntityAutocompleteType
  3. configureOptions() 内部,您可以使用您需要的任何普通 EntityType 选项以及一些额外的选项来配置您的字段(请参阅 表单选项参考)。

创建此类后,在您的表单中使用它

1
2
3
4
5
6
7
8
9
10
11
12
// src/Form/AnyForm.php
// ...

class AnyForm extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
+            ->add('food', FoodAutocompleteField::class)
        ;
    }
}

注意

避免将任何选项传递给 ->add() 方法的第三个参数,因为这些选项在 Ajax 调用以获取结果期间不会被使用。 相反,将所有选项包含在自定义类(FoodAutocompleteField)中,或将它们作为 额外的选项 传递。

恭喜! 您的 EntityType 现在是 Ajax 驱动的了!

样式化 Tom Select

在您的 assets/controllers.json 文件中,您应该看到一行自动包含 Tom Select 的 CSS 文件,这将为您提供基本样式。

如果您正在使用 Bootstrap,请将 tom-select.default.css 设置为 false,并将 tom-select.bootstrap5.css 设置为 true

1
2
3
4
"autoimport": {
    "tom-select/dist/css/tom-select.default.css": false,
    "tom-select/dist/css/tom-select.bootstrap5.css": true
}

要进一步自定义,您可以使用您自己的自定义 CSS 覆盖类,甚至控制 Tom Select 各个部分的呈现方式。 请参阅 Tom Select 渲染模板

表单选项参考

所有 ChoiceTypeEntityTypeTextType 字段都有以下新选项(这些选项也可以在您的自定义 Ajax 自动完成类中使用,例如上面的 FoodAutocompleteField

autocomplete (默认值: false)
设置为 true 以在您的 select 元素上激活 Stimulus 插件。
tom_select_options (默认值: [])
使用此选项设置自定义 Tom Select 选项。 如果您需要使用 JavaScript 设置选项,请参阅 扩展 Tom Select
options_as_html (默认值: false)
如果您的选项(例如 choice_label)包含 HTML,则设置为 true。 如果您的自动完成是 AJAX 驱动的,则不需要此选项。
autocomplete_url (默认值: null)
通常您不需要手动设置此选项。 但是,您可以手动创建一个自动完成-Ajax 端点(例如,对于自定义 ChoiceType),然后将其设置为将字段更改为 AJAX 驱动的选择。
loading_more_text (默认值: '加载更多结果...')
在获取更多结果时,在列表底部呈现。 此消息使用 AutocompleteBundle 域自动翻译。
no_results_found_text (默认值: '未找到结果')
当未找到匹配结果时呈现。 此消息使用 AutocompleteBundle 域自动翻译。
no_more_results_text (默认值: '没有更多结果')
在显示匹配结果后,在列表底部呈现。 此消息使用 AutocompleteBundle 域自动翻译。

对于 Ajax 驱动的自动完成字段类(即 getParent() 返回 BaseEntityAutocompleteType 的类),除了上述选项外,您还可以传递

searchable_fields (默认值: null)
将此设置为实体上应在搜索匹配选项时使用的字段数组。 默认情况下(即 null),将搜索实体上的所有字段。 关系字段也可以使用 - 例如,如果您的实体具有 category 关系属性,则可以使用 category.name
security (默认值: false)

保护 Ajax 端点。 默认情况下,任何用户都可以访问该端点。 要保护它,请将 security 传递给应要求访问该端点的字符串角色(例如 ROLE_FOOD_ADMIN)。 或者,传递一个回调并返回 true 以授予访问权限,或返回 false 以拒绝访问权限

1
2
3
4
5
6
7
use Symfony\Bundle\SecurityBundle\Security;

[
    'security' => function(Security $security): bool {
        return $security->isGranted('ROLE_FOO');
    },
];
filter_query (默认值: null)

如果您想完全控制为“搜索结果”执行的查询,请使用此选项。 这与 searchable_fieldsmax_results 不兼容

1
2
3
4
5
6
7
8
9
10
[
    'filter_query' => function(QueryBuilder $qb, string $query, EntityRepository $repository) {
        if (!$query) {
            return;
        }

        $qb->andWhere('entity.name LIKE :filter OR entity.description LIKE :filter')
            ->setParameter('filter', '%'.$query.'%');
    },
];
max_results (默认值: 10)
允许您控制自动完成端点返回的最大结果数。
min_characters (默认值: 3)
允许您控制加载结果所需的最小字符数。
preload (默认值: focus)
设置为 focus 以在控件收到焦点时调用 load 函数。 设置为 true 以在控件初始化时(使用空搜索)调用 load。 设置为 false 以在控件收到焦点时不调用 load 函数。
extra_options (默认值 [])
允许您为基于 Ajax 的自动完成字段传递额外的选项。

将额外的选项传递给 Ajax 驱动的自动完成

2.14

在 Autocomplete 2.14 中添加了传递额外选项的功能。

当字段在 Ajax 调用中呈现时,自动完成字段选项不会被保留。 因此,默认情况下,诸如基于当前表单数据排除某些选项之类的功能是不可能的。

为了部分避免此限制,添加了 extra_options 选项。

警告

只有标量值(stringintegerfloatboolean)、nullarray(由与之前提到的相同类型组成)可以作为额外的选项传递。

考虑到以下示例,当首次呈现表单类型时,它将使用在将 food 字段添加到
FoodForm 时定义的 query_builder。 但是,当 Ajax 用于获取结果时,在后续渲染中,将使用默认的 query_builder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// src/Form/FoodForm.php
// ...

class FoodForm extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $currentFoodId = $builder->getData()->getId();

        $builder
            ->add('food', FoodAutocompleteField::class, [
                'query_builder' => function (EntityRepository $er) {
                    return $er->createQueryBuilder('o')
                        ->andWhere($qb->expr()->notIn('o.id', [$currentFoodId]));
                };
            ])
        ;
    }
}

如果某些食物可以由其他食物组成,我们可能想要排除
“根”食物从可用食物列表中排除。 为了实现这一点,我们可以删除
从上面的示例中删除 query_builder 选项,并将 excluded_foods 额外选项传递给 FoodAutocompleteField

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// src/Form/FoodForm.php
// ...

class FoodForm extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $currentFoodId = $builder->getData()->getId();

        $builder
            ->add('food', FoodAutocompleteField::class, [
                'extra_options' => [
                    'excluded_foods' => [$currentFoodId],
                ],
            )
        ;
    }
}

extra_options 的魔力在于,每次进行 Ajax 调用时,它都会传递给 FoodAutocompleteField。 因此,现在我们可以在 FoodAutocompleteField 的默认 query_builder 中使用 excluded_foods 额外选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// src/Form/FoodAutocompleteField.php
// ...

use Symfony\UX\Autocomplete\Form\AsEntityAutocompleteField;
use Symfony\UX\Autocomplete\Form\BaseEntityAutocompleteType;

#[AsEntityAutocompleteField]
class FoodAutocompleteField extends AbstractType
{
    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            // ...
            'query_builder' => function (Options $options) {
                return function (EntityRepository $er) use ($options) {
                    $qb = $er->createQueryBuilder('o');

                    $excludedFoods = $options['extra_options']['excluded_foods'] ?? [];
                    if ([] !== $excludedFoods) {
                        $qb->andWhere($qb->expr()->notIn('o.id', $excludedFoods));
                    }

                    return $qb;
                };
            }
        ]);
    }

    public function getParent(): string
    {
        return BaseEntityAutocompleteType::class;
    }
}

与 TextType 字段一起使用

以上所有选项也可以与 TextType 字段一起使用

1
2
3
4
5
6
7
8
9
10
11
12
$builder
    // ...
    ->add('tags', TextType::class, [
        'autocomplete' => true,
        'tom_select_options' => [
            'create' => true,
            'createOnBlur' => true,
            'delimiter' => ',',
        ],
        // 'autocomplete_url' => '... optional: custom endpoint, see below',
    ])
;

<input> 字段不会有任何自动完成功能,但它允许用户输入新选项并在框中将它们视为漂亮的“项目”。 提交时,所有选项(由 delimiter 分隔)将作为字符串发送。

可以通过 autocomplete_url 选项向其添加自动完成功能 - 但您可能需要创建自己的 自定义自动完成端点

自定义 AJAX URL/路由

2.7

在 Twig Components 2.7 中添加了指定路由的功能。

Autocomplete 组件使用的 Ajax 调用的默认路由是 /autocomplete/{alias}/。 有时,自定义此 URL 可能很有用 - 例如,使 URL 位于特定的防火墙下。

要使用另一个路由,首先声明它

1
2
3
4
# config/routes/attributes.yaml
ux_entity_autocomplete_admin:
    controller: ux.autocomplete.entity_autocomplete_controller
    path: '/admin/autocomplete/{alias}'

然后在属性上指定此新路由

1
2
3
4
5
6
// src/Form/FoodAutocompleteField.php
#[AsEntityAutocompleteField(route: 'ux_entity_autocomplete_admin')]
class FoodAutocompleteField
{
    // ...
}

扩展 Tom Select

自定义 Tom Select 的最简单方法是通过您传递给字段的 tom_select_options 选项。 这对于简单的操作(例如 Tom Select 的 loadingClass 选项,它设置为字符串)非常有效。 但是其他选项(例如 onInitialize)必须通过 JavaScript 设置。

为此,创建一个自定义 Stimulus 控制器并监听核心 Stimulus 控制器分发的以下一个或两个事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// assets/controllers/custom-autocomplete_controller.js
import { Controller } from '@hotwired/stimulus';

export default class extends Controller {
    initialize() {
        this._onPreConnect = this._onPreConnect.bind(this);
        this._onConnect = this._onConnect.bind(this);
    }

    connect() {
        this.element.addEventListener('autocomplete:pre-connect', this._onPreConnect);
        this.element.addEventListener('autocomplete:connect', this._onConnect);
    }

    disconnect() {
        // You should always remove listeners when the controller is disconnected to avoid side-effects
        this.element.removeEventListener('autocomplete:connect', this._onConnect);
        this.element.removeEventListener('autocomplete:pre-connect', this._onPreConnect);
    }

    _onPreConnect(event) {
        // TomSelect has not been initialized - options can be changed
        console.log(event.detail.options); // Options that will be used to initialize TomSelect
        event.detail.options.onChange = (value) => {
            // ...
        };
    }

    _onConnect(event) {
        // TomSelect has just been initialized and you can access details from the event
        console.log(event.detail.tomSelect); // TomSelect instance
        console.log(event.detail.options); // Options used to initialize TomSelect
    }
}

注意

扩展控制器应尽早加载(删除 /* stimulusFetch: 'lazy' */),以便它可以侦听原始控制器分发的事件。

然后,更新您的字段配置以使用您的新控制器(它将与核心 Autocomplete 控制器一起使用)

1
2
3
4
5
6
7
8
$builder
    ->add('food', EntityType::class, [
        'class' => Food::class,
        'autocomplete' => true,
+        'attr' => [
+            'data-controller' => 'custom-autocomplete',
+        ],
    ])

或者,如果使用自定义 Ajax 类,请将 attr 选项添加到您的 configureOptions() 方法中

1
2
3
4
5
6
7
8
9
public function configureOptions(OptionsResolver $resolver)
{
    $resolver->setDefaults([
        'class' => Food::class,
+        'attr' => [
+            'data-controller' => 'custom-autocomplete',
+        ],
    ]);
}

高级:创建自动完成器(不使用表单)

如果您未使用表单系统,则可以创建一个 Ajax 自动完成端点,然后 手动初始化 Stimulus 控制器。 这仅适用于 Doctrine 实体:如果您自动完成的不是实体,请参阅 手动使用 Stimulus 控制器

要公开端点,请创建一个实现 Symfony\UX\Autocomplete\EntityAutocompleterInterface 的类,并使用 ux.entity_autocompleter 标记此服务,包括 alias 选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
namespace App\Autocompleter;

use App\Entity\Food;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
use Symfony\UX\Autocomplete\EntityAutocompleterInterface;

#[AutoconfigureTag('ux.entity_autocompleter', ['alias' => 'food'])]
class FoodAutocompleter implements EntityAutocompleterInterface
{
    public function getEntityClass(): string
    {
        return Food::class;
    }

    public function createFilteredQueryBuilder(EntityRepository $repository, string $query): QueryBuilder
    {
        return $repository
            // the alias "food" can be anything
            ->createQueryBuilder('food')
            ->andWhere('food.name LIKE :search OR food.description LIKE :search')
            ->setParameter('search', '%'.$query.'%')

            // maybe do some custom filtering in all cases
            //->andWhere('food.isHealthy = :isHealthy')
            //->setParameter('isHealthy', true)
        ;
    }

    public function getLabel(object $entity): string
    {
        return $entity->getName();
    }

    public function getValue(object $entity): string
    {
        return $entity->getId();
    }

    public function isGranted(Security $security): bool
    {
        // see the "security" option for details
        return true;
    }
}

因此,您现在可以通过 ux_entity_autocomplete 路由和 alias 路由通配符自动完成您的 Food 实体

1
{{ path('ux_entity_autocomplete', { alias: 'food' }) }}

通常,您会将此 URL 传递给 Stimulus 控制器,这将在下一节中讨论。

将额外的选项传递给自动完成器

2.14

在 Autocomplete 2.14 中添加了传递额外选项的功能。

如果您需要将额外的选项传递给自动完成器,您可以通过以下方式进行操作
实现 \Symfony\UX\Autocomplete\OptionsAwareEntityAutocompleterInterface
接口。

提示

如果您想知道为什么您可能需要使用 extra_options
功能,请参阅 Autocomplete <select>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;
use Sylius\Component\Product\Model\ProductAttributeInterface;
use Symfony\Bundle\SecurityBundle\Security;
use Symfony\UX\Autocomplete\OptionsAwareEntityAutocompleterInterface;

#[AutoconfigureTag('ux.entity_autocompleter', ['alias' => 'food'])]
class FoodAutocompleter implements OptionsAwareEntityAutocompleterInterface
{
+   /**
+    * @var array<string, mixed>
+    */
+   private array $options = [];

// ...

+   public function createFilteredQueryBuilder(EntityRepository $repository, string $query): QueryBuilder
+   {
+       $excludedFoods = $this->options['extra_options']['excluded_foods'] ?? [];
+
+       $qb = $repository->createQueryBuilder('o');
+
+       if ($productAttributesToBeExcluded !== []) {
+           $qb
+               ->andWhere($qb->expr()->notIn('o.id', $excludedFoods));
+               ->setParameter('excludedFoods', $excludedFoods)
+           ;
+       }
+
+       return $qb;
+   }

+   /**
+   * @param array<string, mixed> $options
+   */
+   public function setOptions(array $options): void
+   {
+       $this->options = $options;
+   }

手动使用 Stimulus 控制器

此库附带一个 Stimulus 控制器,可以在任何 selectinput 元素上激活 Tom Select。 这可以在表单组件之外使用。 例如

1
2
3
4
<select
    name="food"
    {{ stimulus_controller('symfony/ux-autocomplete/autocomplete') }}
>

就是这样! 如果您希望通过 Ajax 自动完成选项,请传递 url 值,如果您创建了 自定义自动完成器,这将非常有效

1
2
3
4
5
6
<select
    name="food"
    {{ stimulus_controller('symfony/ux-autocomplete/autocomplete', {
        url: path('ux_entity_autocomplete', { alias: 'food' })
    }) }}
>

注意

如果您想创建一个不是用于实体的 AJAX 自动完成端点,则需要手动创建它。 唯一的要求是响应返回具有以下格式的 JSON

1
2
3
4
5
6
{
    "results": [
        { "value": "1", "text": "Pizza" },
        { "value": "2", "text":"Banana"}
    ]
}

对于使用 Tom Select 选项组,格式如下

1
2
3
4
5
6
7
8
9
{
    "results": {
        "options": [
            { "value": "1", "text": "Pizza", "group_by": ["food"] },
            { "value": "2", "text": "Banana", "group_by": ["food"] }
        ],
        "optgroups": [{ "value": "food", "label": "food" }]
    }
}

有了这个之后,生成到您的控制器的 URL 并将其传递给 stimulus_controller() Twig 函数的 url 值,或传递给您的表单字段的 autocomplete_url 选项。 用户输入的搜索词作为名为 query 的查询参数传递。

除了 url 之外,Stimulus 控制器还具有各种其他值,包括 tomSelectOptions。 有关完整列表,请参阅 controller.ts 文件。

单元测试

在为您的表单编写单元测试时,使用 TypeTestCase 类,您可以考虑注册所需的类型扩展 AutocompleteChoiceTypeExtension,如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// tests/Form/Type/TestedTypeTest.php
namespace App\Tests\Form\Type;

use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\UX\Autocomplete\Form\AutocompleteChoiceTypeExtension;

class TestedTypeTest extends TypeTestCase
{
    protected function getTypeExtensions(): array
    {
        return [
            new AutocompleteChoiceTypeExtension(),
        ];
    }

    // ... your tests
}

向后兼容性承诺

此捆绑包旨在遵循与 Symfony 框架相同的向后兼容性承诺:http://symfony.ac.cn/doc/current/contributing/code/bc.html

这项工作,包括代码示例,根据 Creative Commons BY-SA 3.0 许可获得许可。
TOC
    版本