自動完成 <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()
方法的第 3 個參數,因為這些選項在 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 Render Templates。
表單選項參考
所有 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
欄位時定義的 query_builder
到 FoodForm
。但是,當 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
功能,請參閱自動完成 <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 框架相同的向後相容性承諾:https://symfony.dev.org.tw/doc/current/contributing/code/bc.html