Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Symfony 2 GenemuFormBundle how to create a jQuery Select2 with Ajax

Tags:

forms

symfony

I'm trying to add a Select2 input using the GenemuFormBundle as described in the "Use jQuery Select2 with Ajax" doc. Adding a jQuery Select2 Field following the jQuery Select2 Field documentation works just fine.

But the documentation on how to implement an Ajax-loading Select2 form is very inconclusive. If I unterstand the doc correctly, it aims to create the same as mentioned in the Select2 documentation. This is exactly the thing I'd like to create. I added hidden field as well as the required JavaScript, but the only thing that I get is a Variable "id" does not exist in xBundle:x:new.html.twig at line x.

Form builder (taken directly form the mentioned doc):

...
->add('field_name', 'genemu_jqueryselect2_hidden', array(
    'configs' => array(
        'multiple' => true // Wether or not multiple values are allowed (default to false)
    )
))
->add('field_name', 'genemu_jqueryselect2_entity', array(
    'class' => 'xBundle:Entity',
    'property' => 'foo',
))

View (also taken directly form the doc):

{% block stylesheets %}
    {{ form_stylesheet(form) }}
{% endblock %}

{% block javascript %}
    {{ form_javascript(form) }}
{% endblock %}

{% block genemu_jqueryselect2_javascript %}

    <script type="text/javascript">
        $field = $('#{{ id }}');

        var $configs = {{ configs|json_encode|raw }};

        // custom configs
        $configs = $.extend($configs, {
            query: function (query) {
                var data = {results: []}, i, j, s;
                for (i = 1; i < 5; i++) {
                    s = "";
                    for (j = 0; j < i; j++) {s = s + query.term;}
                    data.results.push({id: query.term + i, text: s});
                }
                query.callback(data);
            }
        });
        // end of custom configs

        $field.select2($configs);
    </script>

{% endblock %}
like image 893
wowpatrick Avatar asked Mar 27 '14 09:03

wowpatrick


1 Answers

I just struggled with this exact issue and thought I would throw in my own findings for anyone that happens to stumble across this. I did find a solution, but I would probably still recommend just going with the ZenStruckFormBundle recommended by Doug because it seems to actually be designed to work as a solution for a select2 field type that loads via ajax and relates back to an entity.

The real reason this doesn't work is that the the genemu_jqueryselect2_* types for the GenemuFormBundle do not implement a data transformer that will work with an entity when you need an ajax-loading select2 field. It doesn't seem like it was ever designed to work this way. When you use the genemu_jqueryselect2_hidden type and have the “multiple’ config option set to true, then it adds an ArrayToStringTransformer. This will not work with an entity.

To fix this you need to make a new form field type and define that its parent is genemu_jqueryselect2_hidden then make a few customizations. It would look something like this…

namespace Acme\DemoBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Acme\DemoBundle\Form\DataTransformer\EntityCollectionToIdTransformer;
use Doctrine\Common\Persistence\ObjectManager;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

class EntityCollectionSelectorType extends AbstractType
{
    /**
     * @var ObjectManager
     */
     protected $om;

    /**
     * @param ObjectManager $om
     */
    public function __construct(ObjectManager $om)
    {
        $this->om = $om;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $transformer = new EntityCollectionToIdTransformer($this->om, $options['configs']['entity']);
        $builder->resetViewTransformers();
        $builder->addModelTransformer($transformer);
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'invalid_message' => 'The selected entity does not exist',
            'required' => true,
            'auto_initialize' => false,
            'configs' => array('multiple' => true),
            'error_bubbling' => false, 
        ));
    }

    public function getParent()
    {
        return 'genemu_jqueryselect2_hidden';
    }

    public function getName()
    {
        return 'entity_collection_selector';
    }
}

Then you will also need to add the new data transformer used in the above form field type, so that it can translate the values between the form field and the entity…

namespace Acme\DemoBundle\Form\DataTransformer;

use Symfony\Component\Form\DataTransformerInterface;
use Symfony\Component\Form\Exception\TransformationFailedException;
use Doctrine\ORM\PersistentCollection;
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Common\Collections\ArrayCollection;

class EntityCollectionToIdTransformer implements DataTransformerInterface
{
    /**
     * @var ObjectManager
     */
    private $om;

    /**
     * @var string The Doctrine entity type to use
     */
    private $entityType;

    /**
     * @param ObjectManager $om
     */
    public function __construct(ObjectManager $om, $entityType)
    {
        $this->om = $om;
        $this->entityType = $entityType;
    }

    /**
     * Transforms a collection of entities to a comma separated string
     *
     * @param  ArrayCollection $entities
     * @return string
     */
    public function transform($entities)
    {
        if (null == $entities || empty($entities)) {
            return '';
        }

        $results = '';
        foreach ($entities as $entity) {
            $results .= $entity->getId() . ',';
        }
        $results = trim($results, ' ,');

        return $results;
    }

   /**
    * Transforms a string of comma separated IDs to a PersistentCollection for Doctrine
    *
    * @param  string $values
    * @return PersistentCollection|ArrayCollection
    * @throws TransformationFailedException if entity is not found.
    */
    public function reverseTransform($values)
    {
        if (!$values) {
            return new ArrayCollection();
        }
        $values = explode(',', $values);

        $collection = array();
        foreach ($values as $id) {
            $item = $this->om->getRepository($this->entityType)->findOneById($id);

            if (!is_null($item)) {
                $collection[] = $item;
            }
            else {
                throw new TransformationFailedException(sprintf(
                    'An entity with ID "%s" does not exist!',
                    $value
                ));
            }
        }

        return new PersistentCollection($this->om, $this->entityType, new ArrayCollection($collection));
    }
}

Now make sure you define your new field type in your config for your services…

<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

    <parameters>
        ...
        <parameter key="acme_demo.form.type.entity_collection_selector.class">Acme\DemoBundle\Form\Type\EntityCollectionSelectorType</parameter>
        ...
    </parameters>

    <services>
        ...
        <service id="acme_demo.form.type.entity_collection_selector"
            class="%acme_demo.form.type.entity_collection_selector.class%">
            <argument type="service" id="doctrine.orm.default_entity_manager" />
            <tag name="form.type" alias="entity_collection_selector" />
        </service>
        ...
    </services>
</container>

Now you can use it as such…

$builder->add('customers', 'entity_collection_selector', array(
    'configs' => array('entity' => 'AcmeDemoBundle:Customer')
));
like image 185
ChadSikorra Avatar answered Sep 29 '22 07:09

ChadSikorra