简体   繁体   中英

Symfony3 + Select2 tags like list

I'm trying to have a form that is responsible to register food products in my database. For each product, I'd like also to register the ingredients written on the product package.

For those two Entities I have the following two classes:

Ingredient:

class Ingredient {

    // ...

    /**
     * @var string
     *
     * @Assert\NotBlank()
     * @Assert\Type("string")
     *
     * @ORM\Column(name="name", type="string", length=255, unique=false)
     */
    private $name;

    /**
     * @ORM\ManyToMany(targetEntity="NutritionBundle\Entity\Product", mappedBy="ingredients")
     */
    private $products;

    // ...

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

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

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

    // ...

    /**
     * @return Product
     */
    public function getProducts() {
        return $this->products;
    }

    /**
     * @param Product $products
     *
     * @return Ingredient
     */
    public function setProducts( Product $products ) {
        $this->products = $products;

        return $this;
    }

    // ...

}

Product:

class Product {
    // ...

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

    // ...

    /**
     * @ORM\ManyToMany( targetEntity="NutritionBundle\Entity\Ingredient", inversedBy="products" )
     * @ORM\JoinTable(name="ingredients_products")
     */
    private $ingredients;

    // ...

    public function __construct() {
        $this->ingredients = new ArrayCollection();
    }

    // ...

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

        return $this;
    }

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

    // ...

    /**
     * @return mixed
     */
    public function getIngredients() {
        return $this->ingredients;
    }

    /**
     * @param mixed $ingredients
     *
     * @return Product
     */
    public function setIngredients( $ingredients ) {
        $this->ingredients = $ingredients;

        return $this;
    }

    // ...
}

Then, on the twig side, I need to create the form and utilize the Select2 in order to archive the following:

  1. Load the ingredients in Select2 only using AJAX ( I already did that, but I am not sure I did it in the right way )
  2. Prevent the Symfony to load all the ingredients in the Select2 when it loads ( the ingredients table it could have some hundred of thousands of ingredients and I guess it could be a major performance issue ) .
  3. Allow the end user to insert a new ingredient in the ingredients field that doesn't exists yet in the ingredients table, and then, once the form is submitted to create the new ingredients as records of the ingredients table. ( I have also done that, but I don't know how to save the ingredients once the form is submited. ) .

At the moment, the ProductType class I did for the product form looks like this:

class ProductType extends AbstractType {
    public function buildForm( FormBuilderInterface $builder, array $options ) {
        $builder
            ->add( 'name' )
            ->add( 'company' )
            ->add( 'front_picture' )
            ->add( 'back_picture' )
            ->add( 'weight' )
            ->add( 'unit' )
            ->add( 'barcode' )
            ->add( 'product_url' )
            ->add( 'ingredients' );
    //          ->add(
    //              'ingredients',
    //              EntityType::class,
    //              array(
    //                  'class'        => 'NutritionBundle\Entity\Ingredient',
    //                  'choice_label' => function ( $ingredient ) {
    //                      return sprintf(
    //                          '%2$s [E%1$s]',
    //                          $ingredient->getCode(),
    //                          $ingredient->getName()
    //                      );
    //                  },
    //                  'choice_value' => 'id',
    //                  'multiple'     => true,
    //              )
    //          );

    // $builder->get( 'ingredients' )->addModelTransformer( $this->ingredients_transformer );
    }

    public function configureOptions( OptionsResolver $resolver ) {
        $resolver->setDefaults(
            array(
                'data_class' => 'NutritionBundle\Entity\Product',
            )
        );
    }

    public function getBlockPrefix() {
        return 'nutritionbundle_product';
    }
}

As you can see in the commented out code, I have also try the EntityType option, but propably doesn't meet my requirements.

Finally in the twig I have the following code for the Ingredients field:

<div
    class="form-group {% if product_form.ingredients.vars.errors|length %}has-error{% endif %}"
>
    <label
        for="{{ product_form.ingredients.vars.id }}"
        class="control-label"
    >
        Ingredients
    </label>
    <select
        name="{{ product_form.ingredients.vars.full_name|e('html_attr') }}"
        id="{{ product_form.ingredients.vars.id }}"
        multiple="multiple"
        class="form-control"
        data-values="{{ product_form.ingredients.vars.value|join(',') }}"
    ></select>
    <span class="help-block">
        <small>Enter the product ingredients as they appear in the ingredients list.</small>
        </span>
        {% if product_form.ingredients.vars.errors|length %}
        <span class="help-block">
            {{ form_errors( product_form.ingredients ) }}
        </span>
        {% endif %}
</div>

The reason I have to render the Ingredients field in this way, is to achieve the customization I need for loading only the ingredients already assigned to given product using only AJAX.

Finally, the final issue is that, because of this customized field rendering, the Symfony still rendering the Ingredients field using the default control:

在此处输入图片说明

As you can see, above the buttons is my customized rendered field and below the buttons is the field forced by Symfony.

In my current form state, if I try to create new ingredients that doesn't exists yet in the DB, I get error, because the ingredients are not in the DB and ofcourse they can't be assigned to the product.

To not render the Ingredients field again, you can prevent the rendering with

{% do product_form.ingredients.setRendered %}

But I suggest you use https://github.com/tetranz/select2entity-bundle

Then you won't need to prevent the rendering. Using that Bundle you'll have to define a remote route in a controller which will return the list of options.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM