danilovl/select-autocompleter-bundle
Installation:
composer require danilovl/select-autocompleter-bundle
Register the bundle in config/bundles.php:
Danilovl\SelectAutocompleterBundle\SelectAutocompleterBundle::class => ['all' => true],
Basic Configuration:
Add to config/packages/danilovl_select_autocompleter.yaml:
danilovl_select_autocompleter:
select2:
theme: "bootstrap4" # or 'bootstrap5', 'classic', 'bootstrap4-dark'
width: "100%"
placeholder: "Search..."
First Use Case:
Create a form type extending AbstractAutocompleterType:
use Danilovl\SelectAutocompleterBundle\Form\Type\AbstractAutocompleterType;
class UserAutocompleteType extends AbstractAutocompleterType
{
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults([
'class' => User::class,
'choice_label' => 'fullName',
'ajax_route' => 'user_autocomplete',
'min_input_length' => 2,
]);
}
}
Route for Autocomplete:
Define a route in config/routes.yaml:
user_autocomplete:
path: /api/users/autocomplete
controller: App\Controller\UserAutocompleteController::class
methods: GET
Controller:
use Danilovl\SelectAutocompleterBundle\Controller\AutocompleteControllerTrait;
class UserAutocompleteController extends AbstractController
{
use AutocompleteControllerTrait;
public function __invoke(Request $request, UserRepository $userRepository)
{
return $this->handleAutocomplete($request, $userRepository, 'fullName');
}
}
Use in Form:
$builder->add('user', UserAutocompleteType::class);
Replace standard EntityType with AbstractAutocompleterType:
// Before
$builder->add('user', EntityType::class, ['class' => User::class]);
// After
$builder->add('user', UserAutocompleteType::class);
Override default options in configureOptions:
$resolver->setDefaults([
'ajax_route' => 'custom_route',
'min_input_length' => 1,
'multiple' => true,
'allow_clear' => true,
'select2_options' => [
'ajax' => [
'dataType' => 'json',
'delay' => 250,
],
],
]);
Pass dynamic parameters via ajax_route_parameters:
$resolver->setDefaults([
'ajax_route' => 'department_autocomplete',
'ajax_route_parameters' => ['company_id' => $companyId],
]);
Customize the response format in the controller:
public function __invoke(Request $request, ProductRepository $productRepository)
{
$results = $productRepository->findByName($request->query->get('term'));
return $this->json(array_map(function ($product) {
return [
'id' => $product->getId(),
'text' => $product->getName(),
'price' => $product->getPrice(),
];
}, $results));
}
Update the form type to handle custom fields:
$resolver->setDefaults([
'template' => '@DanilovlSelectAutocompleter/autocompleter_widget.html.twig',
'select2_options' => [
'templateResult' => 'customTemplate',
'templateSelection' => 'customTemplate',
],
]);
Add custom Twig templates (e.g., templates/custom_autocomplete.html.twig):
{# templates/custom_autocomplete.html.twig #}
<div class="custom-autocomplete">
{{ form_widget(form) }}
<span class="price">{{ data.price }}</span>
</div>
Use the bundle with API Platform entities:
$resolver->setDefaults([
'class' => Product::class,
'ajax_route' => 'api_products',
'ajax_route_parameters' => [],
'choice_label' => 'name',
'api_platform' => true, // Enable API Platform integration
]);
Implement pagination in the autocomplete controller:
public function __invoke(Request $request, UserRepository $userRepository)
{
$page = $request->query->getInt('page', 1);
$limit = 10;
$users = $userRepository->findByNamePaginated(
$request->query->get('term'),
$page,
$limit
);
$data = array_map(function ($user) {
return ['id' => $user->getId(), 'text' => $user->getFullName()];
}, $users);
return $this->json([
'results' => $data,
'pagination' => [
'page' => $page,
'last_page' => ceil($users->getTotalItems() / $limit),
],
]);
}
Update the form type to handle pagination:
$resolver->setDefaults([
'select2_options' => [
'ajax' => [
'data' => function ($params) {
return [
'term' => $params['term'],
'page' => $params['page'] ?? 1,
];
},
'processResults' => function ($data) {
return [
'results' => $data['results'],
'pagination' => [
'more' => $data['pagination']['page'] < $data['pagination']['last_page'],
],
];
},
],
],
]);
Combine with Symfony UX for enhanced interactivity:
// assets/controllers/autocomplete_controller.js
import { Controller } from '@hotwired/stimulus';
export default class extends Controller {
connect() {
this.element.addEventListener('change', (event) => {
// Handle selection changes
});
}
}
Update Twig template:
{{ form_widget(form) }}
{{ stimulus_controller('autocomplete', {'value': form.vars.value|default('')}) }}
Listen to form events for dynamic updates:
$builder->addEventListener(FormEvents::PRE_SET_DATA, function (FormEvent $event) {
$form = $event->getForm();
$data = $event->getData();
if ($data) {
$form->add('custom_field', TextType::class, [
'data' => $data->getCustomField(),
]);
}
});
Customize error messages:
$resolver->setDefaults([
'error_bubbling' => true,
'constraints' => [
new NotBlank(),
new Callback([$this, 'validateUser']),
],
]);
public function validateUser($value, ExecutionContextInterface $context)
{
if ($value && !$value->isActive()) {
$context->buildViolation('User is inactive.')
->atPath('user')
->addViolation();
}
}
Offload heavy processing to the server:
$resolver->setDefaults([
'ajax_route' => 'user_autocomplete_processed',
'select2_options' => [
'ajax' => [
'beforeSend' => function ($jqXHR, settings) {
settings.data.push({ name: 'process', value: 'heavy' });
},
],
],
]);
# config/packages/nelmio_cors.yaml
nelmio_cors:
defaults:
allow_origin: ['*']
allow_methods: ['GET', 'POST']
allow_headers: ['Content-Type']
expose_headers: []
max_age: 3600
hosts: []
origin_regex: false
Install NelmioCorsHow can I help you explore Laravel packages today?