From 7ff1584eb95a228edcce32760e275ca0392a8919 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20B=C3=B6hmer?= Date: Mon, 6 Feb 2023 00:08:32 +0100 Subject: [PATCH] Fixed multi-part action selectors. --- .../elements/datatables/parts_controller.js | 29 ++-- .../controllers/elements/select_controller.js | 7 + .../structural_entity_select_controller.js | 4 + src/Controller/SelectAPIController.php | 29 +++- src/Form/Type/CurrencyEntityType.php | 69 +------- .../Helper/StructuralEntityChoiceHelper.php | 161 ++++++++++++++++++ src/Form/Type/StructuralEntityType.php | 97 ++--------- .../components/datatables.macro.html.twig | 60 ++++--- 8 files changed, 266 insertions(+), 190 deletions(-) create mode 100644 src/Form/Type/Helper/StructuralEntityChoiceHelper.php diff --git a/assets/controllers/elements/datatables/parts_controller.js b/assets/controllers/elements/datatables/parts_controller.js index 02098ab9..52ad772a 100644 --- a/assets/controllers/elements/datatables/parts_controller.js +++ b/assets/controllers/elements/datatables/parts_controller.js @@ -18,6 +18,7 @@ */ import DatatablesController from "./datatables_controller.js"; +import TomSelect from "tom-select"; import * as bootbox from "bootbox"; @@ -63,22 +64,22 @@ export default class extends DatatablesController { { //Clear options select_element.innerHTML = null; - $(select_element).selectpicker('destroy'); + //$(select_element).selectpicker('destroy'); - for(let i=0; inodesListBuilder = $nodesListBuilder; $this->translator = $translator; + $this->choiceHelper = $choiceHelper; } /** @@ -167,10 +170,32 @@ class SelectAPIController extends AbstractController foreach ($nodes_list as $node) { if ($node instanceof AbstractStructuralDBElement) { $entry = [ + 'text' => $this->choiceHelper->generateChoiceLabel($node), + 'value' => $this->choiceHelper->generateChoiceValue($node), + ]; + + $data = $this->choiceHelper->generateChoiceAttr($node, [ + 'disable_not_selectable' => true, + ]); + //Remove the data-* prefix for each key + $data = array_combine( + array_map(function ($key) { + if (strpos($key, 'data-') === 0) { + return substr($key, 5); + } + return $key; + }, array_keys($data)), + $data + ); + + //Append the data to the entry + $entry += $data; + + /*$entry = [ 'text' => str_repeat('   ', $node->getLevel()).htmlspecialchars($node->getName()), 'value' => $node->getID(), 'data-subtext' => $node->getParent() ? $node->getParent()->getFullPath() : null, - ]; + ];*/ } elseif ($node instanceof AbstractNamedDBElement) { $entry = [ 'text' => htmlspecialchars($node->getName()), diff --git a/src/Form/Type/CurrencyEntityType.php b/src/Form/Type/CurrencyEntityType.php index 08bc17ea..856acc9e 100644 --- a/src/Form/Type/CurrencyEntityType.php +++ b/src/Form/Type/CurrencyEntityType.php @@ -25,6 +25,7 @@ namespace App\Form\Type; use App\Entity\Attachments\AttachmentType; use App\Entity\Base\AbstractStructuralDBElement; use App\Entity\PriceInformations\Currency; +use App\Form\Type\Helper\StructuralEntityChoiceHelper; use App\Services\Attachments\AttachmentURLGenerator; use App\Services\Trees\NodesListBuilder; use Doctrine\ORM\EntityManagerInterface; @@ -41,9 +42,9 @@ class CurrencyEntityType extends StructuralEntityType { protected ?string $base_currency; - public function __construct(EntityManagerInterface $em, NodesListBuilder $builder, AttachmentURLGenerator $attachmentURLGenerator, TranslatorInterface $translator, ?string $base_currency) + public function __construct(EntityManagerInterface $em, NodesListBuilder $builder, TranslatorInterface $translator, StructuralEntityChoiceHelper $choiceHelper, ?string $base_currency) { - parent::__construct($em, $builder, $attachmentURLGenerator, $translator); + parent::__construct($em, $builder, $translator, $choiceHelper); $this->base_currency = $base_currency; } @@ -59,6 +60,12 @@ class CurrencyEntityType extends StructuralEntityType // This options allows you to override the currency shown for the null value $resolver->setDefault('base_currency', null); + $resolver->setDefault('choice_attr', function (Options $options) { + return function ($choice) use ($options) { + return $this->choice_helper->generateChoiceAttrCurrency($choice, $options); + }; + }); + $resolver->setDefault('empty_message', function (Options $options) { //By default we use the global base currency: $iso_code = $this->base_currency; @@ -75,62 +82,4 @@ class CurrencyEntityType extends StructuralEntityType //If short is set to true, then the name of the entity will only shown in the dropdown list not in the selected value. $resolver->setDefault('short', false); } - - protected function generateChoiceAttr(AbstractStructuralDBElement $choice, $key, $value, $options): array - { - $tmp = parent::generateChoiceAttr($choice, $key, $value, $options); - - if (!$choice instanceof Currency) { - throw new RuntimeException('The choice must be an instance of '.Currency::class); - } - - if(!empty($choice->getIsoCode())) { - $symbol = Currencies::getSymbol($choice->getIsoCode()); - } else { - $symbol = null; - } - - if ($options['short']) { - $tmp['data-short'] = $symbol; - } else { - $tmp['data-short'] = $choice->getName(); - } - - $tmp += [ - 'data-symbol' => $symbol, - ]; - - return $tmp; - } - - protected function getChoiceContent(AbstractStructuralDBElement $choice, $key, $value, $options): string - { - if(!$choice instanceof Currency) { - throw new RuntimeException('$choice must be an instance of Currency!'); - } - - //Generate the level spacing - /** @var AbstractStructuralDBElement|null $parent */ - $parent = $options['subentities_of']; - /*** @var AbstractStructuralDBElement $choice */ - $level = $choice->getLevel(); - //If our base entity is not the root level, we need to change the level, to get zero position - if (null !== $options['subentities_of']) { - $level -= $parent->getLevel() - 1; - } - - $tmp = str_repeat('', $level); - - //Show currency symbol or ISO code and the name of the currency - if(!empty($choice->getIsoCode())) { - $tmp .= Currencies::getSymbol($choice->getIsoCode()); - //Add currency name as badge - $tmp .= sprintf('%s', $options['short'] ? 'picker-hs' : '' , htmlspecialchars($choice->getName())); - } else { - $tmp .= htmlspecialchars($choice->getName()); - } - - return $tmp; - } - } diff --git a/src/Form/Type/Helper/StructuralEntityChoiceHelper.php b/src/Form/Type/Helper/StructuralEntityChoiceHelper.php new file mode 100644 index 00000000..a59befd5 --- /dev/null +++ b/src/Form/Type/Helper/StructuralEntityChoiceHelper.php @@ -0,0 +1,161 @@ +. + */ + +namespace App\Form\Type\Helper; + +use App\Entity\Attachments\AttachmentType; +use App\Entity\Base\AbstractStructuralDBElement; +use App\Entity\PriceInformations\Currency; +use App\Services\Attachments\AttachmentURLGenerator; +use RuntimeException; +use Symfony\Component\Intl\Currencies; +use Symfony\Component\OptionsResolver\Options; +use Symfony\Contracts\Translation\TranslatorInterface; + +class StructuralEntityChoiceHelper +{ + + private AttachmentURLGenerator $attachmentURLGenerator; + private TranslatorInterface $translator; + + public function __construct(AttachmentURLGenerator $attachmentURLGenerator, TranslatorInterface $translator) + { + $this->attachmentURLGenerator = $attachmentURLGenerator; + $this->translator = $translator; + } + + /** + * Generates the choice attributes for the given AbstractStructuralDBElement. + * @param AbstractStructuralDBElement $choice + * @param Options|array $options + * @return array|string[] + */ + public function generateChoiceAttr(AbstractStructuralDBElement $choice, $options): array + { + $tmp = []; + + //Disable attribute if the choice is marked as not selectable + if (($options['disable_not_selectable'] ?? false) && $choice->isNotSelectable()) { + $tmp += ['disabled' => 'disabled']; + } + + if ($choice instanceof AttachmentType) { + $tmp += ['data-filetype_filter' => $choice->getFiletypeFilter()]; + } + + $level = $choice->getLevel(); + /** @var AbstractStructuralDBElement|null $parent */ + $parent = $options['subentities_of'] ?? null; + if (null !== $parent) { + $level -= $parent->getLevel() - 1; + } + + $tmp += [ + 'data-level' => $level, + 'data-parent' => $choice->getParent() ? $choice->getParent()->getFullPath() : null, + 'data-path' => $choice->getFullPath('->'), + 'data-image' => $choice->getMasterPictureAttachment() ? $this->attachmentURLGenerator->getThumbnailURL($choice->getMasterPictureAttachment(), 'thumbnail_xs') : null, + ]; + + if ($choice instanceof AttachmentType && !empty($choice->getFiletypeFilter())) { + $tmp += ['data-filetype_filter' => $choice->getFiletypeFilter()]; + } + + return $tmp; + } + + /** + * Generates the choice attributes for the given AbstractStructuralDBElement. + * @param Currency $choice + * @param Options|array $options + * @return array|string[] + */ + public function generateChoiceAttrCurrency(Currency $choice, $options): array + { + $tmp = $this->generateChoiceAttr($choice, $options); + + if(!empty($choice->getIsoCode())) { + $symbol = Currencies::getSymbol($choice->getIsoCode()); + } else { + $symbol = null; + } + + if ($options['short']) { + $tmp['data-short'] = $symbol; + } else { + $tmp['data-short'] = $choice->getName(); + } + + $tmp += [ + 'data-symbol' => $symbol, + ]; + + return $tmp; + } + + /** + * Returns the choice label for the given AbstractStructuralDBElement. + * @param AbstractStructuralDBElement $choice + * @return string + */ + public function generateChoiceLabel(AbstractStructuralDBElement $choice): string + { + return $choice->getName(); + } + + /** + * Returns the choice value for the given AbstractStructuralDBElement. + * @param AbstractStructuralDBElement|null $element + * @return string|int|null + */ + public function generateChoiceValue(?AbstractStructuralDBElement $element) + { + if ($element === null) { + return null; + } + + /** + * Do not change the structure below, even when inspection says it can be replaced with a null coalescing operator. + * It is important that the value returned here for a existing element is an int, and for a new element a string. + * I dont really understand why, but it seems to be important for the choice_loader to work correctly. + * So please do not change this! + */ + 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(); + } + + /** + * @param AbstractStructuralDBElement|null $element + * @return string|null + */ + public function generateGroupBy(AbstractStructuralDBElement $element): ?string + { + //Show entities that are not added to DB yet separately from other entities + if ($element->getID() === null) { + return $this->translator->trans('entity.select.group.new_not_added_to_DB'); + } + + return null; + } +} \ No newline at end of file diff --git a/src/Form/Type/StructuralEntityType.php b/src/Form/Type/StructuralEntityType.php index ed537ce5..b2c5195b 100644 --- a/src/Form/Type/StructuralEntityType.php +++ b/src/Form/Type/StructuralEntityType.php @@ -24,6 +24,7 @@ namespace App\Form\Type; use App\Entity\Attachments\AttachmentType; use App\Entity\Base\AbstractStructuralDBElement; +use App\Form\Type\Helper\StructuralEntityChoiceHelper; use App\Form\Type\Helper\StructuralEntityChoiceLoader; use App\Services\Attachments\AttachmentURLGenerator; use App\Services\Trees\NodesListBuilder; @@ -53,20 +54,20 @@ use Symfony\Contracts\Translation\TranslatorInterface; class StructuralEntityType extends AbstractType { protected EntityManagerInterface $em; - protected AttachmentURLGenerator $attachmentURLGenerator; protected TranslatorInterface $translator; + protected StructuralEntityChoiceHelper $choice_helper; /** * @var NodesListBuilder */ - protected $builder; + protected NodesListBuilder $builder; - public function __construct(EntityManagerInterface $em, NodesListBuilder $builder, AttachmentURLGenerator $attachmentURLGenerator, TranslatorInterface $translator) + public function __construct(EntityManagerInterface $em, NodesListBuilder $builder, TranslatorInterface $translator, StructuralEntityChoiceHelper $choice_helper) { $this->em = $em; $this->builder = $builder; - $this->attachmentURLGenerator = $attachmentURLGenerator; $this->translator = $translator; + $this->choice_helper = $choice_helper; } public function buildForm(FormBuilderInterface $builder, array $options): void @@ -105,44 +106,23 @@ class StructuralEntityType extends AbstractType 'subentities_of' => null, //Only show entities with the given parent class 'disable_not_selectable' => false, //Disable entries with not selectable property 'choice_value' => function (?AbstractStructuralDBElement $element) { - if ($element === null) { - return null; - } - - /** - * Do not change the structure below, even when inspection says it can be replaced with a null coalescing operator. - * It is important that the value returned here for a existing element is an int, and for a new element a string. - * I dont really understand why, but it seems to be important for the choice_loader to work correctly. - * So please do not change this! - */ - 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(); + return $this->choice_helper->generateChoiceValue($element); }, //Use the element id as option value and for comparing items 'choice_loader' => function (Options $options) { return new StructuralEntityChoiceLoader($options, $this->builder, $this->em); }, 'choice_label' => function (Options $options) { return function ($choice, $key, $value) use ($options) { - return $this->generateChoiceLabels($choice, $key, $value, $options); + return $this->choice_helper->generateChoiceLabel($choice, $options); }; }, 'choice_attr' => function (Options $options) { return function ($choice, $key, $value) use ($options) { - return $this->generateChoiceAttr($choice, $key, $value, $options); + return $this->choice_helper->generateChoiceAttr($choice, $options); }; }, - 'group_by' => function (AbstractStructuralDBElement $element) - { - //Show entities that are not added to DB yet separately from other entities - if ($element->getID() === null) { - return $this->translator->trans('entity.select.group.new_not_added_to_DB'); - } - - return null; + 'group_by' => function (AbstractStructuralDBElement $element) { + return $this->choice_helper->generateGroupBy($element); }, 'choice_translation_domain' => false, //Don't translate the entity names ]); @@ -207,61 +187,4 @@ class StructuralEntityType extends AbstractType //Otherwise just return the value return $value; } - - protected function generateChoiceAttr(AbstractStructuralDBElement $choice, $key, $value, $options): array - { - $tmp = []; - - //Disable attribute if the choice is marked as not selectable - if ($options['disable_not_selectable'] && $choice->isNotSelectable()) { - $tmp += ['disabled' => 'disabled']; - } - - if ($choice instanceof AttachmentType) { - $tmp += ['data-filetype_filter' => $choice->getFiletypeFilter()]; - } - - $level = $choice->getLevel(); - /** @var AbstractStructuralDBElement|null $parent */ - $parent = $options['subentities_of']; - if (null !== $parent) { - $level -= $parent->getLevel() - 1; - } - - $tmp += [ - 'data-level' => $level, - 'data-parent' => $choice->getParent() ? $choice->getParent()->getFullPath() : null, - 'data-path' => $choice->getFullPath('->'), - 'data-image' => $choice->getMasterPictureAttachment() ? $this->attachmentURLGenerator->getThumbnailURL($choice->getMasterPictureAttachment(), 'thumbnail_xs') : null, - ]; - - if ($choice instanceof AttachmentType && !empty($choice->getFiletypeFilter())) { - $tmp += ['data-filetype_filter' => $choice->getFiletypeFilter()]; - } - - return $tmp; - } - - protected function getElementNameWithLevelWhitespace(AbstractStructuralDBElement $choice, $options, $whitespace = "   "): string - { - /** @var AbstractStructuralDBElement|null $parent */ - $parent = $options['subentities_of']; - - /*** @var AbstractStructuralDBElement $choice */ - $level = $choice->getLevel(); - //If our base entity is not the root level, we need to change the level, to get zero position - if (null !== $options['subentities_of']) { - $level -= $parent->getLevel() - 1; - } - - $tmp = str_repeat($whitespace, $level); //Use 3 spaces for intendation - $tmp .= htmlspecialchars($choice->getName()); - - return $tmp; - } - - protected function generateChoiceLabels(AbstractStructuralDBElement $choice, $key, $value, $options): string - { - return $choice->getName(); - } } diff --git a/templates/components/datatables.macro.html.twig b/templates/components/datatables.macro.html.twig index c699838a..f798fb28 100644 --- a/templates/components/datatables.macro.html.twig +++ b/templates/components/datatables.macro.html.twig @@ -32,36 +32,40 @@ {# #} {% trans with {'%count%': ''} %}part_list.action.part_count{% endtrans %} - + + + + + + + + + + + + + + + + + + + + + - - + + + - +
+ +