Easyadminbundle: Custom collection type: handle add new entity

Created on 14 Jan 2017  路  6Comments  路  Source: EasyCorp/EasyAdminBundle

Hi,

I have a working well backend but i would like to know how i can allow to add new element in a collection type

it look like that:
capture d ecran 2017-01-14 a 15 43 42
i would like to be able to add test4 without going to Tag entity editing.
So something like that: https://codepen.io/vineethtr/pen/emaygz

Thanks a lot

feature wontfix

Most helpful comment

@maxenceboucas Hi I just try to describe how I implemented tags like you want, except of styles but I think you can do it by yourself.

It will be like this:

screenshot-onk local-2017-01-20-14-09-42

(Also you can see issues about this)

To do it

1.First in my News Entity I have:

<?php

namespace AcmeBundle\Entity\News;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;


/**
 * @ORM\Entity
 * @ORM\Table(name="news")
 */
class News
{

    /**
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", nullable=false)
     */
    private $title;

    /**
     * @ORM\ManyToMany(targetEntity="AcmeBundle\Entity\News\NewsTag", cascade={"persist"})
     */
    private $tags;


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

    /**
     * @param mixed $id
     */
    public function setId($id)
    {
        $this->id = $id;
    }

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

    /**
     * @param mixed $title
     */
    public function setTitle($title)
    {
        $this->title = $title;
    }

    /**
     * -------------------------- Tags ------------------------------
     */

    /**
     * @param NewsTag $tag
     */
    public function addTag(NewsTag $tag)
    {
        $this->tags->add($tag);
    }

    public function removeTag(NewsTag $tag)
    {
        // ...
    }


    /**
     * -------------------------- Tags end-------------------------------
     */

    public function getTags()
    {
        return $this->tags;
    }



    public function __construct()
    {

        $this->tags = new ArrayCollection();

    }

    /**
     * {@inheritdoc}
     */
    public function __toString()
    {
        return (string) $this->getTitle();

    }
}

2.Second my Tag Entity:

<?php

namespace AcmeBundle\Entity\News;

use Doctrine\ORM\Mapping as ORM;

/**
 *
 * @ORM\Table(name="news_tags")
 * @ORM\Entity
 */
class NewsTag
{
    /**
     *
     * @var int
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id = null;

    /**
     *
     * @var string
     * @ORM\Column(type="string")
     */
    protected $name;


    protected $news;


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

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

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



    public function __construct()
    {

    }

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

  1. Customize your admin controller and add action:
 public function createNewsEntityFormBuilder(News $entity, $view)
    {
        $formBuilder = parent::createEntityFormBuilder($entity, $view);

        $formBuilder->remove('tags');

        $formBuilder->add('tags', CollectionType::class, array(
            'allow_add'    => true,
            'allow_delete' => true,
            'by_reference' => false,
            'entry_type' => TagType::class
        ));

        return $formBuilder;
    }

  1. Add Tag Type in AcmeBundle\FormType
//AcmeBundle\Form\Type\TagType.php

<?php

namespace AcmeBundle\Form\Type;

use AcmeBundle\Entity\News\NewsTag;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class TagType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('name');
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => NewsTag::class,
        ));
    }
}

  1. In your easy admin config:
easy_admin:
    entities:
        News:
            class: AcmeBundle\Entity\News\News
            label: 'News'
            form:
                title: ""
                fields:
                    #your fields ...
                    - { property: 'tags', type: collection }

All 6 comments

I just found that on EasyAdminBundle/Ressources/views/form/bootstrap_3_layout.html.twig

