Answer the question
In order to leave comments, you need to log in
How to organize dynamic lists of categories and subcategories using forms in Symfony2?
Greetings.
Colleagues, I ran into a small problem, for which I can not find a solution. Participate in a brainstorming session, maybe someone has met a similar task.
I am using Symfony2.3. We have Entity:
Category:
// src/Acme/DemoBundle/Entity/Category.php<br><br>
namespace Acme\DemoBundle\Entity;<br><br>
use Gedmo\Mapping\Annotation as Gedmo;<br>
use Doctrine\ORM\Mapping as ORM;<br><br>
/**<br>
* @Gedmo\Tree(type="nested")<br>
* @ORM\Table(name="categories")<br>
* use repository for handy tree functions<br>
* @ORM\Entity(repositoryClass="Gedmo\Tree\Entity\Repository\NestedTreeRepository")<br>
*/<br>
class Category<br>
{<br>
/**<br>
* @ORM\Column(name="id", type="integer")<br>
* @ORM\Id<br>
* @ORM\GeneratedValue<br>
*/<br>
private $id;<br><br>
/**<br>
* @Gedmo\Translatable<br>
* @ORM\Column(name="name", type="string", length=64)<br>
*/<br>
private $name;<br><br>
/**<br>
* @Gedmo\TreeLeft<br>
* @ORM\Column(name="lft", type="integer")<br>
*/<br>
private $lft;<br><br>
/**<br>
* @Gedmo\TreeLevel<br>
* @ORM\Column(name="lvl", type="integer")<br>
*/<br>
private $lvl;<br><br>
/**<br>
* @Gedmo\TreeRight<br>
* @ORM\Column(name="rgt", type="integer")<br>
*/<br>
private $rgt;<br><br>
/**<br>
* @Gedmo\TreeRoot<br>
* @ORM\Column(name="root", type="integer", nullable=true)<br>
*/<br>
private $root;<br><br>
/**<br>
* @Gedmo\TreeParent<br>
* @ORM\ManyToOne(targetEntity="Category", inversedBy="children")<br>
* @ORM\JoinColumn(name="parent_id", referencedColumnName="id", onDelete="CASCADE")<br>
*/<br>
private $parent;<br><br>
/**<br>
* @ORM\OneToMany(targetEntity="Category", mappedBy="parent")<br>
* @ORM\OrderBy({"lft" = "ASC"})<br>
*/<br>
private $children;<br>
// src/Acme/DemoBundle/Entity/Item.php<br>
namespace Acme\DemoBundle\Entity;<br><br>
use DoctrineExtensions\Taggable\Taggable;<br>
use Doctrine\Common\Collections\ArrayCollection;<br>
use Doctrine\ORM\Mapping as ORM;<br>
use Doctrine\Common\Collections\Collection;<br><br>
/**<br>
* Item entity<br>
*<br>
* @ORM\Table(name="items")<br>
* @ORM\HasLifecycleCallbacks<br>
* @ORM\Entity<br>
*/<br>
class Item implements Taggable<br>
{<br>
/**<br>
* @ORM\Id<br>
* @ORM\Column(type="integer")<br>
* @ORM\GeneratedValue(strategy="AUTO")<br>
*/<br>
protected $id;<br><br>
/**<br>
* @ORM\Column(name="name", type="string", length=64, nullable=true)<br>
*/<br>
protected $name;<br><br>
/**<br>
* @ORM\ManyToMany(targetEntity="Category", inversedBy="items")<br>
* @ORM\JoinColumn(name="category_id", referencedColumnName="id"),<br>
* inverseJoinColumn=(name="item_id", referencedColumnName="id")<br>
*<br>
**/<br>
protected $categories;<br><br>
public function getCategories(){<br>
return $this->categories;<br>
}<br><br>
public function setCategories($categories){<br>
$this->categories = $categories;<br><br>
return $this->categories;<br>
}<br>
// src/Acme/DemoBundle/Form/AddItemForm.php<br>
namespace Acme\DemoBundle\Form;<br><br>
use Symfony\Component\Form\AbstractType;<br>
use Symfony\Component\Form\FormBuilderInterface;<br>
use Symfony\Component\OptionsResolver\OptionsResolverInterface;<br>
use Doctrine\ORM\EntityRepository;<br>
use Symfony\Component\Validator\Constraints\Collection;<br><br>
/**<br>
* Add item form<br>
*<br>
*/<br>
class AddModelForm extends AbstractType<br>
{ public function buildForm(FormBuilderInterface $builder, array $options) {<br>
$builder->add('categories', 'collection', array(<br>
'type' => 'entity',<br>
'allow_add' => true,<br>
'allow_delete' => true,<br>
'prototype' => true,<br>
'show_legend' => false,<br>
'data' => array(''),<br>
'widget_add_btn' => array('label' => _('Добавить категорию')),<br>
'options' => array(<br>
'widget_control_group' => false,<br>
'label_render' => false,<br>
'class' => 'AcmeDemoBundle:Category',<br>
'query_builder' => function(EntityRepository $er) {<br>
return $er->createQueryBuilder('c')<br>
->where('c.lvl = 0')<br>
->orderBy('c.id', 'ASC');<br>
},<br>
'property' => 'name',<br>
'empty_value' => _('Choose category'),<br>
),<br><br>
)<br>
);<br>
}<br>
}<br>
// src/Acme/DemoBundle/Controller/ItemController.php<br>
...<br>
public function editAction($itemId) {<br>
$item= $em->getRepository('AcmeDemoBundle:Item')<br>
->findOneById($itemId);<br><br>
$form = $this->createForm(new AddItemForm(), $item);<br>
}<br><br>
public function addAction() {<br>
$item = new Item();<br><br>
$form = $this->createForm(new AddItemForm(), $item);<br>
}<br>
...<br>
Answer the question
In order to leave comments, you need to log in
And why is the “write everything in JS” option bad? Just wrap the logic in your FormType, add a DataTransformer and a ViewTransformer, and you're good-practice. Again, you can do with just expanding the collection type and replacing the widget. There are a lot of options, see what will be easier for you to do.
and here there is no other option - only handles, only js
it is better to unload the entire set of categories into a js-array, which is then fed to the object that implements work with the tree (parent-children), and then it will be convenient to work with this object, animating the buttons
I deal with DataTransformers and custom types.
Main form:
class CategoryForm extends AbstractType
{
private $em;
public function __construct($em) {
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$transformer = new CategoryToChoiceTransformer($this->em);
$builder->add(
$builder->create('categories', 'collection',
array(
'type' => new CategoryCollectionType($this->em)
)
)->addModelTransformer($transformer));
$builder->add('save', 'submit');
}
public function getDefaultOptions(array $options)
{
$resolver->setDefaults(array(
'data_class' => 'Acme\DemoBundle\Entity\Item',
));
}
class CategoryCollectionType extends AbstractType
{
private $em;
public function __construct($em) {
$this->em = $em;
}
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder->add('categories', 'collection', array(
'type' => 'entity',
'allow_add' => true,
'allow_delete' => true,
'prototype' => false,
'show_legend' => true,
'widget_add_btn' => array('label' => _('Добавить категорию')),
'options' => array(
'widget_control_group' => false,
'label_render' => false,
'show_legend' => false,
'class' => 'AcmeDemoBundle:Category',
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('c')
->orderBy('c.id', 'ASC');
},
'property' => 'name',
'empty_value' => _('Выберите категорию'),
),
)
);
}
public function getName()
{
return 'category_collection_type';
}
}
class CategoryToChoiceTransformer implements DataTransformerInterface
{
/**
* @var ObjectManager
*/
private $om;
/**
* @param ObjectManager $om
*/
public function __construct(ObjectManager $om)
{
$this->om = $om;
}
/**
*
* @param Issue|null $issue
* @return string
*/
public function transform($categories)
{
$categoriesExpanded = new ArrayCollection();
if (!empty($categories)) {
$categoryRepository = $this->om->getRepository('AcmeDemoBundle:Category');
foreach ($categories as $category) {
$path = new ArrayCollection($categoryRepository->getPath($category));
$categoriesExpanded->add($path);
}
}
return $categoriesExpanded;
}
public function reverseTransform($f) {
...
}
}
array(
[0] => array( ObjectCategory#..., ObjectCategory#..., ObjectCategory#..., ),
[1] => array( ObjectCategory#..., ObjectCategory#..., ObjectCategory#..., )
Didn't find what you were looking for?
Ask your questionAsk a Question
731 491 924 answers to any question