跳到內容

自動完成 <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() 方法的第 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

表單選項參考

所有 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 欄位時定義的 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 中新增。

如果您需要將額外選項傳遞至自動完成器,您可以透過以下方式執行
實作 \Symfony\UX\Autocomplete\OptionsAwareEntityAutocompleterInterface
介面。

提示

如果您想知道為什麼您可能需要使用 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 控制器,可以在任何 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 框架相同的向後相容性承諾:https://symfony.dev.org.tw/doc/current/contributing/code/bc.html

這個作品,包含程式碼範例,依據創用CC 姓名標示-相同方式分享 3.0 授權條款授權。
目錄
    版本