Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Enable Select2Search in Symfony form

I want to enable select 2 search in my Symfony form what i tried so far:

In my form class i have this:

->add('parent', EntityType::class, [
                'class' => Category::class,
                'choice_label' => 'title',
                'attr' => [
                    'class' => 'select2'
                ]
            ])

In my twig file this :

<head>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.2-rc.1/css/select2.min.css" rel="stylesheet" />
    <!-- Loading jquery here--><script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.2-rc.1/js/select2.min.js"></script>
</head>

    {{ form_start(form) }}
    <script type="text/javascript">
        $('select').select2();
    </script>
    {{ form_widget(form) }}
    {{ form_end(form) }}

But i do not get the dropdown with the search bar. Just the default dropdown menu of Symfony. What am I doing wrong

like image 653
Noob Avatar asked May 18 '18 21:05

Noob


3 Answers

The main reason is that the field is created after you try and target it, by this line:

{{ form_widget(form) }}

The JavaScript must be executed after that in order to be able to target the field (besides, the HTML structure of your template is wrong).

Try this :

<!DOCTYPE html>
<html>
<head>
    <title>Test form</title>
    <link href="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.2-rc.1/css/select2.min.css" rel="stylesheet" />
    <!-- Loading jquery here--><script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.2-rc.1/js/select2.min.js"></script>
</head>
<body>
    {{ form_start(form) }}
    {{ form_widget(form) }}
    {{ form_end(form) }}

    <script>
        $('select').select2();
    </script>
</body>
</html>

It's usually better to wait for the page to be loaded before executing scripts, using jQuery you could ensure that it's the case by changing the script to this:

<script>
    $(document).ready(function(){
        $('.select2').select2();
    });
</script>

Notice that I also changed the jQuery selector to use the class you've added to the field in your form builder. This way you control the select field you want to target.

like image 89
Philippe-B- Avatar answered Nov 09 '22 18:11

Philippe-B-


You are initiating the select2 components without configuration, so it doesn't know where is the data source.

Before start coding, you need to install and configure FOSJsRoutingBundle. This bundle will help you with access to ajax routes

For fully configured sync symfony-forms~select2 you could do something like this.

Entity Person

class Person
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="SEQUENCE")
     * @ORM\SequenceGenerator(sequenceName="person_id_seq", allocationSize=1, initialValue=1)
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", nullable=true)
     */
    private $name;

    /**
     * @var Country
     *
     * @ORM\ManyToOne(targetEntity="Country")
     * @ORM\JoinColumns({
     *   @ORM\JoinColumn(name="country_id", referencedColumnName="id")
     * })
     */
    private $country;



    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set name
     *
     * @param string $name
     *
     * @return Person
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    /**
     * Set country
     *
     * @param \AppBundle\Entity\Country $country
     *
     * @return Person
     */
    public function setCountry(\AppBundle\Entity\Country $country = null)
    {
        $this->country = $country;

        return $this;
    }

    /**
     * Get country
     *
     * @return \AppBundle\Entity\Country
     */
    public function getCountry()
    {
        return $this->country;
    }

    public function __toString()
    {
        return $this->name;
    }
}

Entity Country

class Country
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer", nullable=false)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="SEQUENCE")
     * @ORM\SequenceGenerator(sequenceName="country_id_seq", allocationSize=1, initialValue=1)
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", nullable=true)
     */
    private $name;

    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set name
     *
     * @param string $name
     *
     * @return Country
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }

    public function __toString()
    {
        return $this->name;
    }
}

Country repository

class CountryRepository extends \Doctrine\ORM\EntityRepository {

 public function countriesSelect2($term)
    {
        $qb = $this->createQueryBuilder('c');

        $qb->where(
            $qb->expr()->like($qb->expr()->lower('c.name'), ':term')
        )
            ->setParameter('term', '%' . strtolower($term) . '%');

        return $qb->getQuery()->getArrayResult();
    }
}

Country controller

Check how the route is exposed to the options parameter and returns a JsonResponse. You could also use a serializer too.

/**
 * Country controller.
 *
 * @Route("countries")
 */
class CountryController extends Controller
{
    /**
     * Lists all person entities.
     *
     * @Route("/", name="countries",options={"expose"=true})
     * @Method("GET")
     */
    public function indexAction(Request $request)
    {
        $countryRepo = $this->getDoctrine()->getRepository('AppBundle:Country');
        $data = $countryRepo->countriesSelect2($request->get('q', ''));

        //$response = $this->get('serializer')->serialize($data,'json');

        return new JsonResponse($data);
    }
}

So far so good, now comes the good parts, let's go and configure our form

PersonType

class PersonType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('name')
            ->add('country',EntityType::class,[
                'class' => Country::class,
                'attr' => [
                  'class' => 'select2', // the class to use with jquery
                    'data-source' => 'countries', //the exposed route name for data-soirce as attr
                    'data-allow-clear' => 'true'//another extra attr to customize
                ],
            ]);
    }/**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\Person'
        ));
    }

    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        return 'appbundle_person';
    }
}

JS, showing the select2

Remember, you have configured already the select2 options with attributes, you just have to use them properly

$(document).ready(function () {
            $('.select2').each(function () {//using the select2 class
                if (!$().select2) {//checking the script
                    return;
                }
                $.fn.select2.defaults.set("theme", "bootstrap");//some theming if you want

                $($(this)).select2({
                    placeholder: "Select",
                    width: 'auto',
                    allowClear: $(this).attr("data-allow-clear") ? $(this).attr("data-allow-clear") : true, //using my options from the form
                    ajax: {
                        url: Routing.generate($(this).attr("data-source")), //here its the magic
                        dataType: 'json',
                        processResults: function (data) {
                            //console.log(data);
                            return {
                                results: $.map(data, function (item) {
                                    return {
                                        text: item.name, //you need to map this because the plugin accepts only id and text
                                        id: item.id
                                    }
                                })
                            };
                        }
                    }
                });
            });
        });

after that, all is done. All the code is working as I tested my self

Hope it helps!

like image 4
Juan I. Morales Pestana Avatar answered Nov 09 '22 17:11

Juan I. Morales Pestana


There is a nice bundle for it: TetranzBundle

You can configure your form field in FormType class like that:

            ->add('product', Select2EntityType::class, [
                'label'=>'product',
                'required'=>true,
                'mapped'=>true,
                'multiple' => false,
                'remote_route' => 'product_select2_ajax',
                'class' => 'AppBundle:Product',
//                'property' => 'name',
                'minimum_input_length' => 0,
                'page_limit' => 10,
                'allow_clear' => true,
                'delay' => 250,
                'cache' => true,
                'cache_timeout' => 60000, // if 'cache' is true
                'language' => 'pl',
                'placeholder' => "select.product",
            ])
like image 2
Andrew Vakhniuk Avatar answered Nov 09 '22 16:11

Andrew Vakhniuk