danilovl / select-autocompleter-bundle
Symfony select ajax autocomleter bundle
Installs: 4 584
Dependents: 0
Suggesters: 0
Security: 0
Stars: 4
Watchers: 2
Forks: 0
Open Issues: 0
Type:symfony-bundle
Requires
- php: ^8.3
- doctrine/doctrine-bundle: ^2
- symfony/form: ^7.0
- symfony/framework-bundle: ^7.0
- symfony/security-bundle: ^7.0
- twig/twig: ^3.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.64
- phpstan/extension-installer: ^1.3
- phpstan/phpstan: ^1.10
- phpstan/phpstan-symfony: ^1.3
- phpunit/phpunit: ^10.2
- symfony/yaml: ^7.0
- dev-master
- v3.7.8
- v3.7.7
- v3.7.6
- v3.7.5
- v3.7.4
- v3.7.3
- v3.7.2
- v3.7.1
- v3.7.0
- v3.6.0
- v3.5.3
- v3.5.2
- v3.5.1
- v3.5.0
- v3.4.1
- v3.4.0
- v3.3.5
- v3.3.4
- v3.3.3
- v3.3.2
- v3.3.1
- v3.3.0
- 3.2.5
- v3.2.4
- v3.2.3
- v3.2.2
- v3.2.1
- v3.2.0
- v3.1.0
- v3.0.9
- v3.0.8
- v3.0.7
- v3.0.6
- v3.0.5
- v3.0.4
- v3.0.3
- v3.0.2
- v3.0.1
- v3.0.0
- v2.1.2
- v2.1.1
- v2.1.0
- v2.0.4
- v2.0.3
- v2.0.2
- v2.0.1
- v2.0.0
- v1.3.0
- v1.2.0
- v1.1.1
- v1.1.0
- v1.0.12
- v1.0.11
- v1.0.10
- v1.0.9
- v1.0.8
- v1.0.7
- v1.0.6
- v1.0.5
- v1.0.4
- v1.0.3
- v1.0.2
- v1.0.1
- v1.0.0
This package is auto-updated.
Last update: 2024-11-06 22:22:32 UTC
README
SelectAutocompleterBundle
About
This is a Symfony bundle which enables the popular Select2 component to be used as a drop-in replacement for a standard fields on a Symfony form.
The main feature of this bundle is that the list of choices is retrieved via a remote ajax call.
Requirements
- PHP 8.3 or higher
- Symfony 7.0 or higher
1. Installation
Install danilovl/select-autocompleter-bundle
package by Composer:
composer require danilovl/select-autocompleter-bundle
<?php // config/bundles.php return [ // ... Danilovl\SelectAutocompleterBundle\SelectAutocompleterBundle::class => ['all' => true] ];
2. Configuration
After installing the bundle, add this route to your routing:
# config/routing.yaml _danilovl_select_autocomopleter: resource: "@SelectAutocompleterBundle/Resources/config/routing.yaml" prefix: /select-autocomplete
System default options for all autocompleters, which will be used if necessary.
# danilovl/select-autocompleter-bundle/src/Resources/config/default.yaml ... default: id_property: 'id' property: 'name' property_search_type: 'any' image_result_width: '100px' image_selection_width: '18px' widget: 'select2_v4' root_alias: 'e' limit: 10 base_template: '@SelectAutocompleter/Form/versions.html.twig' role_prefix: 'ROLE_' select_option: delay: 1000 theme: 'default' language: 'auto' width: 'resolve' amd_base: './' amd_language_base: './i18n/' cache: false cdn: auti: false script: 'https://cdn.jsdelivr.net/npm/select2@4.0.12/dist/js/select2.min.js' link: 'https://cdn.jsdelivr.net/npm/select2@4.0.12/dist/css/select2.min.css' language: 'https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/js/i18n/%%language%%.min.js' security: public_access: false voter: 'danilovl.select_autocompleter.voter.default' condition: 'and' role: [] route: name: 'danilovl_select_autocomplete' parameters: [] extra: []
List of available options which you can change in you project.
This options will be applied for all autocompleters. For example:
# config/config.yaml ... danilovl_select_autocompleter: default_option: widget: 'select2_v4' manager: null id_property: 'id' root_alias: 'e' property: 'name' property_search_type: 'any' image: 'image' image_result_width: '100px' image_selection_width: '18px' limit: 10 base_template: '@SelectAutocompleter/Form/versions.html.twig' cdn: auto: false link: 'https://cdn.jsdelivr.net/npm/select2@4.0.12/dist/css/select2.min.css' script: 'https://cdn.jsdelivr.net/npm/select2@4.0.12/dist/js/select2.min.js' language: 'https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/js/i18n/%%language%%.min.js' select_option: placeholder: "app.search_placeholder" delay: 1000 minimum_input_length: 0 maximum_input_length: 0 minimum_results_for_search: 0 maximum_selection_length: 0 minimum_selection_length: 0 multiple: false width: 'resolve' scroll_after_select: false select_on_close: false theme: 'custom' language: 'auto' amd_base: './' amd_language_base: './i18n/' cache: true security: public_access: false voter: 'danilovl.select_autocompleter.voter.default' role: - ROLE_ADMIN - ROLE_API condition: 'or' to_string: format: "ID %%d: %%s" properties: - 'id' - 'name' where: - 'e.active = true' order_by: createdAt: 'ASC' uptadetAt: 'DESC' route: name: 'custom_select_autocomplete' parameters: [] extra: []
3. Customization default options for all autcompleters
3.1 Widget
By default only one widget select2_v4
is available.
# config/config.yaml ... danilovl_select_autocompleter: default_option: widget: 'select2_v4'
3.2 Cdn
If you want to add default select2.min.js
, select2.min.css
and i18n.js
files on page.
Links are defined in default.yaml
.
# config/config.yaml ... danilovl_select_autocompleter: default_option: cdn: auto: true
Or you can choose a specific script if, for example, you already have some scripts available on the page.
# config/config.yaml ... danilovl_select_autocompleter: default_option: cdn: link: auto script: auto language: auto
Or you can define you own path for script and css files.
# config/config.yaml ... danilovl_select_autocompleter: default_option: cdn: link: 'public/css/select2.min.css' script: 'public/js/select2.min.js' language: 'public/js/language.en.js'
3.3 Select options
For customization select is available following settings.
Text defined in placeholder
will be translated by twig function truns
.
# config/config.yaml ... danilovl_select_autocompleter: default_option: select_option: placeholder: "app.search_placeholder" delay: 1000 minimum_input_length: 1 maximum_input_length: 3 minimum_results_for_search: 5 maximum_selection_length: 0 minimum_selection_length: 0 multiple: true width: false scroll_after_select: false select_on_close: false theme: 'custom' language: 'en' amd_base: './' amd_language_base: './i18n/' cache: false
3.4 toString
Simple __toString
format.
# config/config.yaml ... danilovl_select_autocompleter: default_option: to_string: format: "ID %%d: %%s" properties: - 'id' - 'name'
If to_string
option is auto
then __toString()
method was called by Class.
# config/config.yaml ... danilovl_select_autocompleter: default_option: to_string: auto: true
3.5 Where
Simple where
condition.
# config/config.yaml ... danilovl_select_autocompleter: default_option: where: - 'e.active = true AND e.id > 100' - 'DATE_ADD(e.publishedAt, 10, "day") >= CURRENT_TIMESTAMP()' - 'e.totalAmount >= 50 AND e.totalAmount <= 15000' - 'e.prepared = 1 OR e.sent = 1' - 'e.id IN (1,2)'
Generated SQL query.
where (e.active = true AND e.id > 100) AND (DATE_ADD(e.published_at, 10, "day") >= CURRENT_TIMESTAMP()) AND (e.total_amount >= 50 AND e.total_amount <= 15000) AND (e.prepared = 1 OR e.sent = 1) AND (e.id IN (1,2))
3.6 Order by
Order result by.
# config/config.yaml ... danilovl_select_autocompleter: default_option: order_by: createdAt: 'ASC' uptadetAt: 'DESC'
Generated SQL query.
ORDER BY e.created_at ASC, e.uptadet_at DESC
4. Configuring autocompleters
For Doctrine ORM
you should use key orm
. For Doctrine ODM
you should use key odm
.
The configuration is practically no different for orm
or odm
.
4.1. ORM autocompleters
4.1.1 Simple configuration
Simple configuration.
Identifier name
will be duplicated with prefix type orm.
or odm.
, which can be used for identification autocompleters in forms.
# config/config.yaml ... danilovl_select_autocompleter: orm: - name: 'user' class: 'App:User' property: 'username' - name: 'group' class: 'App:Group' property: 'name' property_search_type: 'equal'
4.1.2 Simple search
start
is LIKE 'search%'
any
is LIKE '%search%'
end
is LIKE 'search%'
equal
is = 'search'
# config/config.yaml ... danilovl_select_autocompleter: orm: - name: 'group' class: 'App:Group' property: 'name' search_simple: name: 'start' text: 'any' descrption: 'and'
4.1.3 Custom search pattern
You can defined custom search pattern. Symbol %
in yaml must be duplicate - %%
.
You must use key word $search
to insert search text into a pattern.
# config/config.yaml ... danilovl_select_autocompleter: orm: - name: 'group' class: 'App:Group' property: 'name' search_pattern: name: 'group_$search%%' description: '%%$search%%' - name: 'product' class: 'App:Product' property: 'name' search_pattern: price: 'EUR%%'
4.1.4 toString
If to_string
option auto
is true
, then __toString()
method will be called by Class.
# config/config.yaml ... danilovl_select_autocompleter: orm: - name: 'group' class: 'App:Group' property: 'name' to_string: auto: true
You could defined custom __toString()
format, sprintf
function will be called.
For each variables in properties
, function (string)
will be called.
Symbol %
in yaml must be duplicate - %%
.
# config/config.yaml ... danilovl_select_autocompleter: orm: - name: 'product' class: 'App:Product' property: 'name' to_string: format: "ID %%d: %%s %%d" properties: - 'id' - 'name' - 'price'
4.1.5 Result ordering
You can add ordering.
# config/config.yaml ... danilovl_select_autocompleter: orm: - name: 'product' class: 'App:Product' property: 'name' order_by: createdAt: 'ASC' uptadetAt: 'DESC'
4.1.6 Call repository method
If you want to use a existing repository method from you project. Other parameters will be ignored.
Repository method should have public
access and return QueryBuilder
or Builder
.
# config/config.yaml danilovl_select_autocompleter: orm: - name: 'product' class: 'App:Product' property: 'name' repository: method: 'createSearchQueryBuilder'
For entity App:Product
will be found Repository
, then method createSearchQueryBuilder
will be called with AutocompleterQuery
and Config
as a parameters.
This means that all the search logic will be processing by the method which you defined.
4.1.7 Overriding default_option
You can override default_option
for specific autocompleter.
# config/config.yaml ... danilovl_select_autocompleter: orm: - name: 'product' class: 'App:Product' id_property: 'id' property: 'name' widget: 'custom_widget' to_string: format: "ID %%d: %%s %%d" properties: - 'id' - 'name' - 'price' search_pattern: name: '%%$search%%' price: 'EUR%%' select_option: placeholder: "app.search_placeholder_product" delay: 1500 minimum_input_length: 1 maximum_input_length: 4 security: public_access: false role: - 'ROLE_USER' - 'ROLE_ADMIN'
5. Security
5.1 By roles
You could restrict access to autcompleter by user roles.
Restrict access for all autocompleters.
# config/config.yaml ... danilovl_select_autocompleter: default_option: security: role: - 'ROLE_USER' - 'ROLE_ADMIN'
You can user condition or
or and
# config/config.yaml ... danilovl_select_autocompleter: default_option: security: role: - 'ROLE_USER' - 'ROLE_ADMIN' condition: 'and'
If the user has no role ROLE_USER
or ROLE_ADMIN
, voter return false
# config/config.yaml ... danilovl_select_autocompleter: default_option: security: role: - 'ROLE_USER' - 'ROLE_ADMIN' condition: 'or'
If the user has at least one of ROLE_USER
or ROLE_ADMIN
, voter return true
Restrict access for some specific autocompleter.
# config/config.yaml ... danilovl_select_autocompleter: orm: - name: 'user' class: 'App:User' security: role: - 'ROLE_USER' - 'ROLE_ADMIN'
5.2 By URL patterns
You could restrict access to autocompleters by securing URL patterns
# config/security.yaml security: access_control: - { path: ^/(%app_locales%)/select-autocompleter/(\w+)/autocomplete, roles: [ROLE_AUTCOMOPLETER, ROLE_ADMIN] }
5.3 By voter
You could create you own voter for autcompleters.
<?php declare(strict_types=1); namespace App\Security\Voter; use Danilovl\SelectAutocompleterBundle\Constant\VoterSupportConstant; use Danilovl\SelectAutocompleterBundle\Interfaces\AutocompleterInterface; use LogicException; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authorization\Voter\Voter; use Symfony\Bundle\SecurityBundle\Security; class CustomAutocompleterVoter extends Voter { private const SUPPORTS = [ VoterSupportConstant::GET_RESULT ]; private Security $security; public function __construct(Security $security) { $this->security = $security; } protected function supports(string $attribute, mixed $subject): bool { if (!in_array($attribute, self::SUPPORTS, true)) { return false; } if (!$subject instanceof AutocompleterInterface) { return false; } } protected function voteOnAttribute(string $attribute, mixed $subject, TokenInterface $token): bool { if ($attribute !== VoterSupportConstant::GET_DATA) { throw new LogicException('This code should not be reached!'); } // custom logic return true; } }
Register new voter as a service.
# config/security.yaml ... services: app.voter.custom: class: App\Security\Voter\CustomAutocompleterVoter public: true arguments: - '@security.helper'
Set voter for all autocompleters.
# config/config.yaml ... danilovl_select_autocompleter: default_option: security: voter: 'app.voter.custom'
Set voter for some specific autocompleter.
# config/config.yaml ... danilovl_select_autocompleter: orm: - name: 'user' class: 'App:User' security: voter: 'app.voter.custom'
5.4 Use isGranted
method in custom autocompleter.
<?php declare(strict_types=1); namespace App\Autocompleter; use Danilovl\SelectAutocompleterBundle\Model\Autocompleter\AutocompleterQuery; use Danilovl\SelectAutocompleterBundle\Model\SelectDataFormat\Item; use Danilovl\SelectAutocompleterBundle\Resolver\Config\AutocompleterConfigResolver; use Danilovl\SelectAutocompleterBundle\Service\OrmAutocompleter; use Symfony\Component\Security\Core\Authorization\Voter\VoterInterface; class CustomShopAutocompleter extends OrmAutocompleter { private ShopRepository $shopRepository; public function __construct( ManagerRegistry $registry, AutocompleterConfigResolver $resolver, ShopRepository $shopRepository ) { parent::__construct($registry, $resolver); $this->shopRepository = $shopRepository; } public function isGranted(): int { return VoterInterface::ACCESS_ABSTAIN; } }
6. Dependent select
Sometimes you need options of a select will be loaded/refreshed by ajax based on selection of another select.
6.1 OnyToMany
For example entity - City
dependent on Country
and Region
.
# config/config.yaml ... danilovl_select_autocompleter: orm: - name: 'autocompleter.country' class: 'App:Country' - name: 'autocompleter.region' class: 'App:Region' - name: 'autocompleter.city' class: 'App:City' dependent_selects: - name: 'dependent_on_country' parent_property: 'country' - name: 'dependent_on_region' parent_property: 'region'
parent_property
is the name of the variable in dependent class.
<?php declare(strict_types=1); namespace App\Entity; #[ORM\Table(name: 'city')] #[ORM\Entity(repositoryClass: CityRepository::class)] #[ORM\HasLifecycleCallbacks] class City { use IdTrait; use TimestampAbleTrait; use LocationTrait; #[ORM\Column(name: 'name', type: Types::STRING, nullable: false)] protected ?string $name = null; #[ORM\ManyToOne(targetEntity: Country::class, inversedBy: 'cities')] #[ORM\JoinColumn(name: 'id_country', referencedColumnName: 'id', nullable: false)] protected ?Country $country = null; #[ORM\ManyToOne(targetEntity: Region::class, inversedBy: 'regions')] #[ORM\JoinColumn(name: 'id_region', referencedColumnName: 'id', nullable: false)] protected ?Region $region = null; }
Parent and dependent fields should be in form together.
parent_field
- name of master field in your FormBuilder
.
<?php declare(strict_types=1); namespace App\Form; use Danilovl\SelectAutocompleterBundle\Form\Type\AutocompleterType; class CityType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { $builder ->add('country', AutocompleterType::class, [ 'autocompleter' => [ 'name' => 'orm.country' ] ]) ->add('cityByCountry', AutocompleterType::class, [ 'autocompleter' => [ 'name' => 'orm.city', 'dependent_select' => [ 'name' => 'dependent_on_country', 'parent_field' => 'cityByCountry' ] ] ]) ->add('region', AutocompleterType::class, [ 'autocompleter' => [ 'name' => 'orm.region' ] ]) ->add('cityByRegion', AutocompleterType::class, [ 'autocompleter' => [ 'name' => 'orm.city', 'dependent_select' => [ 'name' => 'dependent_on_region', 'parent_field' => 'cityByRegion' ] ] ]); } }
6.2 Simple ManyToMany
For example entity - Tag
has many Cheque
# config/config.yaml ... danilovl_select_autocompleter: orm: - name: 'cheque' class: 'App:Cheque' property: 'chequeNumber' - name: 'tag' class: 'App:Tag' dependent_selects: - name: 'cheques' parent_property: 'id' many_to_many: chequesAlies: 'e.cheques'
6.3 Complicated ManyToMany
For example entity - Work
dependent on Firm
through custom entity FirmWork
# config/config.yaml ... danilovl_select_autocompleter: orm: - name: 'firm' class: 'App:Firm' - name: 'work' class: 'App:Work' dependent_selects: - name: 'firms' parent_property: 'id' many_to_many: firmWork: 'e.firms' firm: 'firmWork.firm'
7. Route
7.1 Redefine global route
You can redefine global autocomleter route and add some extra route parameters.
# config/config.yaml ... danilovl_select_autocompleter: default_option: route: name: 'danilovl_select_autocomplete' parameters: [] extra: []
7.2 Redefine autocompleter route
You can redefine specific autocomleter route and add some extra route parameters.
# config/config.yaml ... danilovl_select_autocompleter: orm: - name: 'firm' class: 'App:Firm' route: name: 'danilovl_select_autocomplete_firm' parameters: id: 200 enable_migraiton: 'yes'
8. Using
Simple configuration in form.
You should use 'name' => 'orm.shop'
for identification autocompleter.
<?php declare(strict_types=1); use Danilovl\SelectAutocompleterBundle\Form\Type\AutocompleterType; class CityType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('shop', AutocompleterType::class, [ 'autocompleter' => [ 'name' => 'orm.shop' ], 'required' => true, 'constraints' => [ new NotBlank ] ]); } }
You can override select_option
.
<?php declare(strict_types=1); use Danilovl\SelectAutocompleterBundle\Form\Type\AutocompleterType; class CityType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('shop', AutocompleterType::class, [ 'autocompleter' => [ 'name' => 'orm.shop', 'multiple' => true, 'select_option' => [ 'placeholder' => 'app.form_type_placeholder', 'delay' => 0, 'minimum_input_length' => 2 ] ], 'required' => true, 'constraints' => [ new NotBlank ] ]); } }
9. Custom Autocompleter
You can create your own custom autocompleter.
# config/config.yaml ... danilovl_select_autocompleter: orm: - name: 'customShop' class: 'App:Shop'
<?php declare(strict_types=1); namespace App\Autocompleter; use Danilovl\SelectAutocompleterBundle\Service\OrmAutocompleter; class CustomShopAutocompleter extends OrmAutocompleter { }
If the standard functionality is not enough, or you want to reuse you existing code.
<?php declare(strict_types=1); namespace App\Autocompleter; use Danilovl\SelectAutocompleterBundle\Model\Autocompleter\AutocompleterQuery; use Danilovl\SelectAutocompleterBundle\Model\SelectDataFormat\Item; use Danilovl\SelectAutocompleterBundle\Resolver\Config\AutocompleterConfigResolver; use Danilovl\SelectAutocompleterBundle\Service\OrmAutocompleter; class CustomShopAutocompleter extends OrmAutocompleter { private ShopRepository $shopRepository; public function __construct( ManagerRegistry $registry, AutocompleterConfigResolver $resolver, ShopRepository $shopRepository ) { parent::__construct($registry, $resolver); $this->shopRepository = $shopRepository; } public function createQueryBuilder(): QueryBuilder { return $this->shopRepository->baseQueryBuilder(); } protected function createAutocompleterQueryBuilder(AutocompleterQuery $query): QueryBuilder { return $this->shopRepository->queryBuilderFindNearestShopByName( $query->search, $this->getOffset($query), $this->config->limit, $query->extra ); } public function transformObjectToItem($object): Item { $item = new Item; $item->option = $object->getIdentificator(); $item->value = sprintf('%s (%s,%s)', $object->getName(), $object->getAddress(), $object->getCity()->getName()); return $item; } }
If you need additional parameters in request you can defined extra
parameter which will be available in AutocompleterQuery
.
<?php declare(strict_types=1); namespace App\Form; use App\Autocompleter\CustomAutocompleter; class CityType extends AbstractType { public function buildForm(FormBuilderInterface $builder, array $options): void { $builder->add('shop', CustomAutocompleter::class, [ 'autocompleter' => [ 'name' => 'orm.customShop', 'extra' => [ 'language' => 'en' ] ], 'required' => true, 'constraints' => [ new NotBlank ] ]); } }
Then you must defined new autocompleter service in you services.yaml
with danilovl_select_autocompleter.autocompleter
tag and alias
name.
app.autocompleter.custom: class: App\Autocompleter\CustomShopAutocompleter tags: - {name: 'danilovl.select_autocompleter.autocompleter', alias: 'own.customAutocompleter'}
Or you can use autocompleter attribute AsAutocompleter
with require alias
field.
<?php declare(strict_types=1); namespace App\Autocompleter; use Danilovl\SelectAutocompleterBundle\Attribute\AsAutocompleter; #[AsAutocompleter(alias: 'own.customShop')] class CustomShopAutocompleter extends OrmAutocompleter { }
Or you can use symfony service attribute AutoconfigureTag
with require name
and alias
parameters.
<?php declare(strict_types=1); namespace App\Autocompleter; use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag; #[AutoconfigureTag(name: 'danilovl.select_autocompleter.autocompleter', attributes: ['alias' => 'own.customAutocompleter'])] class CustomShopAutocompleter extends OrmAutocompleter { }
10. Custom autocompleter widget template
Create you own custom autocompleter template which extends versions.html.twig
and redefine the blocks you need.
{# templates/autocompleter/custom_widget_template.html.twig #} {% extends '@SelectAutocompleter/Form/versions.html.twig' %} {% block cdn %} {# new code #} {% endblock %} {% block style %} {# new code #} {% endblock %} {% block select_tag %} {# new code #} {% endblock %} {% block input %} {# new code #} {% endblock %} {% block options %} {# new code #} {% endblock %} {% block options_data %} {# new code #} {% endblock %} {% block options_ajax %} {# new code #} {% endblock %} {% block options_ajax_data %} {# new code #} {% endblock %} {% block initialize %} {# new code #} {% endblock %} {% block select_event_selecting %} {# new code #} {% endblock %} {% block select_event_unselecting %} {# new code #} {% endblock %} {% block widget_NEW_WIDGET_NAME %} {# new code #} {% endblock %}
Then you need to add path for new custom template to config.
For all autocompleters.
# config/config.yaml ... danilovl_select_autocompleter: default_option: base_template: 'autocompleter/custom_widget_template.html.twig'
Or for some specific autocompleter.
# config/config.yaml ... danilovl_select_autocompleter: orm: - name: 'user' class: 'App:User' property: 'username' base_template: 'autocompleter/custom_widget_template.html.twig'
License
The SelectAutocompleterBundle is open-sourced software licensed under the MIT license.