```
{% block collection_row %}
{{ block('form_row') }}

{% if allow_add|default(false) %}
    {% set js_add_item %}
        $(function() {
            if (event.preventDefault) event.preventDefault(); else event.returnValue = false;

            var collection = $('#{{ id }}');
            // Use a counter to avoid having the same index more than once
            var numItems = collection.data('count') || collection.children('div.form-group').length;

            collection.prev('.collection-empty').remove();

            var newItem = collection.attr('data-prototype')
                .replace(/\>__name__label__\</g, '>' + numItems + '<')
                .replace(/_{{ name }}___name__/g, '_{{ name }}_' + numItems)
                .replace(/{{ name }}\]\[__name__\]/g, '{{ name }}][' + numItems + ']')
            ;

            // Increment the counter and store it in the collection
            collection.data('count', ++numItems);

            collection.append(newItem).trigger('easyadmin.collection.item-added');
        });
    {% endset %}

    <div class="text-right field-collection-action">
        <a href="#" onclick="{{ js_add_item|raw }}" class="text-primary">
            <i class="fa fa-plus-square"></i>
            {{ (form|length == 0 ? 'action.add_new_item' : 'action.add_another_item')|trans({}, 'EasyAdminBundle') }}
        </a>
    </div>
{% endif %}

{% endblock collection_row %}
````

Some clue ? :)

@maxenceboucas Hi I just try to describe how I implemented tags like you want, except of styles but I think you can do it by yourself.

It will be like this:

screenshot-onk local-2017-01-20-14-09-42

(Also you can see issues about this)

To do it

1.First in my News Entity I have:

<?php

namespace AcmeBundle\Entity\News;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;


/**
 * @ORM\Entity
 * @ORM\Table(name="news")
 */
class News
{

    /**
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string", nullable=false)
     */
    private $title;

    /**
     * @ORM\ManyToMany(targetEntity="AcmeBundle\Entity\News\NewsTag", cascade={"persist"})
     */
    private $tags;


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

    /**
     * @param mixed $id
     */
    public function setId($id)
    {
        $this->id = $id;
    }

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

    /**
     * @param mixed $title
     */
    public function setTitle($title)
    {
        $this->title = $title;
    }

    /**
     * -------------------------- Tags ------------------------------
     */

    /**
     * @param NewsTag $tag
     */
    public function addTag(NewsTag $tag)
    {
        $this->tags->add($tag);
    }

    public function removeTag(NewsTag $tag)
    {
        // ...
    }


    /**
     * -------------------------- Tags end-------------------------------
     */

    public function getTags()
    {
        return $this->tags;
    }



    public function __construct()
    {

        $this->tags = new ArrayCollection();

    }

    /**
     * {@inheritdoc}
     */
    public function __toString()
    {
        return (string) $this->getTitle();

    }
}

2.Second my Tag Entity:

<?php

namespace AcmeBundle\Entity\News;

use Doctrine\ORM\Mapping as ORM;

/**
 *
 * @ORM\Table(name="news_tags")
 * @ORM\Entity
 */
class NewsTag
{
    /**
     *
     * @var int
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id = null;

    /**
     *
     * @var string
     * @ORM\Column(type="string")
     */
    protected $name;


    protected $news;


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

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

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



    public function __construct()
    {

    }

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

  1. Customize your admin controller and add action:
 public function createNewsEntityFormBuilder(News $entity, $view)
    {
        $formBuilder = parent::createEntityFormBuilder($entity, $view);

        $formBuilder->remove('tags');

        $formBuilder->add('tags', CollectionType::class, array(
            'allow_add'    => true,
            'allow_delete' => true,
            'by_reference' => false,
            'entry_type' => TagType::class
        ));

        return $formBuilder;
    }

  1. Add Tag Type in AcmeBundle\FormType
//AcmeBundle\Form\Type\TagType.php

<?php

namespace AcmeBundle\Form\Type;

use AcmeBundle\Entity\News\NewsTag;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;

class TagType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('name');
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => NewsTag::class,
        ));
    }
}

  1. In your easy admin config:
easy_admin:
    entities:
        News:
            class: AcmeBundle\Entity\News\News
            label: 'News'
            form:
                title: ""
                fields:
                    #your fields ...
                    - { property: 'tags', type: collection }

Thanks a lot for your answer.

I didn't find any issues about creating at the same time new tag and referencing old one in the same Form.

I have this error :
Expected value of type "Doctrine\Common\Collections\Collection|array" for association field "DashboardBundle\Entity\Contact#$tags", got "string" instead.

@maxenceboucas please create exactly like I wrote (don't forget update your schema) and don't add your another's getters setters etc. (you can add it later) it should work in my bundle all works fine

I recently closed an issue which was very similar to this one. The problem is that creating a generic way to create new entities on-the-fly while you editing other entity is very complex. I'd like to have that feature as a user ... but it would be a nightmare for us to implement and maintain it. I'm sorry.

@maxenceboucas Same here. Impossible to solve this problem in this Bundle... The problem is that in the "add.." method the parameter's type is not the expected type.

UPDATE
I found the solution... It was trivial but I did not see that; in the Type class use configureOptions method instead of setDefaultOptions.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

joazvsoares picture joazvsoares  路  4Comments

javiereguiluz picture javiereguiluz  路  4Comments

SirMishaa picture SirMishaa  路  3Comments

bocharsky-bw picture bocharsky-bw  路  3Comments

tamert picture tamert  路  3Comments