EasyAdminBundle icon indicating copy to clipboard operation
EasyAdminBundle copied to clipboard

Custom filter : ChoiceType with "multiple = true" produce an error Undefined array key "comparison"

Open fdiedler opened this issue 3 years ago • 9 comments

Describe the bug

When I want to create a custom filter with ChoiceType Symfony field and the option multiple = true, I have an error. It works only if I set multiple to false.

I use the last EA version (v 4.0.8). I don't want to define a "comparaison", whats is wrong ?

To Reproduce Just create a custom filter like this

class SupplierFilter implements FilterInterface
{
    use FilterTrait;

    public static function new(string $propertyName, $label = null): self
    {
        return (new self())
            ->setFilterFqcn(__CLASS__)
            ->setProperty($propertyName)
            ->setLabel($label)
            ->setFormType(ChoiceType::class)
            ->setFormTypeOptions([
                "choices" => ["Checker" => 1, "Cleaner" => 2],
                "multiple" => true,
                "expanded" => true,
            ])
        ;
    }

    public function apply(QueryBuilder $queryBuilder, FilterDataDto $filterDataDto, ?FieldDto $fieldDto, EntityDto $entityDto): void
    {
	if ($filterDataDto->getValue() == 1)
        {
            $queryBuilder->andWhere("entity.checker is NOT NULL");        
        }
        if ($filterDataDto->getValue() == 2)
        {
            $queryBuilder->andWhere("entity.cleaner is NOT NULL");        
        }
    }
}

Error screen

error

fdiedler avatar Mar 03 '22 11:03 fdiedler

I'm not sure if the attribute comparison could be a nullable value on FilterDataDto. The filter system is normalized on form data. I let others to reply about it.

Right now, I suggest you to rewrite your choice filter from the EA one by using the provided ChoiceFilterType, and adapt keys of form options (minor changes):

//...

final class SupplierFilter implements FilterInterface
{
    use FilterTrait;

    public static function new(string $propertyName, $label = null): self
    {
        return (new self())
            ->setFilterFqcn(__CLASS__)
            ->setProperty($propertyName)
            ->setLabel($label)
            ->setFormType(ChoiceFilterType::class) // EA custom choice filter type
            ->setFormTypeOption('value_type_options.choices', [/* your choices */])
            ->setFormTypeOption('value_type_options.multiple', true)
            ->setFormTypeOption('value_type_options.expanded', true)
            // Some other options prefixed by 'value_type_options.' for Symfony ChoiceType options...
        ;
    }

    public function apply(QueryBuilder $queryBuilder, FilterDataDto $filterDataDto, ?FieldDto $fieldDto, EntityDto $entityDto): void
    {
        // Your logic...
    }
}

The only (small) risk is in case of EA changes about ChoiceFilterType.

Ang3 avatar Mar 03 '22 14:03 Ang3

@Ang3 Unfortunately, I don't want the comparison field ea

Your solution is the same that using directly the ChoiceFilterType with parameters :

public function configureFilters(Filters $filters): Filters
    {
        return $filters
            ->add(ChoiceFilter::new('Supplier')->setChoices(["Checker" => 1, "Cleaner" => 2])->canSelectMultiple()->renderExpanded())
            //->add(SupplierFilter::new('Supplier'))
        ;
    }

@javiereguiluz I think the attribute comparison may (should ?) be a nullable value on FilterDataDto because most of the time, we do not neek to have the comparaison field.

Is there a workaround, an hidden option, to disable the comparison field ?

Thanks,

fdiedler avatar Mar 03 '22 14:03 fdiedler

@fdiedler, Ok I didn't understand well your needs. In this case, maybe you could try this (on basic ChoiceFilterType):

// ...
->setFormTypeOption('comparison_type', 'hidden') // EA is waiting for a type for the comparaison form
// By setting "hidden", you should have a HiddenType (so hidden input => yes it's a trick! ^^)

Ang3 avatar Mar 03 '22 17:03 Ang3

@Ang3 Not working, I have an error messsage

Could not load type "hidden": class does not exist.

