I have the following problem. I have two tables in my Symony 6 project:
- Articles
- Variants
These two tables are linked to each other in that an article can have several variants and a variant always belongs to one article.
Now I have a form to create a new variant in which I have to select the corresponding article. For this I want to use the EntityType Field as a dropdown.
The problem is that I have up to 100,000 articles in the article table which causes hughe loading problems when loading all options at once. Therefore, I thought about using Select2 so that a query is only made after 1-2 characters have been entered and only the articles that match the search are displayed in the dropdown.
With a "normal" ChoiceType I managed it in another context, but I don't know how to transfer this to the EntityType.
Below is the code for the working ChoiceType:
JS:
$(#sizes).select2({
ajax: {
url: {{ path("text_ajax_load_select_option") }},
dataType: 'json',
type: 'GET',
data: function (params) {
var queryParameters = {
term: params.term,
}
return queryParameters;
},
processResults: function (data) {
return {
results: $.map(data.selectOptions, function (selectOptions) {
return {
text: selectOptions.title,
id: selectOptions.title
}
})
};
}
}
})
Controller:
#[Route('/ajax_load_select_options', name: 'test_ajax_load_select_option', methods: ['GET', 'POST'])]
public function getDropdownOptions(ChoicesService $service, Request $request, SizesRepository $repository) {
$sizes = $service->getData($sizesRepository, $request);
return $this->json(['selectOptions' => $sizes],200);
}
ChoicesService:
class ChoicesService {
public function getData($repository, $request) {
$searchTerm = $request->query->get('term');
// Get results from the Repository
$results = $repository->getDropdownData($searchTerm);
return $results;
}
}
Function in Repository:
public function getDropdownData($searchTerm="") {
$query = $this->createQueryBuilder($this->table);
if($searchTerm != "") {
$this->searchQuery .= $this->table.'.title LIKE '.'\'%'.$searchTerm.'%\'';
$query->andWhere($this->searchQuery);
}
$results = $query->getQuery()->getResult();
return $results;
}
ArtikelType:
class ArtikelType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options): void
{
// Create the fields for the form
$builder
.. other fields
->add('sizes', ChoiceType::class, [
'multiple'=>true,
'label' => "Sizes",
'choices' => [
],
'attr' => [
'class' => 'dropdown'
]
])
;
// Add EventSubscribers for specific fields (here: sizes and colors)
$builder->addEventSubscriber(new SelectFieldListener([[$sizes="sizes",$label="Sizes"]]));
}
EventListener:
class SelectFieldListener implements EventSubscriberInterface
{
public function __construct($fields=array()) {
$this->fields = $fields;
}
public static function getSubscribedEvents(): array {
return [
FormEvents::PRE_SET_DATA => 'onPreSetData',
FormEvents::PRE_SUBMIT => 'onPreSubmit',
];
}
//This Event is used to prepopulate the previous select options for all Select Fields in the corresponding form
public function onPreSetData(FormEvent $event, $test): void
{
// Get the parent form
$form = $event->getForm();
// Get the data for the choice field
$data = $event->getData();
// Create the Arrays for the choices
$choices = array();
$choices_formatted = array();
foreach($this->fields as $field) {
$functionName = 'get'.ucfirst($field[0]);
$choices[$field[0]] = $data->$functionName();
foreach($choices[$field[0]] as $key=>$value){
$choices_formatted[$field[0]][$value] = $value;
$form->add(
$field[0],
ChoiceType::class,
[
'multiple'=>true,
'label' => $field[1],
'choices'=> $choices_formatted[$field[0]],
'attr' => [
'class' => 'dropdown'
]
]
);
}
}
}
public function onPreSubmit(FormEvent $event) {
// Get the parent form
$form = $event->getForm();
// Get the data for the choice field
$data = $event->getData();
// Create the Arrays for the choices
$choices = array();
$choices_formatted = array();
foreach($this->fields as $field) {
$choices[$field[0]] = $data[$field[0]];
foreach($choices[$field[0]] as $key=>$value){
$choices_formatted[$field[0]][$value] = $value;
$form->add(
$field[0],
ChoiceType::class,
[
'multiple'=>true,
'label' => $field[1],
'choices'=> $choices_formatted[$field[0]],
'attr' => [
'class' => 'dropdown'
]
]
);
}
}
}
}
New Form (VariantenType) with the EntityType Dropdown to populate dynamically based on search input in select2 field:
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
->add('title')
->add('article_no')
->add('description')
->add('base_article', EntityType::class, [
'class' => Artikel::class,
'multiple' => false,
'choice_label' => 'title',
// 'query_builder' => function(ArtikelRepository $er) {
// return $er->createQueryBuilder('artikel')
// ->orderBy('artikel.title', 'ASC');
// },
'label' => 'Base Article',
])
;
}
I would be happy about any ideas and suggestions.
Best regards Schwaluck
CodePudding user response:
You just need to add a PRE_SUBMIT
event listener to your FormBuilder:
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
// ... other properties
->add('base_article', EntityType::class, [
'class' => Artikel::class,
'choice_label' => 'title',
'label' => 'Base Article',
'choices' => [],
'attr' => [
'class' => 'select2article'
]
]);
$builder->addEventListener(
FormEvents::PRE_SUBMIT,
function (FormEvent $event) {
$data = $event->getData();
$form = $event->getForm();
if(isset($data['base_article']) and $data['base_article']!=null){
$selected = $data['base_article'];
$form->add('base_article', EntityType::class, array(
'class' => Artikel::class,
'choice_label' => 'title',
'label' => 'Base Article',
'attr' => [
'class' => 'select2article'
],
'query_builder' => function (EntityRepository $er) use ($selected){
return $er->createQueryBuilder('a')
->where('a.id = :id')
->setParameter('id', $selected);
},
));
}
}
);
}
JS
$('.select2article').select2({
ajax: {
url: {{ path("text_ajax_load_select_option") }},
dataType: 'json',
type: 'GET',
data: function (params) {
var queryParameters = {
term: params.term,
}
return queryParameters;
},
processResults: function (data) {
return {
results: $.map(data.selectOptions, function (selectOptions) {
return {
text: selectOptions.title,
id: selectOptions.id
}
})
};
}
}
})
So after sending the request, we get into our event listener. There we will get the selected id
of the base_article
property, after which we will add the query_builder
parameter to the form with the selection by id from the Artikel
entity