diff --git a/assets/ts_src/ajax_ui.js b/assets/ts_src/ajax_ui.js
new file mode 100644
index 00000000..1bf94e3a
--- /dev/null
+++ b/assets/ts_src/ajax_ui.js
@@ -0,0 +1,304 @@
+"use strict";
+/*
+ *
+ * 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
+ *
+ */
+Object.defineProperty(exports, "__esModule", { value: true });
+var Cookies = require("js-cookie");
+/**
+ * Extract the title (The name between the
tags) of a HTML snippet.
+ * @param {string} html The HTML code which should be searched.
+ * @returns {string} The title extracted from the html.
+ */
+function extractTitle(html) {
+ var title = "";
+ var regex = /(.*?)<\/title>/gi;
+ if (regex.test(html)) {
+ var matches = html.match(regex);
+ for (var match in matches) {
+ title = $(matches[match]).text();
+ }
+ }
+ return title;
+}
+var AjaxUI = /** @class */ (function () {
+ function AjaxUI() {
+ this.BASE = "/";
+ this.trees_filled = false;
+ this.statePopped = false;
+ //Make back in the browser go back in history
+ window.onpopstate = this.onPopState;
+ $(document).ajaxError(this.onAjaxError.bind(this));
+ //$(document).ajaxComplete(this.onAjaxComplete.bind(this));
+ }
+ /**
+ * Starts the ajax ui und execute handlers registered in addStartAction().
+ * Should be called in a document.ready, after handlers are set.
+ */
+ AjaxUI.prototype.start = function () {
+ console.info("AjaxUI started!");
+ this.BASE = $("body").data("base-url") + "/";
+ console.info("Base path is " + this.BASE);
+ this.registerLinks();
+ this.registerForm();
+ this.fillTrees();
+ };
+ /**
+ * Fill the trees with the given data.
+ */
+ AjaxUI.prototype.fillTrees = function () {
+ var categories = Cookies.get("tree_datasource_tree-categories");
+ var devices = Cookies.get("tree_datasource_tree-devices");
+ var tools = Cookies.get("tree_datasource_tree-tools");
+ if (typeof categories == "undefined") {
+ categories = "categories";
+ }
+ if (typeof devices == "undefined") {
+ devices = "devices";
+ }
+ if (typeof tools == "undefined") {
+ tools = "tools";
+ }
+ this.treeLoadDataSource("tree-categories", categories);
+ this.treeLoadDataSource("tree-devices", devices);
+ this.treeLoadDataSource("tree-tools", tools);
+ this.trees_filled = true;
+ //Register tree btns to expand all, or to switch datasource.
+ $(".tree-btns").click(function (event) {
+ event.preventDefault();
+ $(this).parents("div.dropdown").removeClass('show');
+ //$(this).closest(".dropdown-menu").removeClass('show');
+ $(".dropdown-menu.show").removeClass("show");
+ var mode = $(this).data("mode");
+ var target = $(this).data("target");
+ var text = $(this).text() + " \n"; //Add caret or it will be removed, when written into title
+ if (mode === "collapse") {
+ // @ts-ignore
+ $('#' + target).treeview('collapseAll', { silent: true });
+ }
+ else if (mode === "expand") {
+ // @ts-ignore
+ $('#' + target).treeview('expandAll', { silent: true });
+ }
+ else {
+ Cookies.set("tree_datasource_" + target, mode);
+ exports.ajaxUI.treeLoadDataSource(target, mode);
+ }
+ return false;
+ });
+ };
+ /**
+ * Load the given url into the tree with the given id.
+ * @param target_id
+ * @param datasource
+ */
+ AjaxUI.prototype.treeLoadDataSource = function (target_id, datasource) {
+ var text = $(".tree-btns[data-mode='" + datasource + "']").html();
+ text = text + " \n"; //Add caret or it will be removed, when written into title
+ switch (datasource) {
+ case "categories":
+ exports.ajaxUI.initTree("#" + target_id, 'tree/categories/');
+ break;
+ case "locations":
+ exports.ajaxUI.initTree("#" + target_id, 'tree/locations');
+ break;
+ case "footprints":
+ exports.ajaxUI.initTree("#" + target_id, 'tree/footprints');
+ break;
+ case "manufacturers":
+ exports.ajaxUI.initTree("#" + target_id, 'tree/manufacturers');
+ break;
+ case "suppliers":
+ exports.ajaxUI.initTree("#" + target_id, 'tree/suppliers');
+ break;
+ case "tools":
+ exports.ajaxUI.initTree("#" + target_id, 'tree/tools/');
+ break;
+ case "devices":
+ exports.ajaxUI.initTree("#" + target_id, 'tree/devices');
+ break;
+ }
+ $("#" + target_id + "-title").html(text);
+ };
+ /**
+ * Fill a treeview with data from the given url.
+ * @param tree The Jquery selector for the tree (e.g. "#tree-tools")
+ * @param url The url from where the data should be loaded
+ */
+ AjaxUI.prototype.initTree = function (tree, url) {
+ //let contextmenu_handler = this.onNodeContextmenu;
+ $.getJSON(exports.ajaxUI.BASE + url, function (data) {
+ // @ts-ignore
+ $(tree).treeview({
+ data: data,
+ enableLinks: false,
+ showIcon: false,
+ showBorder: true,
+ onNodeSelected: function (event, data) {
+ if (data.href) {
+ exports.ajaxUI.navigateTo(data.href);
+ }
+ },
+ //onNodeContextmenu: contextmenu_handler,
+ expandIcon: "fas fa-plus fa-fw fa-treeview", collapseIcon: "fas fa-minus fa-fw fa-treeview"
+ }).treeview('collapseAll', { silent: true });
+ });
+ };
+ /**
+ * Register all links, for loading via ajax.
+ */
+ AjaxUI.prototype.registerLinks = function () {
+ $('a').not(".link-external, [data-no-ajax]").click(function (event) {
+ var a = $(this);
+ var href = $.trim(a.attr("href"));
+ //Ignore links without href attr and nav links ('they only have a #)
+ if (href != null && href != "" && href.charAt(0) !== '#') {
+ event.preventDefault();
+ exports.ajaxUI.navigateTo(href);
+ }
+ });
+ console.debug('Links registered!');
+ };
+ /**
+ * Register all forms for loading via ajax.
+ */
+ AjaxUI.prototype.registerForm = function () {
+ var options = {
+ success: this.onAjaxComplete,
+ beforeSubmit: function (arr, $form, options) {
+ //When data-with-progbar is specified, then show progressbar.
+ if ($form.data("with-progbar") != undefined) {
+ exports.ajaxUI.showProgressBar();
+ }
+ return true;
+ }
+ };
+ $('form').not('[data-no-ajax]').ajaxForm(options);
+ console.debug('Forms registered!');
+ };
+ AjaxUI.prototype.showProgressBar = function () {
+ //Blur content background
+ $('#content').addClass('loading-content');
+ // @ts-ignore
+ $('#progressModal').modal({
+ keyboard: false,
+ backdrop: false,
+ show: true
+ });
+ };
+ AjaxUI.prototype.hideProgressBar = function () {
+ // @ts-ignore
+ $('#progressModal').modal('hide');
+ //Remove the remaining things of the modal
+ $('.modal-backdrop').remove();
+ $('body').removeClass('modal-open');
+ $('body, .navbar').css('padding-right', "");
+ };
+ /**
+ * Navigates to the given URL
+ * @param url The url which should be opened.
+ * @param show_loading Show the loading bar during loading.
+ */
+ AjaxUI.prototype.navigateTo = function (url, show_loading) {
+ if (show_loading === void 0) { show_loading = true; }
+ if (show_loading) {
+ this.showProgressBar();
+ }
+ $.ajax(url, {
+ success: this.onAjaxComplete
+ });
+ //$.ajax(url).promise().done(this.onAjaxComplete);
+ };
+ /**
+ * Called when an error occurs on loading ajax. Outputs the message to the console.
+ */
+ AjaxUI.prototype.onAjaxError = function (event, request, settings) {
+ 'use strict';
+ //Ignore aborted requests.
+ if (request.statusText == 'abort') {
+ return;
+ }
+ console.error("Error getting the ajax data from server!");
+ console.log(event);
+ console.log(request);
+ console.log(settings);
+ //If it was a server error and response is not empty, show it to user.
+ if (request.status == 500 && request.responseText !== "") {
+ console.log("Response:" + request.responseText);
+ }
+ };
+ /**
+ * This function gets called every time, the "back" button in the browser is pressed.
+ * We use it to load the content from history stack via ajax and to rewrite url, so we only have
+ * to load #content-data
+ * @param event
+ */
+ AjaxUI.prototype.onPopState = function (event) {
+ var page = location.href;
+ exports.ajaxUI.statePopped = true;
+ exports.ajaxUI.navigateTo(page);
+ };
+ /**
+ * This function takes the response of an ajax requests, and does the things we need to do for our AjaxUI.
+ * This includes inserting the content and pushing history.
+ * @param responseText
+ * @param textStatus
+ * @param jqXHR
+ */
+ AjaxUI.prototype.onAjaxComplete = function (responseText, textStatus, jqXHR) {
+ console.debug("Ajax load completed!");
+ exports.ajaxUI.hideProgressBar();
+ //Parse response to DOM structure
+ var dom = $.parseHTML(responseText);
+ //And replace the content container
+ $("#content").replaceWith($("#content", dom));
+ //Replace login menu too (so everything is up to date)
+ $("#login-content").replaceWith($('#login-content', dom));
+ //Replace flash messages and show them
+ $("#message-container").replaceWith($('#message-container', dom));
+ $(".toast").toast('show');
+ //Set new title
+ var title = extractTitle(responseText);
+ document.title = title;
+ //Push to history, if we currently arent poping an old value.
+ if (!exports.ajaxUI.statePopped) {
+ // @ts-ignore
+ history.pushState(null, title, this.url);
+ }
+ else {
+ //Clear pop state
+ exports.ajaxUI.statePopped;
+ }
+ //Do things on the new dom
+ exports.ajaxUI.registerLinks();
+ exports.ajaxUI.registerForm();
+ };
+ return AjaxUI;
+}());
+exports.ajaxUI = new AjaxUI();
+//# sourceMappingURL=ajax_ui.js.map
\ No newline at end of file
diff --git a/assets/ts_src/ajax_ui.js.map b/assets/ts_src/ajax_ui.js.map
new file mode 100644
index 00000000..aa016c85
--- /dev/null
+++ b/assets/ts_src/ajax_ui.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"ajax_ui.js","sourceRoot":"","sources":["ajax_ui.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/assets/ts_src/ajax_ui.ts b/assets/ts_src/ajax_ui.ts
index 300d36b6..7ed18ea1 100644
--- a/assets/ts_src/ajax_ui.ts
+++ b/assets/ts_src/ajax_ui.ts
@@ -50,15 +50,12 @@ function extractTitle(html : string) : string {
class AjaxUI {
+ protected BASE = "/";
+
private trees_filled : boolean = false;
private statePopped : boolean = false;
- public test()
- {
- alert("Test");
- }
-
public constructor()
{
//Make back in the browser go back in history
@@ -74,14 +71,132 @@ class AjaxUI {
public start()
{
console.info("AjaxUI started!");
+
+ this.BASE = $("body").data("base-url") + "/";
+ console.info("Base path is " + this.BASE);
+
this.registerLinks();
this.registerForm();
+ this.fillTrees();
}
+ /**
+ * Fill the trees with the given data.
+ */
+ public fillTrees()
+ {
+ let categories = Cookies.get("tree_datasource_tree-categories");
+ let devices = Cookies.get("tree_datasource_tree-devices");
+ let tools = Cookies.get("tree_datasource_tree-tools");
+
+ if(typeof categories == "undefined") {
+ categories = "categories";
+ }
+
+ if(typeof devices == "undefined") {
+ devices = "devices";
+ }
+
+ if(typeof tools == "undefined") {
+ tools = "tools";
+ }
+
+ this.treeLoadDataSource("tree-categories", categories);
+ this.treeLoadDataSource("tree-devices", devices);
+ this.treeLoadDataSource("tree-tools", tools);
+
+ this.trees_filled = true;
+
+ //Register tree btns to expand all, or to switch datasource.
+ $(".tree-btns").click(function (event) {
+ event.preventDefault();
+ $(this).parents("div.dropdown").removeClass('show');
+ //$(this).closest(".dropdown-menu").removeClass('show');
+ $(".dropdown-menu.show").removeClass("show");
+ let mode = $(this).data("mode");
+ let target = $(this).data("target");
+ let text = $(this).text() + " \n"; //Add caret or it will be removed, when written into title
+
+ if (mode==="collapse") {
+ // @ts-ignore
+ $('#' + target).treeview('collapseAll', { silent: true });
+ }
+ else if(mode==="expand") {
+ // @ts-ignore
+ $('#' + target).treeview('expandAll', { silent: true });
+ } else {
+ Cookies.set("tree_datasource_" + target, mode);
+ ajaxUI.treeLoadDataSource(target, mode);
+ }
+
+ return false;
+ });
+ }
+
+ /**
+ * Load the given url into the tree with the given id.
+ * @param target_id
+ * @param datasource
+ */
+ protected treeLoadDataSource(target_id, datasource) {
+ let text : string = $(".tree-btns[data-mode='" + datasource + "']").html();
+ text = text + " \n"; //Add caret or it will be removed, when written into title
+ switch(datasource) {
+ case "categories":
+ ajaxUI.initTree("#" + target_id, 'tree/categories/');
+ break;
+ case "locations":
+ ajaxUI.initTree("#" + target_id, 'tree/locations');
+ break;
+ case "footprints":
+ ajaxUI.initTree("#" + target_id, 'tree/footprints');
+ break;
+ case "manufacturers":
+ ajaxUI.initTree("#" + target_id, 'tree/manufacturers');
+ break;
+ case "suppliers":
+ ajaxUI.initTree("#" + target_id, 'tree/suppliers');
+ break;
+ case "tools":
+ ajaxUI.initTree("#" + target_id, 'tree/tools/');
+ break;
+ case "devices":
+ ajaxUI.initTree("#" + target_id, 'tree/devices');
+ break;
+ }
+
+ $( "#" + target_id + "-title").html(text);
+ }
+
+ /**
+ * Fill a treeview with data from the given url.
+ * @param tree The Jquery selector for the tree (e.g. "#tree-tools")
+ * @param url The url from where the data should be loaded
+ */
+ public initTree(tree, url) {
+ //let contextmenu_handler = this.onNodeContextmenu;
+ $.getJSON(ajaxUI.BASE + url, function (data) {
+ // @ts-ignore
+ $(tree).treeview({
+ data: data,
+ enableLinks: false,
+ showIcon: false,
+ showBorder: true,
+ onNodeSelected: function(event, data) {
+ if(data.href) {
+ ajaxUI.navigateTo(data.href);
+ }
+ },
+ //onNodeContextmenu: contextmenu_handler,
+ expandIcon: "fas fa-plus fa-fw fa-treeview", collapseIcon: "fas fa-minus fa-fw fa-treeview"}).treeview('collapseAll', { silent: true });
+ });
+ }
+
+
/**
* Register all links, for loading via ajax.
*/
- protected registerLinks()
+ public registerLinks()
{
$('a').not(".link-external, [data-no-ajax]").click(function (event) {
let a = $(this);
@@ -92,14 +207,14 @@ class AjaxUI {
ajaxUI.navigateTo(href);
}
}
- )
+ );
console.debug('Links registered!');
}
/**
* Register all forms for loading via ajax.
*/
- protected registerForm()
+ public registerForm()
{
let options : JQueryFormOptions = {
success: this.onAjaxComplete,
@@ -110,7 +225,7 @@ class AjaxUI {
}
return true;
}
- }
+ };
$('form').not('[data-no-ajax]').ajaxForm(options);
console.debug('Forms registered!');
@@ -168,7 +283,10 @@ class AjaxUI {
return;
}
+ console.error("Error getting the ajax data from server!");
console.log(event);
+ console.log(request);
+ console.log(settings);
//If it was a server error and response is not empty, show it to user.
if(request.status == 500 && request.responseText !== "")
{
diff --git a/assets/tsconfig.json b/assets/tsconfig.json
index 36c1e869..1dbb6fc8 100644
--- a/assets/tsconfig.json
+++ b/assets/tsconfig.json
@@ -4,7 +4,7 @@
"target": "es5",
"sourceMap": true,
"typeRoots": ["../node_modules"],
- "types": ["jquery", "js-cookie", "bootstrap", "jquery.form"]
+ "types": ["jquery", "js-cookie", "bootstrap", "jquery.form", "bootstrap-treeview"]
},
"exclude": [
"node_modules"
diff --git a/package.json b/package.json
index c17d0de8..72bae9c9 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
"dependencies": {
"@ckeditor/ckeditor5-build-classic": "^12.0.0",
"@types/bootstrap": "^4.3.0",
+ "@types/bootstrap-treeview": "^1.20.0",
"@types/jquery": "^3.3.29",
"@types/jquery.form": "^3.26.30",
"@types/js-cookie": "^2.2.1",
diff --git a/src/Controller/TreeController.php b/src/Controller/TreeController.php
index 10898734..980814d0 100644
--- a/src/Controller/TreeController.php
+++ b/src/Controller/TreeController.php
@@ -30,9 +30,16 @@
namespace App\Controller;
use App\Entity\Category;
+use App\Entity\Device;
+use App\Entity\Footprint;
+use App\Entity\Manufacturer;
+use App\Entity\Storelocation;
+use App\Entity\Supplier;
use App\Helpers\TreeViewNode;
use App\Services\ToolsTreeBuilder;
+use App\Services\TreeBuilder;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
+use Symfony\Component\Form\Extension\Core\Type\LocaleType;
use Symfony\Component\Routing\Annotation\Route;
/**
@@ -55,10 +62,11 @@ class TreeController extends AbstractController
/**
* @Route("/tree/category/{id}", name="tree_category")
+ * @Route("/tree/categories")
*/
public function categoryTree(TreeBuilder $builder, Category $category = null)
{
- if($category != null) {
+ if ($category != null) {
$tree[] = $builder->elementToTreeNode($category);
} else {
$tree = $builder->typeToTree(Category::class);
@@ -68,7 +76,85 @@ class TreeController extends AbstractController
return $this->json($tree, 200, [], ['skip_null_values' => true]);
}
+ /**
+ * @Route("/tree/footprint/{id}", name="tree_footprint")
+ * @Route("/tree/footprints")
+ */
+ public function footprintTree(TreeBuilder $builder, Footprint $footprint = null)
+ {
+ if ($footprint != null) {
+ $tree[] = $builder->elementToTreeNode($footprint);
+ } else {
+ $tree = $builder->typeToTree(Footprint::class, null);
+ }
+ return $this->json($tree, 200, [], ['skip_null_values' => true]);
+ }
+
+ /**
+ * @Route("/tree/location/{id}", name="tree_location")
+ * @Route("/tree/locations")
+ */
+ public function locationTree(TreeBuilder $builder, Storelocation $location = null)
+ {
+ if ($location != null) {
+ $tree[] = $builder->elementToTreeNode($location);
+ } else {
+ $tree = $builder->typeToTree(Storelocation::class, null);
+ }
+
+
+ return $this->json($tree, 200, [], ['skip_null_values' => true]);
+ }
+
+ /**
+ * @Route("/tree/manufacturer/{id}", name="tree_manufacturer")
+ * @Route("/tree/manufacturers")
+ */
+ public function manufacturerTree(TreeBuilder $builder, Manufacturer $manufacturer = null)
+ {
+ if ($manufacturer != null) {
+ $tree[] = $builder->elementToTreeNode($manufacturer);
+ } else {
+ $tree = $builder->typeToTree(Manufacturer::class, null);
+ }
+
+
+ return $this->json($tree, 200, [], ['skip_null_values' => true]);
+ }
+
+ /**
+ * @Route("/tree/supplier/{id}", name="tree_supplier")
+ * @Route("/tree/suppliers")
+ */
+ public function supplierTree(TreeBuilder $builder, Supplier $supplier = null)
+ {
+ if ($supplier != null) {
+ $tree[] = $builder->elementToTreeNode($supplier);
+ } else {
+ $tree = $builder->typeToTree(Supplier::class, null);
+ }
+
+
+ return $this->json($tree, 200, [], ['skip_null_values' => true]);
+ }
+
+ /**
+ * @Route("/tree/device/{id}", name="tree_device")
+ * @Route("/tree/devices")
+ */
+ public function deviceTree(TreeBuilder $builder, Device $device = null)
+ {
+ if ($device != null) {
+ $tree[] = $builder->elementToTreeNode($device);
+ } else {
+ $tree = $builder->typeToTree(Device::class, null);
+ }
+
+
+ return $this->json($tree, 200, [], ['skip_null_values' => true]);
+ }
+
}
diff --git a/src/Entity/AttachmentType.php b/src/Entity/AttachmentType.php
index ba4d4f98..5c99c437 100644
--- a/src/Entity/AttachmentType.php
+++ b/src/Entity/AttachmentType.php
@@ -29,7 +29,7 @@ use Doctrine\ORM\Mapping as ORM;
/**
* Class AttachmentType.
*
- * @ORM\Entity()
+ * @ORM\Entity(repositoryClass="App\Repository\StructuralDBElementRepository")
* @ORM\Table(name="attachement_types")
*/
class AttachmentType extends StructuralDBElement
diff --git a/src/Entity/Category.php b/src/Entity/Category.php
index f2f6d8fb..c4d97a36 100644
--- a/src/Entity/Category.php
+++ b/src/Entity/Category.php
@@ -28,7 +28,7 @@ use Doctrine\ORM\Mapping as ORM;
/**
* Class AttachmentType.
*
- * @ORM\Entity
+ * @ORM\Entity(repositoryClass="App\Repository\StructuralDBElementRepository")
* @ORM\Table(name="categories")
*/
class Category extends PartsContainingDBElement
diff --git a/src/Entity/Device.php b/src/Entity/Device.php
index 8a60f686..905b119a 100644
--- a/src/Entity/Device.php
+++ b/src/Entity/Device.php
@@ -37,7 +37,7 @@ use Doctrine\ORM\Mapping as ORM;
/**
* Class AttachmentType.
*
- * @ORM\Entity()
+ * @ORM\Entity(repositoryClass="App\Repository\StructuralDBElementRepository")
* @ORM\Table(name="devices")
*/
class Device extends PartsContainingDBElement
diff --git a/src/Entity/Footprint.php b/src/Entity/Footprint.php
index dfd96b53..49cb887e 100644
--- a/src/Entity/Footprint.php
+++ b/src/Entity/Footprint.php
@@ -37,7 +37,7 @@ use Doctrine\ORM\Mapping as ORM;
/**
* Class Footprint.
*
- * @ORM\Entity()
+ * @ORM\Entity(repositoryClass="App\Repository\StructuralDBElementRepository")
* @ORM\Table("footprints")
*/
class Footprint extends PartsContainingDBElement
diff --git a/src/Entity/Manufacturer.php b/src/Entity/Manufacturer.php
index 0cfccf4b..7a327f16 100644
--- a/src/Entity/Manufacturer.php
+++ b/src/Entity/Manufacturer.php
@@ -37,7 +37,7 @@ use Doctrine\ORM\Mapping as ORM;
/**
* Class Manufacturer.
*
- * @ORM\Entity()
+ * @ORM\Entity(repositoryClass="App\Repository\StructuralDBElementRepository")
* @ORM\Table("manufacturers")
*/
class Manufacturer extends Company
diff --git a/src/Entity/Storelocation.php b/src/Entity/Storelocation.php
index a0f43f23..63e424ad 100644
--- a/src/Entity/Storelocation.php
+++ b/src/Entity/Storelocation.php
@@ -37,7 +37,7 @@ use Doctrine\ORM\Mapping as ORM;
/**
* Class Storelocation.
*
- * @ORM\Entity()
+ * @ORM\Entity(repositoryClass="App\Repository\StructuralDBElementRepository")
* @ORM\Table("storelocations")
*/
class Storelocation extends PartsContainingDBElement
diff --git a/src/Entity/StructuralDBElement.php b/src/Entity/StructuralDBElement.php
index 8390e69d..c76e090a 100644
--- a/src/Entity/StructuralDBElement.php
+++ b/src/Entity/StructuralDBElement.php
@@ -36,8 +36,7 @@ use Doctrine\ORM\PersistentCollection;
* It's allowed to have instances of root elements, but if you try to change
* an attribute of a root element, you will get an exception!
*
- * @ORM\MappedSuperclass()
- * //@ORM\Entity(repositoryClass="App\Repository\StructuralDBElementRepository")
+ * @ORM\MappedSuperclass(repositoryClass="App\Repository\StructuralDBElementRepository")
*/
abstract class StructuralDBElement extends AttachmentContainingDBElement
{
@@ -310,50 +309,6 @@ abstract class StructuralDBElement extends AttachmentContainingDBElement
return implode("\n", $html);
}
- public function buildBootstrapTree(
- $page,
- $parameter,
- $recursive = false,
- $show_root = false,
- $use_db_root_name = true,
- $root_name = '$$'
- ): array {
- if ('$$' == $root_name) {
- $root_name = _('Oberste Ebene');
- }
-
- $subelements = $this->getSubelements(false);
- $nodes = array();
-
- foreach ($subelements as $element) {
- $nodes[] = $element->buildBootstrapTree($page, $parameter);
- }
-
- // if we are on root level?
- if (-1 == $this->getParentID()) {
- if ($show_root) {
- $tree = array(
- array('text' => $use_db_root_name ? htmlspecialchars($this->getName()) : $root_name,
- 'href' => $page.'?'.$parameter.'='.$this->getID(),
- 'nodes' => $nodes, ),
- );
- } else { //Dont show root node
- $tree = $nodes;
- }
- } elseif (!empty($nodes)) {
- $tree = array('text' => htmlspecialchars($this->getName()),
- 'href' => $page.'?'.$parameter.'='.$this->getID(),
- 'nodes' => $nodes,
- );
- } else {
- $tree = array('text' => htmlspecialchars($this->getName()),
- 'href' => $page.'?'.$parameter.'='.$this->getID(),
- );
- }
-
- return $tree;
- }
-
/**
* Creates a template loop for a Breadcrumb bar, representing the structural DB element.
*
diff --git a/src/Entity/Supplier.php b/src/Entity/Supplier.php
index 9a7a04b3..0042baa8 100644
--- a/src/Entity/Supplier.php
+++ b/src/Entity/Supplier.php
@@ -37,7 +37,7 @@ use Doctrine\ORM\Mapping as ORM;
/**
* Class Supplier.
*
- * @ORM\Entity()
+ * @ORM\Entity(repositoryClass="App\Repository\StructuralDBElementRepository")
* @ORM\Table("suppliers")
*/
class Supplier extends Company
diff --git a/src/Controller/TreeBuilder.php b/src/Services/TreeBuilder.php
similarity index 88%
rename from src/Controller/TreeBuilder.php
rename to src/Services/TreeBuilder.php
index 2545c429..13956069 100644
--- a/src/Controller/TreeBuilder.php
+++ b/src/Services/TreeBuilder.php
@@ -1,8 +1,9 @@
getSubelements();
$children_nodes = null;
foreach ($children as $child) {
- $children_nodes[] = $this->elementToTreeNode($child);
+ $children_nodes[] = $this->elementToTreeNode($child, $href_type);
}
//Check if we need to generate a href type
@@ -86,7 +86,7 @@ class TreeBuilder
* See EntityURLGenerator::getURL for possible types.
* @return TreeViewNode[] Returns an array, containing all nodes. It is empty if the given class has no elements.
*/
- public function typeToTree(StructuralDBElement $class_name, string $href_type = 'list_parts') : array
+ public function typeToTree(string $class_name, ?string $href_type = 'list_parts') : array
{
/**
* @var $repo StructuralDBElementRepository
@@ -96,7 +96,7 @@ class TreeBuilder
$array = array();
foreach ($root_nodes as $node) {
- $array = $this->elementToTreeNode($node, $href_type);
+ $array[] = $this->elementToTreeNode($node, $href_type);
}
return $array;
diff --git a/templates/base.html.twig b/templates/base.html.twig
index a3818f82..57c6fa1d 100644
--- a/templates/base.html.twig
+++ b/templates/base.html.twig
@@ -13,13 +13,12 @@
-
{% filter trim %}{% block title %}{{ partdb_title}}{% endblock %}{% endfilter %}
{% block stylesheets %}
{{ encore_entry_link_tags('app') }}
{% endblock %}
-
+
{% block body %}
@@ -279,6 +278,7 @@
{% block scripts %}
diff --git a/yarn.lock b/yarn.lock
index 7dce30ec..997797a8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -650,6 +650,13 @@
resolved "https://registry.yarnpkg.com/@types/anymatch/-/anymatch-1.3.1.tgz#336badc1beecb9dacc38bea2cf32adf627a8421a"
integrity sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==
+"@types/bootstrap-treeview@^1.20.0":
+ version "1.20.0"
+ resolved "https://registry.yarnpkg.com/@types/bootstrap-treeview/-/bootstrap-treeview-1.20.0.tgz#08157e2f26b5b278dfd70e2054bbf8aa7d6f0b2d"
+ integrity sha512-o1FFKSNd68ohmyvrHna11q5cLd9aiVUp/Itr7B33OmFY37GFufQvVOH9Iv/+2xcE9tVc7pHfmxucBnQkjZzHCQ==
+ dependencies:
+ "@types/jquery" "*"
+
"@types/bootstrap@^4.3.0":
version "4.3.0"
resolved "https://registry.yarnpkg.com/@types/bootstrap/-/bootstrap-4.3.0.tgz#4cac01617f56239a5146945a98bde64274838c35"