diff --git a/assets/css/app.css b/assets/css/app.css index 5688690a..69c97bad 100644 --- a/assets/css/app.css +++ b/assets/css/app.css @@ -508,6 +508,11 @@ span.highlight { line-height: 1.428; } +.treeview-sm .list-group-item { + padding-top: 3px; + padding-bottom: 3px; +} + /***************************** * Pagination bar *****************************/ diff --git a/assets/js/app.js b/assets/js/app.js index 99c9a05d..e84b9a37 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -24,7 +24,7 @@ import 'datatables.net-select-bs4/css/select.bootstrap4.css' import 'bootstrap-select/dist/css/bootstrap-select.css' import 'awesome-bootstrap-checkbox/awesome-bootstrap-checkbox.css' -import "bootstrap-treeview/src/css/bootstrap-treeview.css" +import "patternfly-bootstrap-treeview/src/css/bootstrap-treeview.css" //require( 'jszip' ); //#require( 'pdfmake' ); @@ -39,12 +39,11 @@ require( 'datatables.net-select-bs4' ); require('bootstrap-select'); require('jquery-form'); -//require('bootstrap-treeview/src/js/bootstrap-treeview'); - //Define jquery globally window.$ = window.jQuery = require("jquery"); -require('bootstrap-treeview/src/js/bootstrap-treeview'); +require('patternfly-bootstrap-treeview/src/js/bootstrap-treeview') + require('./datatables.js'); diff --git a/assets/ts_src/ajax_ui.ts b/assets/ts_src/ajax_ui.ts index 105c8f78..afc562fb 100644 --- a/assets/ts_src/ajax_ui.ts +++ b/assets/ts_src/ajax_ui.ts @@ -217,7 +217,10 @@ class AjaxUI { } }, //onNodeContextmenu: contextmenu_handler, - expandIcon: "fas fa-plus fa-fw fa-treeview", collapseIcon: "fas fa-minus fa-fw fa-treeview"}).treeview('collapseAll', { silent: true }); + expandIcon: "fas fa-plus fa-fw fa-treeview", collapseIcon: "fas fa-minus fa-fw fa-treeview"}) + .on('initialized', function() { + $(this).treeview('collapseAll', { silent: true }); + }); }); } diff --git a/assets/ts_src/event_listeners.ts b/assets/ts_src/event_listeners.ts index 7f37307f..b882ee7b 100644 --- a/assets/ts_src/event_listeners.ts +++ b/assets/ts_src/event_listeners.ts @@ -28,6 +28,8 @@ * */ +import {ajaxUI} from "./ajax_ui"; + /************************************ * * In this file all the functions that has to be called using AjaxUIoperation are registered. @@ -123,10 +125,36 @@ $(document).on("ajaxUI:reload", function () { $(document).on("ajaxUI:start ajaxUI:reload", function () { $(".tooltip").remove(); $('a[title], button[title], span[title], h6[title]') - //@ts-ignore + //@ts-ignore .tooltip("hide").tooltip({container: "body", placement: "auto", boundary: 'window'}); }); +// Add bootstrap treeview on divs with data-tree-data attribute +$(document).on("ajaxUI:start ajaxUI:reload", function() { + $("[data-tree-data]").each(function(index, element) { + let data = $(element).data('treeData'); + + //@ts-ignore + $(element).treeview({ + data: data, + enableLinks: false, + showIcon: false, + showBorder: true, + expandIcon: "fas fa-plus fa-fw fa-treeview", collapseIcon: "fas fa-minus fa-fw fa-treeview", + onNodeSelected: function(event, data) { + if(data.href) { + ajaxUI.navigateTo(data.href); + } + } + }).on('initialized', function() { + $(this).treeview('collapseAll', { silent: true }); + let selected = $(this).treeview('getSelected'); + $(this).treeview('revealNode', [ selected, {silent: true } ]); + }); + + }); +}); + /** * Register the button, to jump to the top of the page. diff --git a/package.json b/package.json index 72bae9c9..e132664f 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,6 @@ "@types/js-cookie": "^2.2.1", "awesome-bootstrap-checkbox": "^1.0.1", "bootstrap-select": "^1.13.8", - "bootstrap-treeview": "jbtronics/bootstrap-treeview", "datatables.net-bs4": "^1.10.19", "datatables.net-buttons-bs4": "^1.5.4", "datatables.net-fixedheader-bs4": "^3.1.5", @@ -32,6 +31,7 @@ "jquery-form": "^4.2.2", "js-cookie": "^2.2.0", "jszip": "^3.2.0", + "patternfly-bootstrap-treeview": "^2.1.8", "pdfmake": "^0.1.53", "ts-loader": "^5.3.3", "typescript": "^3.3.4000" diff --git a/src/Controller/AttachmentTypeController.php b/src/Controller/AttachmentTypeController.php index 13cc82f5..7640f6cc 100644 --- a/src/Controller/AttachmentTypeController.php +++ b/src/Controller/AttachmentTypeController.php @@ -67,7 +67,7 @@ class AttachmentTypeController extends AbstractController } /** - * @Route("/new") + * @Route("/new", name="attachment_type_new") * * @return \Symfony\Component\HttpFoundation\Response */ diff --git a/src/Entity/Attachment.php b/src/Entity/Attachment.php index cd2e8c14..f17cd90e 100644 --- a/src/Entity/Attachment.php +++ b/src/Entity/Attachment.php @@ -91,7 +91,7 @@ class Attachment extends NamedDBElement * * @return DBElement The associated Element. */ - public function getElement(): AttachmentContainingDBElement + public function getElement(): ?AttachmentContainingDBElement { return $this->element; } diff --git a/src/Entity/AttachmentContainingDBElement.php b/src/Entity/AttachmentContainingDBElement.php index 36d68a35..60fd5474 100644 --- a/src/Entity/AttachmentContainingDBElement.php +++ b/src/Entity/AttachmentContainingDBElement.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace App\Entity; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; /** @@ -33,9 +34,9 @@ abstract class AttachmentContainingDBElement extends NamedDBElement /** * @var * //TODO - * //@ORM\OneToMany(targetEntity="Attachment", mappedBy="element") + * @ORM\OneToMany(targetEntity="Attachment", mappedBy="element") */ - protected $attachment; + protected $attachments; //TODO protected $attachmentTypes; @@ -54,7 +55,7 @@ abstract class AttachmentContainingDBElement extends NamedDBElement * * @throws Exception if there was an error */ - public function getAttachmentTypes(): array + public function getAttachmentTypes(): ?array { return $this->attachmentTypes; } @@ -70,7 +71,7 @@ abstract class AttachmentContainingDBElement extends NamedDBElement * * @throws Exception if there was an error */ - public function getAttachments($type_id = null, bool $only_table_attachements = false): array + public function getAttachments($type_id = null, bool $only_table_attachements = false) : Collection { if ($only_table_attachements || $type_id) { $attachements = $this->attachments; diff --git a/src/Entity/AttachmentType.php b/src/Entity/AttachmentType.php index 7d56ee4a..fb26f467 100644 --- a/src/Entity/AttachmentType.php +++ b/src/Entity/AttachmentType.php @@ -25,6 +25,7 @@ namespace App\Entity; use App\Validator\Constraints\NoneOfItsChildren; use Doctrine\Common\Collections\ArrayCollection; +use Doctrine\Common\Collections\Collection; use Doctrine\ORM\Mapping as ORM; /** @@ -58,7 +59,7 @@ class AttachmentType extends StructuralDBElement * @return Attachment[] all attachements with this type, as a one-dimensional array of Attachement-objects * (sorted by their names) */ - public function getAttachementsForType(): ArrayCollection + public function getAttachementsForType(): Collection { // the attribute $this->attachements is used from class "AttachementsContainingDBELement" if (null === $this->attachments) { diff --git a/src/Helpers/TreeViewNode.php b/src/Helpers/TreeViewNode.php index ac282680..3758ead2 100644 --- a/src/Helpers/TreeViewNode.php +++ b/src/Helpers/TreeViewNode.php @@ -40,6 +40,8 @@ class TreeViewNode protected $href; protected $nodes; + protected $state; + /** * Creates a new TreeView node with the given parameters. * @param string $text The text that is shown in the node. (e.g. the name of the node) @@ -53,6 +55,8 @@ class TreeViewNode $this->text = $text; $this->href = $href; $this->nodes = $nodes; + + $this->state = new TreeViewNodeState(); } /** @@ -115,4 +119,29 @@ class TreeViewNode return $this; } + public function getState() : TreeViewNodeState + { + return $this->state; + } + + public function setState(TreeViewNodeState $state) : self + { + $this->state = $state; + return $this; + } + + public function setDisabled(?bool $disabled) : self + { + $this->state->setDisabled($disabled); + + return $this; + } + + public function setSelected(?bool $selected) : self + { + $this->state->setSelected($selected); + + return $this; + } + } diff --git a/src/Helpers/TreeViewNodeState.php b/src/Helpers/TreeViewNodeState.php new file mode 100644 index 00000000..df046f0e --- /dev/null +++ b/src/Helpers/TreeViewNodeState.php @@ -0,0 +1,98 @@ +disabled; + } + + /** + * @param bool|null $disabled + */ + public function setDisabled(?bool $disabled): void + { + $this->disabled = $disabled; + } + + /** + * @return bool|null + */ + public function getExpanded(): ?bool + { + return $this->expanded; + } + + /** + * @param bool|null $expanded + */ + public function setExpanded(?bool $expanded): void + { + $this->expanded = $expanded; + } + + /** + * @return bool|null + */ + public function getSelected(): ?bool + { + return $this->selected; + } + + /** + * @param bool|null $selected + */ + public function setSelected(?bool $selected): void + { + $this->selected = $selected; + } + + +} \ No newline at end of file diff --git a/src/Services/EntityURLGenerator.php b/src/Services/EntityURLGenerator.php index c94a2c91..f78fa399 100644 --- a/src/Services/EntityURLGenerator.php +++ b/src/Services/EntityURLGenerator.php @@ -29,6 +29,7 @@ namespace App\Services; +use App\Entity\AttachmentType; use App\Entity\Category; use App\Entity\NamedDBElement; use App\Entity\Part; @@ -107,6 +108,10 @@ class EntityURLGenerator return $this->urlGenerator->generate('part_edit', ['id' => $entity->getID()]); } + if($entity instanceof AttachmentType) { + return $this->urlGenerator->generate('attachment_type_edit', ['id' => $entity->getID()]); + } + //Otherwise throw an error throw new EntityNotSupported('The given entity is not supported yet!'); } @@ -124,6 +129,10 @@ class EntityURLGenerator return $this->urlGenerator->generate('part_new'); } + if($entity instanceof AttachmentType) { + return $this->urlGenerator->generate('attachment_type_new'); + } + throw new EntityNotSupported('The given entity is not supported yet!'); } diff --git a/src/Services/TreeBuilder.php b/src/Services/TreeBuilder.php index 13956069..3fb7babf 100644 --- a/src/Services/TreeBuilder.php +++ b/src/Services/TreeBuilder.php @@ -31,10 +31,12 @@ namespace App\Services; +use App\Entity\DBElement; use App\Entity\StructuralDBElement; use App\Helpers\TreeViewNode; use App\Repository\StructuralDBElementRepository; use Doctrine\ORM\EntityManagerInterface; +use Symfony\Contracts\Translation\TranslatorInterface; /** * This service gives you multiple possibilities to generate trees. @@ -45,11 +47,13 @@ class TreeBuilder { protected $url_generator; protected $em; + protected $translator; - public function __construct(EntityURLGenerator $URLGenerator, EntityManagerInterface $em) + public function __construct(EntityURLGenerator $URLGenerator, EntityManagerInterface $em, TranslatorInterface $translator) { $this->url_generator = $URLGenerator; $this->em = $em; + $this->translator = $translator; } /** @@ -57,15 +61,18 @@ class TreeBuilder * @param StructuralDBElement $element The element for which the tree should be generated. * @param string $href_type The type of the links that should be used for the links. Set to null, to disable links. * See EntityURLGenerator::getURL for possible types. + * @param DBElement|null $selectedElement When a element is given here, its tree node will be marked as selected in + * the resulting tree. When $selectedElement is not existing in the tree, then nothing happens. * @return TreeViewNode The Node for the given Element. + * @throws \App\Exceptions\EntityNotSupported */ - public function elementToTreeNode(StructuralDBElement $element, ?string $href_type = 'list_parts') : TreeViewNode + public function elementToTreeNode(StructuralDBElement $element, ?string $href_type = 'list_parts', DBElement $selectedElement = null) : TreeViewNode { $children = $element->getSubelements(); $children_nodes = null; foreach ($children as $child) { - $children_nodes[] = $this->elementToTreeNode($child, $href_type); + $children_nodes[] = $this->elementToTreeNode($child, $href_type, $selectedElement); } //Check if we need to generate a href type @@ -75,7 +82,14 @@ class TreeBuilder $href = $this->url_generator->getURL($element, $href_type); } - return new TreeViewNode($element->getName(), $href, $children_nodes); + $tree_node = new TreeViewNode($element->getName(), $href, $children_nodes); + + //Check if we need to select the current part + if ($selectedElement !== null && $element->getID() === $selectedElement->getID()) { + $tree_node->setSelected(true); + } + + return $tree_node; } /** @@ -84,9 +98,12 @@ class TreeBuilder * be generated. * @param string $href_type The type of the links that should be used for the links. Set to null, to disable links. * See EntityURLGenerator::getURL for possible types. + * @param DBElement|null $selectedElement When a element is given here, its tree node will be marked as selected in + * the resulting tree. When $selectedElement is not existing in the tree, then nothing happens. * @return TreeViewNode[] Returns an array, containing all nodes. It is empty if the given class has no elements. + * @throws \App\Exceptions\EntityNotSupported */ - public function typeToTree(string $class_name, ?string $href_type = 'list_parts') : array + public function typeToTree(string $class_name, ?string $href_type = 'list_parts', DBElement $selectedElement = null) : array { /** * @var $repo StructuralDBElementRepository @@ -95,8 +112,27 @@ class TreeBuilder $root_nodes = $repo->findRootNodes(); $array = array(); + + //When we use the newEdit type, add the New Element node. + if ($href_type === 'newEdit') { + //Generate the url for the new node + $href = $this->url_generator->createURL(new $class_name()); + $new_node = new TreeViewNode($this->translator->trans('entity.tree.new'), $href); + //When the id of the selected element is null, then we have a new element, and we need to select "new" node + if ($selectedElement != null && $selectedElement->getID() == null) { + $new_node->setSelected(true); + } + $array[] = $new_node; + //Add spacing + $array[] = (new TreeViewNode(''))->setDisabled(true); + + //Every other treeNode will be used for edit + $href_type = "edit"; + } + + foreach ($root_nodes as $node) { - $array[] = $this->elementToTreeNode($node, $href_type); + $array[] = $this->elementToTreeNode($node, $href_type, $selectedElement); } return $array; diff --git a/src/Twig/AppExtension.php b/src/Twig/AppExtension.php index 4f0fcfca..495af410 100644 --- a/src/Twig/AppExtension.php +++ b/src/Twig/AppExtension.php @@ -29,22 +29,31 @@ namespace App\Twig; +use App\Entity\Attachment; use App\Entity\DBElement; use App\Services\EntityURLGenerator; +use App\Services\TreeBuilder; use Symfony\Component\Cache\Adapter\AdapterInterface; +use Symfony\Component\Serializer\SerializerInterface; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; use s9e\TextFormatter\Bundles\Forum as TextFormatter; +use Twig\TwigFunction; class AppExtension extends AbstractExtension { protected $entityURLGenerator; protected $cache; + protected $serializer; + protected $treeBuilder; - public function __construct(EntityURLGenerator $entityURLGenerator, AdapterInterface $cache) + public function __construct(EntityURLGenerator $entityURLGenerator, AdapterInterface $cache, + SerializerInterface $serializer, TreeBuilder $treeBuilder) { $this->entityURLGenerator = $entityURLGenerator; $this->cache = $cache; + $this->serializer = $serializer; + $this->treeBuilder = $treeBuilder; } public function getFilters() @@ -55,6 +64,19 @@ class AppExtension extends AbstractExtension ]; } + public function getFunctions() + { + return [ + new TwigFunction('generateTreeData', [$this, 'treeData']) + ]; + } + + public function treeData(DBElement $element, string $type = 'newEdit') : string + { + $tree = $this->treeBuilder->typeToTree(get_class($element), $type, $element); + return $this->serializer->serialize($tree, 'json', ['skip_null_values' => true]); + } + public function generateEntityURL(DBElement $entity, string $method = 'info'): string { return $this->entityURLGenerator->getURL($entity, $method); diff --git a/templates/AdminPages/EntityAdminBase.html.twig b/templates/AdminPages/EntityAdminBase.html.twig index 94149446..33957ffd 100644 --- a/templates/AdminPages/EntityAdminBase.html.twig +++ b/templates/AdminPages/EntityAdminBase.html.twig @@ -4,6 +4,10 @@