mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-21 17:39:06 +02:00
Allow to create nested entitiy structures directly from the part edit page.
However there is still a bug, that the newly created entites are not shown as selected (even though they are). Fixes issue #203
This commit is contained in:
parent
1654010ea3
commit
e0c380d81a
3 changed files with 133 additions and 32 deletions
|
@ -22,6 +22,7 @@ namespace App\Form\Type\Helper;
|
||||||
|
|
||||||
use App\Entity\Base\AbstractDBElement;
|
use App\Entity\Base\AbstractDBElement;
|
||||||
use App\Entity\Base\AbstractStructuralDBElement;
|
use App\Entity\Base\AbstractStructuralDBElement;
|
||||||
|
use App\Repository\StructuralDBElementRepository;
|
||||||
use App\Services\Trees\NodesListBuilder;
|
use App\Services\Trees\NodesListBuilder;
|
||||||
use Doctrine\ORM\EntityManagerInterface;
|
use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Symfony\Component\Form\ChoiceList\Loader\AbstractChoiceLoader;
|
use Symfony\Component\Form\ChoiceList\Loader\AbstractChoiceLoader;
|
||||||
|
@ -33,6 +34,8 @@ class StructuralEntityChoiceLoader extends AbstractChoiceLoader
|
||||||
private NodesListBuilder $builder;
|
private NodesListBuilder $builder;
|
||||||
private EntityManagerInterface $entityManager;
|
private EntityManagerInterface $entityManager;
|
||||||
|
|
||||||
|
private ?string $additional_element = null;
|
||||||
|
|
||||||
public function __construct(Options $options, NodesListBuilder $builder, EntityManagerInterface $entityManager)
|
public function __construct(Options $options, NodesListBuilder $builder, EntityManagerInterface $entityManager)
|
||||||
{
|
{
|
||||||
$this->options = $options;
|
$this->options = $options;
|
||||||
|
@ -42,10 +45,16 @@ class StructuralEntityChoiceLoader extends AbstractChoiceLoader
|
||||||
|
|
||||||
protected function loadChoices(): iterable
|
protected function loadChoices(): iterable
|
||||||
{
|
{
|
||||||
return $this->builder->typeToNodesList($this->options['class'], null);
|
$tmp = [];
|
||||||
|
if ($this->additional_element) {
|
||||||
|
$tmp = $this->createNewEntitiesFromValue($this->additional_element);
|
||||||
|
$this->additional_element = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loadChoicesForValues(array $values, callable $value = null)
|
return array_merge($tmp, $this->builder->typeToNodesList($this->options['class'], null));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*public function loadChoicesForValues(array $values, callable $value = null)
|
||||||
{
|
{
|
||||||
$tmp = parent::loadChoicesForValues($values, $value);
|
$tmp = parent::loadChoicesForValues($values, $value);
|
||||||
|
|
||||||
|
@ -59,29 +68,13 @@ class StructuralEntityChoiceLoader extends AbstractChoiceLoader
|
||||||
return $tmp;
|
return $tmp;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [$this->createNewEntityFromValue((string)$values[0])];
|
return [$this->createNewEntitiesFromValue($values[0])[0]];
|
||||||
}
|
|
||||||
|
|
||||||
return $tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*public function loadValuesForChoices(array $choices, callable $value = null)
|
|
||||||
{
|
|
||||||
$tmp = parent::loadValuesForChoices($choices, $value);
|
|
||||||
|
|
||||||
if ($this->options['allow_add'] && count($tmp) === 1) {
|
|
||||||
if ($tmp[0] instanceof AbstractDBElement && $tmp[0]->getID() === null) {
|
|
||||||
return [$tmp[0]->getName()];
|
|
||||||
}
|
|
||||||
|
|
||||||
return [(string)$choices[0]->getID()];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return $tmp;
|
return $tmp;
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
public function createNewEntitiesFromValue(string $value): array
|
||||||
public function createNewEntityFromValue(string $value): AbstractStructuralDBElement
|
|
||||||
{
|
{
|
||||||
if (!$this->options['allow_add']) {
|
if (!$this->options['allow_add']) {
|
||||||
throw new \RuntimeException('Cannot create new entity, because allow_add is not enabled!');
|
throw new \RuntimeException('Cannot create new entity, because allow_add is not enabled!');
|
||||||
|
@ -92,13 +85,32 @@ class StructuralEntityChoiceLoader extends AbstractChoiceLoader
|
||||||
}
|
}
|
||||||
|
|
||||||
$class = $this->options['class'];
|
$class = $this->options['class'];
|
||||||
/** @var AbstractStructuralDBElement $entity */
|
/** @var StructuralDBElementRepository $repo */
|
||||||
$entity = new $class();
|
$repo = $this->entityManager->getRepository($class);
|
||||||
$entity->setName($value);
|
|
||||||
|
|
||||||
//Persist element to database
|
$entities = $repo->getNewEntityFromPath($value, '->');
|
||||||
|
|
||||||
|
$results = [];
|
||||||
|
|
||||||
|
foreach($entities as $entity) {
|
||||||
|
//If the entity is newly created (ID null), add it as result and persist it.
|
||||||
|
if ($entity->getID() === null) {
|
||||||
$this->entityManager->persist($entity);
|
$this->entityManager->persist($entity);
|
||||||
|
$results[] = $entity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return $entity;
|
return $results;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setAdditionalElement(?string $element): void
|
||||||
|
{
|
||||||
|
$this->additional_element = $element;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAdditionalElement(): ?string
|
||||||
|
{
|
||||||
|
return $this->additional_element;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -31,9 +31,12 @@ use Doctrine\ORM\EntityManagerInterface;
|
||||||
use Symfony\Component\Form\AbstractType;
|
use Symfony\Component\Form\AbstractType;
|
||||||
use Symfony\Component\Form\CallbackTransformer;
|
use Symfony\Component\Form\CallbackTransformer;
|
||||||
use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader;
|
use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader;
|
||||||
|
use Symfony\Component\Form\Event\PostSubmitEvent;
|
||||||
|
use Symfony\Component\Form\Event\PreSubmitEvent;
|
||||||
use Symfony\Component\Form\Exception\TransformationFailedException;
|
use Symfony\Component\Form\Exception\TransformationFailedException;
|
||||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||||
use Symfony\Component\Form\FormBuilderInterface;
|
use Symfony\Component\Form\FormBuilderInterface;
|
||||||
|
use Symfony\Component\Form\FormEvents;
|
||||||
use Symfony\Component\Form\FormInterface;
|
use Symfony\Component\Form\FormInterface;
|
||||||
use Symfony\Component\Form\FormView;
|
use Symfony\Component\Form\FormView;
|
||||||
use Symfony\Component\OptionsResolver\Options;
|
use Symfony\Component\OptionsResolver\Options;
|
||||||
|
@ -65,6 +68,38 @@ class StructuralEntityType extends AbstractType
|
||||||
|
|
||||||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||||
{
|
{
|
||||||
|
$builder->addEventListener(FormEvents::PRE_SUBMIT, function (PreSubmitEvent $event) {
|
||||||
|
//When the data contains non-digit characters, we assume that the user entered a new element.
|
||||||
|
//In that case we add the new element to our choice_loader
|
||||||
|
|
||||||
|
$data = $event->getData();
|
||||||
|
if (null === $data || !is_string($data) || $data === "" || ctype_digit($data)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$form = $event->getForm();
|
||||||
|
$options = $form->getConfig()->getOptions();
|
||||||
|
$choice_loader = $options['choice_loader'];
|
||||||
|
if ($choice_loader instanceof StructuralEntityChoiceLoader) {
|
||||||
|
$choice_loader->setAdditionalElement($data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/* $builder->addEventListener(FormEvents::POST_SUBMIT, function (PostSubmitEvent $event) {
|
||||||
|
$name = $event->getForm()->getConfig()->getName();
|
||||||
|
$data = $event->getForm()->getData();
|
||||||
|
|
||||||
|
if ($event->getForm()->getParent() === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$event->getForm()->getParent()->add($name, static::class, $event->getForm()->getConfig()->getOptions());
|
||||||
|
$new_form = $event->getForm()->getParent()->get($name);
|
||||||
|
$new_form->setData($data);
|
||||||
|
});*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
$builder->addModelTransformer(new CallbackTransformer(
|
$builder->addModelTransformer(new CallbackTransformer(
|
||||||
function ($value) use ($options) {
|
function ($value) use ($options) {
|
||||||
return $this->modelTransform($value, $options);
|
return $this->modelTransform($value, $options);
|
||||||
|
@ -81,7 +116,18 @@ class StructuralEntityType extends AbstractType
|
||||||
'show_fullpath_in_subtext' => true, //When this is enabled, the full path will be shown in subtext
|
'show_fullpath_in_subtext' => true, //When this is enabled, the full path will be shown in subtext
|
||||||
'subentities_of' => null, //Only show entities with the given parent class
|
'subentities_of' => null, //Only show entities with the given parent class
|
||||||
'disable_not_selectable' => false, //Disable entries with not selectable property
|
'disable_not_selectable' => false, //Disable entries with not selectable property
|
||||||
'choice_value' => 'id', //Use the element id as option value and for comparing items
|
'choice_value' => function (?AbstractStructuralDBElement $element) {
|
||||||
|
if ($element === null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($element->getID() === null) {
|
||||||
|
//Must be the same as the separator in the choice_loader, otherwise this will not work!
|
||||||
|
return $element->getFullPath('->');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $element->getID();
|
||||||
|
}, //Use the element id as option value and for comparing items
|
||||||
'choice_loader' => function (Options $options) {
|
'choice_loader' => function (Options $options) {
|
||||||
return new StructuralEntityChoiceLoader($options, $this->builder, $this->em);
|
return new StructuralEntityChoiceLoader($options, $this->builder, $this->em);
|
||||||
},
|
},
|
||||||
|
@ -95,6 +141,15 @@ class StructuralEntityType extends AbstractType
|
||||||
return $this->generateChoiceAttr($choice, $key, $value, $options);
|
return $this->generateChoiceAttr($choice, $key, $value, $options);
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
'group_by' => function (AbstractStructuralDBElement $element)
|
||||||
|
{
|
||||||
|
//Show entities that are not added to DB yet separately from other entities
|
||||||
|
if ($element->getID() === null) {
|
||||||
|
return 'New (not added to DB yet)';
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
'choice_translation_domain' => false, //Don't translate the entity names
|
'choice_translation_domain' => false, //Don't translate the entity names
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|
|
@ -89,4 +89,38 @@ class StructuralDBElementRepository extends NamedDBElementRepository
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a structure of AbsstractStructuralDBElements from a path separated by $separator, which splits the various levels.
|
||||||
|
* This function will try to use existing elements, if they are already in the database. If not, they will be created.
|
||||||
|
* An array of the created elements will be returned, with the last element being the deepest element.
|
||||||
|
* @param string $path
|
||||||
|
* @param string $separator
|
||||||
|
* @return AbstractStructuralDBElement[]
|
||||||
|
*/
|
||||||
|
public function getNewEntityFromPath(string $path, string $separator = '->'): array
|
||||||
|
{
|
||||||
|
$parent = null;
|
||||||
|
$result = [];
|
||||||
|
foreach (explode($separator, $path) as $name) {
|
||||||
|
$name = trim($name);
|
||||||
|
if ('' === $name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//See if we already have an element with this name and parent
|
||||||
|
$entity = $this->findOneBy(['name' => $name, 'parent' => $parent]);
|
||||||
|
if (null === $entity) {
|
||||||
|
/** @var AbstractStructuralDBElement $entity */
|
||||||
|
$entity = new ($this->getClassName());
|
||||||
|
$entity->setName($name);
|
||||||
|
$entity->setParent($parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result[] = $entity;
|
||||||
|
$parent = $entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue