mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-21 09:35:49 +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\AbstractStructuralDBElement;
|
||||
use App\Repository\StructuralDBElementRepository;
|
||||
use App\Services\Trees\NodesListBuilder;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Form\ChoiceList\Loader\AbstractChoiceLoader;
|
||||
|
@ -33,6 +34,8 @@ class StructuralEntityChoiceLoader extends AbstractChoiceLoader
|
|||
private NodesListBuilder $builder;
|
||||
private EntityManagerInterface $entityManager;
|
||||
|
||||
private ?string $additional_element = null;
|
||||
|
||||
public function __construct(Options $options, NodesListBuilder $builder, EntityManagerInterface $entityManager)
|
||||
{
|
||||
$this->options = $options;
|
||||
|
@ -42,10 +45,16 @@ class StructuralEntityChoiceLoader extends AbstractChoiceLoader
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
return array_merge($tmp, $this->builder->typeToNodesList($this->options['class'], null));
|
||||
}
|
||||
|
||||
public function loadChoicesForValues(array $values, callable $value = null)
|
||||
/*public function loadChoicesForValues(array $values, callable $value = null)
|
||||
{
|
||||
$tmp = parent::loadChoicesForValues($values, $value);
|
||||
|
||||
|
@ -59,29 +68,13 @@ class StructuralEntityChoiceLoader extends AbstractChoiceLoader
|
|||
return $tmp;
|
||||
}
|
||||
|
||||
return [$this->createNewEntityFromValue((string)$values[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 [$this->createNewEntitiesFromValue($values[0])[0]];
|
||||
}
|
||||
|
||||
return $tmp;
|
||||
}*/
|
||||
|
||||
|
||||
public function createNewEntityFromValue(string $value): AbstractStructuralDBElement
|
||||
public function createNewEntitiesFromValue(string $value): array
|
||||
{
|
||||
if (!$this->options['allow_add']) {
|
||||
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'];
|
||||
/** @var AbstractStructuralDBElement $entity */
|
||||
$entity = new $class();
|
||||
$entity->setName($value);
|
||||
/** @var StructuralDBElementRepository $repo */
|
||||
$repo = $this->entityManager->getRepository($class);
|
||||
|
||||
//Persist element to database
|
||||
$this->entityManager->persist($entity);
|
||||
$entities = $repo->getNewEntityFromPath($value, '->');
|
||||
|
||||
return $entity;
|
||||
$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);
|
||||
$results[] = $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\CallbackTransformer;
|
||||
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\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormEvents;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
|
@ -65,12 +68,44 @@ class StructuralEntityType extends AbstractType
|
|||
|
||||
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(
|
||||
function ($value) use ($options) {
|
||||
return $this->modelTransform($value, $options);
|
||||
}, function ($value) use ($options) {
|
||||
return $this->modelReverseTransform($value, $options);
|
||||
}));
|
||||
function ($value) use ($options) {
|
||||
return $this->modelTransform($value, $options);
|
||||
}, function ($value) use ($options) {
|
||||
return $this->modelReverseTransform($value, $options);
|
||||
}));
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
|
@ -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
|
||||
'subentities_of' => null, //Only show entities with the given parent class
|
||||
'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) {
|
||||
return new StructuralEntityChoiceLoader($options, $this->builder, $this->em);
|
||||
},
|
||||
|
@ -95,6 +141,15 @@ class StructuralEntityType extends AbstractType
|
|||
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
|
||||
]);
|
||||
|
||||
|
|
|
@ -89,4 +89,38 @@ class StructuralDBElementRepository extends NamedDBElementRepository
|
|||
|
||||
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