mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-21 09:35:49 +02:00
Added custom choice form type for tree structure entities.
This commit is contained in:
parent
568367b59e
commit
cfa807c621
7 changed files with 203 additions and 16 deletions
|
@ -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,
|
||||
|
|
|
@ -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',
|
||||
|
|
145
src/Form/Type/StructuralEntityType.php
Normal file
145
src/Form/Type/StructuralEntityType.php
Normal file
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
/**
|
||||
*
|
||||
* part-db version 0.1
|
||||
* Copyright (C) 2005 Christoph Lechner
|
||||
* http://www.cl-projects.de/
|
||||
*
|
||||
* part-db version 0.2+
|
||||
* Copyright (C) 2009 K. Jacobs and others (see authors.php)
|
||||
* http://code.google.com/p/part-db/
|
||||
*
|
||||
* Part-DB Version 0.4+
|
||||
* Copyright (C) 2016 - 2019 Jan Böhmer
|
||||
* https://github.com/jbtronics
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
|
||||
*
|
||||
*/
|
||||
|
||||
namespace App\Form\Type;
|
||||
|
||||
|
||||
use App\Entity\Base\StructuralDBElement;
|
||||
use App\Entity\Parts\Storelocation;
|
||||
use App\Repository\StructuralDBElementRepository;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\ChoiceList\Loader\CallbackChoiceLoader;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\Form\FormInterface;
|
||||
use Symfony\Component\Form\FormView;
|
||||
use Symfony\Component\OptionsResolver\Options;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
|
||||
/**
|
||||
* This class provides a choice form type similar to EntityType, with the difference, that the tree structure
|
||||
* of the StructuralDBElementRepository will be shown to user
|
||||
* @package App\Form\Type
|
||||
*/
|
||||
class StructuralEntityType extends AbstractType
|
||||
{
|
||||
protected $em;
|
||||
protected $options;
|
||||
|
||||
public function __construct(EntityManagerInterface $em)
|
||||
{
|
||||
$this->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;
|
||||
}
|
||||
}
|
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -67,9 +67,11 @@
|
|||
<div class="tab-pane active" id="home_common">
|
||||
{{ 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 %}
|
||||
|
||||
|
|
|
@ -13,3 +13,20 @@
|
|||
{% block form_group_class -%}
|
||||
col-9
|
||||
{%- endblock form_group_class %}
|
||||
|
||||
{%- block choice_widget_options -%}
|
||||
{% for group_label, choice in options %}
|
||||
{%- if choice is iterable -%}
|
||||
<optgroup label="{{ choice_translation_domain is same as(false) ? group_label : group_label|trans({}, choice_translation_domain) }}">
|
||||
{% set options = choice %}
|
||||
{{- block('choice_widget_options') -}}
|
||||
</optgroup>
|
||||
{%- else -%}
|
||||
{% if use_html_in_labels is defined and use_html_in_labels %}
|
||||
<option value="{{ choice.value }}"{% if choice.attr %}{% with { attr: choice.attr } %}{{ block('attributes') }}{% endwith %}{% endif %}{% if choice is selectedchoice(value) %} selected="selected"{% endif %}>{{ choice_translation_domain is same as(false) ? choice.label|raw : choice.label|trans({}, choice_translation_domain)|raw }}</option>
|
||||
{% else %}
|
||||
<option value="{{ choice.value }}"{% if choice.attr %}{% with { attr: choice.attr } %}{{ block('attributes') }}{% endwith %}{% endif %}{% if choice is selectedchoice(value) %} selected="selected"{% endif %}>{{ choice_translation_domain is same as(false) ? choice.label : choice.label|trans({}, choice_translation_domain) }}</option>
|
||||
{% endif %}
|
||||
{%- endif -%}
|
||||
{% endfor %}
|
||||
{%- endblock choice_widget_options -%}
|
Loading…
Add table
Add a link
Reference in a new issue