diff --git a/src/Form/AdminPages/BaseEntityAdminForm.php b/src/Form/AdminPages/BaseEntityAdminForm.php index 005c6a0c..f92c21e2 100644 --- a/src/Form/AdminPages/BaseEntityAdminForm.php +++ b/src/Form/AdminPages/BaseEntityAdminForm.php @@ -34,6 +34,7 @@ namespace App\Form\AdminPages; use App\Entity\Base\NamedDBElement; use App\Entity\Base\StructuralDBElement; +use App\Form\Type\StructuralEntityType; use FOS\CKEditorBundle\Form\Type\CKEditorType; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\DependencyInjection\ParameterBag\ContainerBagInterface; @@ -70,8 +71,8 @@ class BaseEntityAdminForm extends AbstractType 'attr' => ['placeholder' => 'part.name.placeholder'], 'disabled' => !$this->security->isGranted($is_new ? 'create' : 'edit', $entity), ]) - ->add('parent', EntityType::class, ['class' => get_class($entity), 'choice_label' => 'full_path', - 'attr' => ['class' => 'selectpicker', 'data-live-search' => true], 'required' => false, 'label' => 'parent.label', + ->add('parent', StructuralEntityType::class, ['class' => get_class($entity), + 'required' => false, 'label' => 'parent.label', 'disabled' => !$this->security->isGranted($is_new ? 'create' : 'move', $entity), ]) ->add('not_selectable', CheckboxType::class, ['required' => false, diff --git a/src/Form/PartType.php b/src/Form/PartType.php index 597ed97c..08ed5324 100644 --- a/src/Form/PartType.php +++ b/src/Form/PartType.php @@ -33,6 +33,7 @@ use App\Entity\Parts\Category; use App\Entity\Parts\Manufacturer; use App\Entity\Parts\Part; use App\Entity\Parts\Storelocation; +use App\Form\Type\StructuralEntityType; use FOS\CKEditorBundle\Form\Type\CKEditorType; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; @@ -65,20 +66,20 @@ class PartType extends AbstractType ->add('description', TextType::class, ['required' => false, 'empty_data' => '', 'label' => 'description.label', 'help' => 'bbcode.hint', 'attr' => ['placeholder' => 'part.description.placeholder'], 'disabled' => !$this->security->isGranted('description.edit', $part), 'empty_data' => '' ]) - ->add('instock', IntegerType::class, + /*->add('instock', IntegerType::class, ['attr' => ['min' => 0, 'placeholder' => 'part.instock.placeholder'], 'label' => 'instock.label', - 'disabled' => !$this->security->isGranted('instock.edit', $part), ]) + 'disabled' => !$this->security->isGranted('instock.edit', $part), ]) */ ->add('mininstock', IntegerType::class, ['attr' => ['min' => 0, 'placeholder' => 'part.mininstock.placeholder'], 'label' => 'mininstock.label', 'disabled' => !$this->security->isGranted('mininstock.edit', $part), ]) - ->add('category', EntityType::class, ['class' => Category::class, 'choice_label' => 'full_path', - 'attr' => ['class' => 'selectpicker', 'data-live-search' => true], 'label' => 'category.label', + ->add('category', StructuralEntityType::class, ['class' => Category::class, + 'label' => 'category.label', 'disable_not_selectable' => true, 'disabled' => !$this->security->isGranted('move', $part), ]) - ->add('storelocation', EntityType::class, ['class' => Storelocation::class, 'choice_label' => 'full_path', + /*->add('storelocation', EntityType::class, ['class' => Storelocation::class, 'choice_label' => 'full_path', 'attr' => ['class' => 'selectpicker', 'data-live-search' => true], 'required' => false, 'label' => 'storelocation.label', - 'disabled' => !$this->security->isGranted('storelocation.edit', $part), ]) - ->add('manufacturer', EntityType::class, ['class' => Manufacturer::class, 'choice_label' => 'full_path', - 'attr' => ['class' => 'selectpicker', 'data-live-search' => true], 'required' => false, 'label' => 'manufacturer.label', + 'disabled' => !$this->security->isGranted('storelocation.edit', $part), ]) */ + ->add('manufacturer', StructuralEntityType::class, ['class' => Manufacturer::class, + 'required' => false, 'label' => 'manufacturer.label', 'disable_not_selectable' => true, 'disabled' => !$this->security->isGranted('manufacturer.edit', $part), ]) ->add('manufacturer_product_url', UrlType::class, ['required' => false, 'empty_data' => '', 'label' => 'manufacturer_url.label', diff --git a/src/Form/Type/StructuralEntityType.php b/src/Form/Type/StructuralEntityType.php new file mode 100644 index 00000000..a91d8170 --- /dev/null +++ b/src/Form/Type/StructuralEntityType.php @@ -0,0 +1,145 @@ +em = $em; + } + + public function configureOptions(OptionsResolver $resolver) + { + $resolver->setRequired(['class']); + + $resolver->setDefaults(['attr' => ['class' => 'selectpicker', 'data-live-search' => true], + '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_loader' => function (Options $options) { + return new CallbackChoiceLoader(function () use ($options) { + return $this->getEntries($options); + }); + }, 'choice_label' => function ($choice, $key, $value) { + return $this->generateChoiceLabels($choice, $key, $value); + }, 'choice_attr' => function ($choice, $key, $value) { + return $this->generateChoiceAttr($choice, $key, $value); + } + ]); + } + + protected function generateChoiceAttr(StructuralDBElement $choice, $key, $value) : array + { + $tmp = array(); + + if ($this->options['show_fullpath_in_subtext'] && $choice->getParent() != null) { + $tmp += ['data-subtext' => $choice->getParent()->getFullPath()]; + } + + //Disable attribute if the choice is marked as not selectable + if ($this->options['disable_not_selectable'] && $choice->isNotSelectable()) { + $tmp += ['disabled' => 'disabled']; + } + return $tmp; + } + + protected function generateChoiceLabels(StructuralDBElement $choice, $key, $value) : string + { + /** @var StructuralDBElement|null $parent */ + $parent = $this->options['subentities_of']; + + /*** @var StructuralDBElement $choice */ + $level = $choice->getLevel(); + //If our base entity is not the root level, we need to change the level, to get zero position + if ($this->options['subentities_of'] !== null) { + $level -= $parent->getLevel() - 1; + } + + + $tmp = str_repeat('   ', $choice->getLevel()); //Use 3 spaces for intendation + $tmp .= htmlspecialchars($choice->getName($parent)); + return $tmp; + } + + /** + * Gets the entries from database and return an array of them + * @param Options $options + * @return array + */ + public function getEntries(Options $options) : array + { + $this->options = $options; + + /** @var StructuralDBElementRepository $repo */ + $repo = $this->em->getRepository($options['class']); + $choices = $repo->toNodesList(null); + return $choices; + } + + + public function buildView(FormView $view, FormInterface $form, array $options) + { + //Allow HTML in labels. You must override the 'choice_widget_options' block, so that can work + //See extendedBootstrap4_layout.html.twig for that... + $view->vars['use_html_in_labels'] = true; + + parent::buildView($view, $form, $options); // TODO: Change the autogenerated stub + } + + public function getParent() + { + return ChoiceType::class; + } +} \ No newline at end of file diff --git a/src/Form/UserAdminForm.php b/src/Form/UserAdminForm.php index 089dc54c..c516a802 100644 --- a/src/Form/UserAdminForm.php +++ b/src/Form/UserAdminForm.php @@ -35,6 +35,7 @@ namespace App\Form; use App\Entity\UserSystem\Group; use App\Entity\Base\NamedDBElement; use App\Entity\Base\StructuralDBElement; +use App\Form\Type\StructuralEntityType; use FOS\CKEditorBundle\Form\Type\CKEditorType; use Symfony\Bridge\Doctrine\Form\Type\EntityType; use Symfony\Component\Form\AbstractType; @@ -66,8 +67,8 @@ class UserAdminForm extends AbstractType 'attr' => ['placeholder' => 'user.username.placeholder'], 'disabled' => !$this->security->isGranted('edit_username', $entity), ]) - ->add('group', EntityType::class, ['class' => Group::class, 'choice_label' => 'name', - 'attr' => ['class' => 'selectpicker', 'data-live-search' => true], 'required' => false, 'label' => 'group.label', + ->add('group', StructuralEntityType::class, ['class' => Group::class, + 'required' => false, 'label' => 'group.label', 'disable_not_selectable' => true, 'disabled' => !$this->security->isGranted('change_group', $entity), ]) ->add('first_name', TextType::class, ['empty_data' => '', 'label' => 'user.firstName.label', diff --git a/src/Repository/StructuralDBElementRepository.php b/src/Repository/StructuralDBElementRepository.php index fb709f1d..18e11f58 100644 --- a/src/Repository/StructuralDBElementRepository.php +++ b/src/Repository/StructuralDBElementRepository.php @@ -46,7 +46,27 @@ class StructuralDBElementRepository extends EntityRepository */ public function findRootNodes() : array { - return $this->findBy(['parent' => null]); + return $this->findBy(['parent' => null], ['name' => 'ASC']); + } + + /** + * Gets a flattened hierachical tree. Useful for generating option lists. + * @param StructuralDBElement|null $parent This entity will be used as root element. Set to null, to use global root + * @return StructuralDBElement[] A flattened list containing the tree elements. + */ + public function toNodesList(?StructuralDBElement $parent = null): array + { + $result = array(); + + $entities = $this->findBy(['parent' => $parent], ['name' => 'ASC']); + + foreach ($entities as $entity) { + /** @var StructuralDBElement $entity */ + $result[] = $entity; + $result = array_merge($result, $this->toNodesList($entity)); + } + + return $result; } } \ No newline at end of file diff --git a/templates/AdminPages/EntityAdminBase.html.twig b/templates/AdminPages/EntityAdminBase.html.twig index f3d4e23a..d6e28ef4 100644 --- a/templates/AdminPages/EntityAdminBase.html.twig +++ b/templates/AdminPages/EntityAdminBase.html.twig @@ -67,9 +67,11 @@
{{ form_row(form.name) }} {% if form.parent%} - {{ form_row(form.parent) }} + {{ form_row(form.parent) }} + {% endif %} + {% if form.not_selectable is defined %} + {{ form_row(form.not_selectable) }} {% endif %} - {{ form_row(form.not_selectable) }} {% block additional_controls %}{% endblock %} diff --git a/templates/Form/extendedBootstrap4_layout.html.twig b/templates/Form/extendedBootstrap4_layout.html.twig index 4581f1f2..e8fefb43 100644 --- a/templates/Form/extendedBootstrap4_layout.html.twig +++ b/templates/Form/extendedBootstrap4_layout.html.twig @@ -12,4 +12,21 @@ {% block form_group_class -%} col-9 -{%- endblock form_group_class %} \ No newline at end of file +{%- endblock form_group_class %} + +{%- block choice_widget_options -%} + {% for group_label, choice in options %} + {%- if choice is iterable -%} + + {% set options = choice %} + {{- block('choice_widget_options') -}} + + {%- else -%} + {% if use_html_in_labels is defined and use_html_in_labels %} + + {% else %} + + {% endif %} + {%- endif -%} + {% endfor %} +{%- endblock choice_widget_options -%} \ No newline at end of file