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

安装
注意
在开始之前,请确保您已在您的应用中配置了 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,任何 ChoiceType
或 EntityType
都可以通过添加 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 控件

在表单中使用(使用 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
BaseEntityAutocompleteType
是 ParentEntityAutocompleteType
的新替代品。
有 3 个重要事项
- 该类需要
#[AsEntityAutocompleteField]
属性,以便自动完成系统注意到它。 getParent()
方法必须返回BaseEntityAutocompleteType
。- 在
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 渲染模板。
表单选项参考
所有 ChoiceType
、EntityType
和 TextType
字段都有以下新选项(这些选项也可以在您的自定义 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_fields
和max_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
选项。
警告
只有标量值(string
、integer
、float
、boolean
)、null
和 array
(由与之前提到的相同类型组成)可以作为额外的选项传递。
考虑到以下示例,当首次呈现表单类型时,它将使用在将 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 中添加了传递额外选项的功能。
如果您需要将额外的选项传递给自动完成器,您可以通过以下方式进行操作
实现
接口。
提示
如果您想知道为什么您可能需要使用 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 控制器,可以在任何 select
或 input
元素上激活 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