And if I do setFormTypeOption('comparison_type', 'Symfony\\Component\\Form\\Extension\\Core\\Type\\HiddenType')

An error has occurred resolving the options of the form "Symfony\Component\Form\Extension\Core\Type\HiddenType": The option "type" does not exist.

I just want to have a multiple choice filter like the Symfony ChoiceType field component :) (note that there is no comparison field in Symfony, I don't unterstand the purpose of such this field)

Thanks,

fdiedler avatar Mar 03 '22 19:03 fdiedler

@fdiedler thank you for your feedback. I think I did a mistake on form type option key:

// ...
->setFormTypeOption('comparison_type_options.type', 'hidden') // EA is waiting for a type for the comparaison form
// By setting "hidden", you should have a HiddenType (so hidden input => yes it's a trick! ^^)

If you get an error because comparison is required and not defined somewhere, try to set comparison_type_options.empty_data to true:

// ...
->setFormTypeOption('comparison_type_options.type', 'hidden')
->setFormTypeOption('comparison_type_options.empty_data', true)

Ang3 avatar Mar 03 '22 21:03 Ang3

@Ang3 Not working

An error has occurred resolving the options of the form "EasyCorp\Bundle\EasyAdminBundle\Form\Type\ComparisonType": The option "type" with value "hidden" is invalid. Accepted values are: "array", "datetime", "choice", "entity", "numeric", "text".

:(

fdiedler avatar Mar 03 '22 21:03 fdiedler

@fdiedler /cc @Ang3

Following code works well for me :+1:

use EasyCorp\Bundle\EasyAdminBundle\Form\Filter\Type\ChoiceFilterType;
use EasyCorp\Bundle\EasyAdminBundle\Form\Type\ComparisonType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class GenerationFilterType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('comparison', HiddenType::class, [
                'data' => ComparisonType::CONTAINS,
            ])
        ;
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'value_type' => ChoiceType::class,
            'value_type_options' => [
                'choices' => [
                    'Choice1' => 1,
                    'Choice2' => 2,
                    'Choice3' => 3,
                ],
                'multiple' => true,
                'row_attr' => [
                    'style' => 'margin-top: -12px;', // just to eliminate the ugly margin
                ],
            ],
        ]);
    }

    public function getParent(): string
    {
        return ChoiceFilterType::class;
    }
}
image

Why this works

ComparisonFilterType, which is the parent of ChoiceFilterType, adds 'comparison' field as $options['comparison_type'] with $options['comparison_type_options']. So we can simply overwrite this adding.

ttskch avatar Mar 08 '22 11:03 ttskch

@ttskch Thanks a lot for your workaround ! :)

fdiedler avatar Mar 09 '22 20:03 fdiedler

@fdiedler /cc @Ang3

Following code works well for me 👍

use EasyCorp\Bundle\EasyAdminBundle\Form\Filter\Type\ChoiceFilterType;
use EasyCorp\Bundle\EasyAdminBundle\Form\Type\ComparisonType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class GenerationFilterType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder
            ->add('comparison', HiddenType::class, [
                'data' => ComparisonType::CONTAINS,
            ])
        ;
    }

    public function configureOptions(OptionsResolver $resolver): void
    {
        $resolver->setDefaults([
            'value_type' => ChoiceType::class,
            'value_type_options' => [
                'choices' => [
                    'Choice1' => 1,
                    'Choice2' => 2,
                    'Choice3' => 3,
                ],
                'multiple' => true,
                'row_attr' => [
                    'style' => 'margin-top: -12px;', // just to eliminate the ugly margin
                ],
            ],
        ]);
    }

    public function getParent(): string
    {
        return ChoiceFilterType::class;
    }
}
image

Why this works

ComparisonFilterType, which is the parent of ChoiceFilterType, adds 'comparison' field as $options['comparison_type'] with $options['comparison_type_options']. So we can simply overwrite this adding.

Thank you for your workaround. This works perfect for me as well :+1

steevechristen avatar Jan 31 '23 15:01 steevechristen