mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2025-06-21 09:35:49 +02:00
Use stimulus controller for sidebar treeviews.
This commit is contained in:
parent
2d1aa829f6
commit
fc90259032
6 changed files with 157 additions and 169 deletions
63
assets/controllers/elements/sidebar_tree_controller.js
Normal file
63
assets/controllers/elements/sidebar_tree_controller.js
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import {Controller} from "@hotwired/stimulus";
|
||||||
|
import {default as TreeController} from "./tree_controller";
|
||||||
|
|
||||||
|
import "patternfly-bootstrap-treeview/src/css/bootstrap-treeview.css"
|
||||||
|
import "patternfly-bootstrap-treeview";
|
||||||
|
|
||||||
|
export default class extends TreeController {
|
||||||
|
static targets = [ "tree", 'sourceText' ];
|
||||||
|
|
||||||
|
_storage_key;
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
const default_mode = this.element.dataset.defaultMode;
|
||||||
|
|
||||||
|
this._storage_key = 'tree_' + this.element.id;
|
||||||
|
|
||||||
|
//Check if we have a saved mode
|
||||||
|
const stored_mode = localStorage.getItem(this._storage_key);
|
||||||
|
|
||||||
|
//Use stored mode if possible, otherwise use default
|
||||||
|
if(stored_mode) {
|
||||||
|
try {
|
||||||
|
this.setMode(stored_mode);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
//If an error happenes, use the default mode
|
||||||
|
this.setMode(default_mode);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setMode(default_mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setMode(mode) {
|
||||||
|
//Find the button for this mode
|
||||||
|
const modeButton = this.element.querySelector(`[data-mode="${mode}"]`);
|
||||||
|
if(!modeButton) {
|
||||||
|
throw new Error(`Could not find button for mode ${mode}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get the url and text from the button
|
||||||
|
const url = modeButton.dataset.url;
|
||||||
|
const text = modeButton.dataset.text;
|
||||||
|
|
||||||
|
this.sourceTextTarget.innerText = text;
|
||||||
|
|
||||||
|
this.setURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
changeDataSource(event)
|
||||||
|
{
|
||||||
|
const mode = event.params.mode ?? event.target.dataset.mode;
|
||||||
|
const url = event.params.url ?? event.target.dataset.url;
|
||||||
|
const text = event.params.text ?? event.target.dataset.text;
|
||||||
|
|
||||||
|
this.sourceTextTarget.innerText = text;
|
||||||
|
|
||||||
|
this.setURL(url);
|
||||||
|
|
||||||
|
//Save the mode in local storage
|
||||||
|
localStorage.setItem(this._storage_key, mode);
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,6 +6,9 @@ import "patternfly-bootstrap-treeview";
|
||||||
export default class extends Controller {
|
export default class extends Controller {
|
||||||
static targets = [ "tree" ];
|
static targets = [ "tree" ];
|
||||||
|
|
||||||
|
_url = null;
|
||||||
|
_data = null;
|
||||||
|
|
||||||
connect() {
|
connect() {
|
||||||
const treeElement = this.treeTarget;
|
const treeElement = this.treeTarget;
|
||||||
if (!treeElement) {
|
if (!treeElement) {
|
||||||
|
@ -13,9 +16,30 @@ export default class extends Controller {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Fetch data and initialize tree
|
this._url = this.element.dataset.treeUrl;
|
||||||
this._getData().then(this._fillTree.bind(this));
|
this._data = this.element.dataset.treeData;
|
||||||
|
|
||||||
|
this.reinitTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
reinitTree()
|
||||||
|
{
|
||||||
|
//Fetch data and initialize tree
|
||||||
|
this._getData()
|
||||||
|
.then(this._fillTree.bind(this))
|
||||||
|
.catch((err) => {
|
||||||
|
console.error("Could not load the tree data: " + err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
setData(data) {
|
||||||
|
this._data = data;
|
||||||
|
this.reinitTree();
|
||||||
|
}
|
||||||
|
|
||||||
|
setURL(url) {
|
||||||
|
this._url = url;
|
||||||
|
this.reinitTree();
|
||||||
}
|
}
|
||||||
|
|
||||||
_fillTree(data) {
|
_fillTree(data) {
|
||||||
|
@ -65,7 +89,7 @@ export default class extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
searchInput(event) {
|
searchInput(event) {
|
||||||
const data = event.data;
|
const data = event.target.value;
|
||||||
//Do nothing if no data was passed
|
//Do nothing if no data was passed
|
||||||
|
|
||||||
const tree = this.treeTarget;
|
const tree = this.treeTarget;
|
||||||
|
@ -73,10 +97,19 @@ export default class extends Controller {
|
||||||
$(tree).treeview('search', [data]);
|
$(tree).treeview('search', [data]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
_getData() {
|
_getData() {
|
||||||
//Use lambda function to preserve this context
|
//Use lambda function to preserve this context
|
||||||
return new Promise((myResolve, myReject) => {
|
return new Promise((myResolve, myReject) => {
|
||||||
return myResolve(this.element.dataset.treeData);
|
//If a url is defined, fetch the data from the url
|
||||||
|
if (this._url) {
|
||||||
|
return fetch(this._url)
|
||||||
|
.then((response) => myResolve(response.json()))
|
||||||
|
.catch((err) => myReject(err));
|
||||||
|
}
|
||||||
|
|
||||||
|
//Otherwise load the data provided via the data attribute
|
||||||
|
return myResolve(this._data);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,7 +13,7 @@ class SidebarHelper {
|
||||||
console.info("Base path is " + this.BASE);
|
console.info("Base path is " + this.BASE);
|
||||||
|
|
||||||
this.registerSidebarHideButton();
|
this.registerSidebarHideButton();
|
||||||
this.fillTrees();
|
//this.fillTrees();
|
||||||
}
|
}
|
||||||
|
|
||||||
registerSidebarHideButton()
|
registerSidebarHideButton()
|
||||||
|
@ -55,146 +55,6 @@ class SidebarHelper {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fill the trees with the given data.
|
|
||||||
*/
|
|
||||||
fillTrees()
|
|
||||||
{
|
|
||||||
let categories = localStorage.getItem("tree_datasource_tree-categories");
|
|
||||||
let devices = localStorage.getItem("tree_datasource_tree-devices");
|
|
||||||
let tools = localStorage.getItem("tree_datasource_tree-tools");
|
|
||||||
|
|
||||||
if(categories == null) {
|
|
||||||
categories = "categories";
|
|
||||||
}
|
|
||||||
|
|
||||||
if(devices == null) {
|
|
||||||
devices = "devices";
|
|
||||||
}
|
|
||||||
|
|
||||||
if(tools == null) {
|
|
||||||
tools = "tools";
|
|
||||||
}
|
|
||||||
|
|
||||||
this.treeLoadDataSource("tree-categories", categories);
|
|
||||||
this.treeLoadDataSource("tree-devices", devices);
|
|
||||||
this.treeLoadDataSource("tree-tools", tools);
|
|
||||||
|
|
||||||
this.trees_filled = true;
|
|
||||||
|
|
||||||
let _this = this;
|
|
||||||
|
|
||||||
//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<span class='caret'></span>"; //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 {
|
|
||||||
localStorage.setItem("tree_datasource_" + target, mode);
|
|
||||||
_this.treeLoadDataSource(target, mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load the given url into the tree with the given id.
|
|
||||||
* @param target_id
|
|
||||||
* @param datasource
|
|
||||||
*/
|
|
||||||
treeLoadDataSource(target_id, datasource) {
|
|
||||||
let text = $(".tree-btns[data-mode='" + datasource + "']").html();
|
|
||||||
text = text + " \n<span class='caret'></span>"; //Add caret or it will be removed, when written into title
|
|
||||||
switch(datasource) {
|
|
||||||
case "categories":
|
|
||||||
this.initTree("#" + target_id, 'tree/categories');
|
|
||||||
break;
|
|
||||||
case "locations":
|
|
||||||
this.initTree("#" + target_id, 'tree/locations');
|
|
||||||
break;
|
|
||||||
case "footprints":
|
|
||||||
this.initTree("#" + target_id, 'tree/footprints');
|
|
||||||
break;
|
|
||||||
case "manufacturers":
|
|
||||||
this.initTree("#" + target_id, 'tree/manufacturers');
|
|
||||||
break;
|
|
||||||
case "suppliers":
|
|
||||||
this.initTree("#" + target_id, 'tree/suppliers');
|
|
||||||
break;
|
|
||||||
case "tools":
|
|
||||||
this.initTree("#" + target_id, 'tree/tools');
|
|
||||||
break;
|
|
||||||
case "devices":
|
|
||||||
this.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
|
|
||||||
*/
|
|
||||||
initTree(tree, url) {
|
|
||||||
//Get primary color from css variable
|
|
||||||
const primary_color = getComputedStyle(document.documentElement).getPropertyValue('--bs-info');
|
|
||||||
|
|
||||||
//let contextmenu_handler = this.onNodeContextmenu;
|
|
||||||
$.getJSON(this.BASE + url, function (data) {
|
|
||||||
// @ts-ignore
|
|
||||||
$(tree).treeview({
|
|
||||||
data: data,
|
|
||||||
enableLinks: true,
|
|
||||||
showIcon: false,
|
|
||||||
showBorder: true,
|
|
||||||
searchResultBackColor: primary_color,
|
|
||||||
searchResultColor: '#000',
|
|
||||||
onNodeSelected: function(event, data) {
|
|
||||||
if(data.href) {
|
|
||||||
|
|
||||||
//Simulate a click so we just change the inner frame
|
|
||||||
let a = document.createElement('a');
|
|
||||||
a.setAttribute('href', data.href);
|
|
||||||
a.innerHTML = "";
|
|
||||||
$(tree).append(a);
|
|
||||||
a.click();
|
|
||||||
a.remove();
|
|
||||||
//Turbo.visit(data.href)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
//onNodeContextmenu: contextmenu_handler,
|
|
||||||
expandIcon: "fas fa-plus fa-fw fa-treeview", collapseIcon: "fas fa-minus fa-fw fa-treeview"})
|
|
||||||
.on('initialized', function() {
|
|
||||||
$(this).treeview('collapseAll', { silent: true });
|
|
||||||
|
|
||||||
//Implement searching if needed.
|
|
||||||
if($(this).data('treeSearch')) {
|
|
||||||
let _this = this;
|
|
||||||
let $search = $($(this).data('treeSearch'));
|
|
||||||
$search.on( 'input', function() {
|
|
||||||
$(_this).treeview('collapseAll', { silent: true });
|
|
||||||
$(_this).treeview('search', [$search.val()]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new SidebarHelper();
|
export default new SidebarHelper();
|
|
@ -80,7 +80,7 @@ class TreeController extends AbstractController
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route("/category/{id}", name="tree_category")
|
* @Route("/category/{id}", name="tree_category")
|
||||||
* @Route("/categories")
|
* @Route("/categories", name="tree_category_root")
|
||||||
*/
|
*/
|
||||||
public function categoryTree(?Category $category = null): JsonResponse
|
public function categoryTree(?Category $category = null): JsonResponse
|
||||||
{
|
{
|
||||||
|
@ -91,7 +91,7 @@ class TreeController extends AbstractController
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route("/footprint/{id}", name="tree_footprint")
|
* @Route("/footprint/{id}", name="tree_footprint")
|
||||||
* @Route("/footprints")
|
* @Route("/footprints", name="tree_footprint_root")
|
||||||
*/
|
*/
|
||||||
public function footprintTree(?Footprint $footprint = null): JsonResponse
|
public function footprintTree(?Footprint $footprint = null): JsonResponse
|
||||||
{
|
{
|
||||||
|
@ -102,7 +102,7 @@ class TreeController extends AbstractController
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route("/location/{id}", name="tree_location")
|
* @Route("/location/{id}", name="tree_location")
|
||||||
* @Route("/locations")
|
* @Route("/locations", name="tree_location_root")
|
||||||
*/
|
*/
|
||||||
public function locationTree(?Storelocation $location = null): JsonResponse
|
public function locationTree(?Storelocation $location = null): JsonResponse
|
||||||
{
|
{
|
||||||
|
@ -113,7 +113,7 @@ class TreeController extends AbstractController
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route("/manufacturer/{id}", name="tree_manufacturer")
|
* @Route("/manufacturer/{id}", name="tree_manufacturer")
|
||||||
* @Route("/manufacturers")
|
* @Route("/manufacturers", name="tree_manufacturer_root")
|
||||||
*/
|
*/
|
||||||
public function manufacturerTree(?Manufacturer $manufacturer = null): JsonResponse
|
public function manufacturerTree(?Manufacturer $manufacturer = null): JsonResponse
|
||||||
{
|
{
|
||||||
|
@ -124,7 +124,7 @@ class TreeController extends AbstractController
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route("/supplier/{id}", name="tree_supplier")
|
* @Route("/supplier/{id}", name="tree_supplier")
|
||||||
* @Route("/suppliers")
|
* @Route("/suppliers", name="tree_supplier_root")
|
||||||
*/
|
*/
|
||||||
public function supplierTree(?Supplier $supplier = null): JsonResponse
|
public function supplierTree(?Supplier $supplier = null): JsonResponse
|
||||||
{
|
{
|
||||||
|
@ -135,7 +135,7 @@ class TreeController extends AbstractController
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Route("/device/{id}", name="tree_device")
|
* @Route("/device/{id}", name="tree_device")
|
||||||
* @Route("/devices")
|
* @Route("/devices", name="tree_device_root")
|
||||||
*/
|
*/
|
||||||
public function deviceTree(?Device $device = null): JsonResponse
|
public function deviceTree(?Device $device = null): JsonResponse
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,21 +1,13 @@
|
||||||
|
{% import "elements/tree_macros.html.twig" as tree %}
|
||||||
|
|
||||||
{% macro sidebar_dropdown(target) %}
|
|
||||||
<li class="dropdown-header">{% trans %}actions{% endtrans %}</li>
|
|
||||||
<li><a href="#" class="tree-btns dropdown-item" data-mode="expand" data-target="{{ target }}">{% trans %}expandAll{% endtrans %}</a></li>
|
|
||||||
<li><a href="#" class="tree-btns dropdown-item" data-mode="collapse" data-target="{{ target }}">{% trans %}reduceAll{% endtrans %}</a></li>
|
|
||||||
<li role="separator" class="dropdown-divider"></li>
|
|
||||||
<li class="dropdown-header">{% trans %}datasource{% endtrans %}</li>
|
|
||||||
<li><a href="#" class="tree-btns dropdown-item" data-mode="categories" data-target="{{ target }}">{% trans %}category.labelp{% endtrans %}</a></li>
|
|
||||||
<li><a href="#" class="tree-btns dropdown-item" data-mode="locations" data-target="{{ target }}">{% trans %}storelocation.labelp{% endtrans %}</a></li>
|
|
||||||
<li><a href="#" class="tree-btns dropdown-item" data-mode="footprints" data-target="{{ target }}">{% trans %}footprint.labelp{% endtrans %}</a></li>
|
|
||||||
<li><a href="#" class="tree-btns dropdown-item" data-mode="manufacturers" data-target="{{ target }}">{% trans %}manufacturer.labelp{% endtrans %}</a></li>
|
|
||||||
<li><a href="#" class="tree-btns dropdown-item" data-mode="suppliers" data-target="{{ target }}">{% trans %}supplier.labelp{% endtrans %}</a></li>
|
|
||||||
<li><a href="#" class="tree-btns dropdown-item" data-mode="devices" data-target="{{ target }}">{% trans %}device.labelp{% endtrans %}</a></li>
|
|
||||||
<li><a href="#" class="tree-btns dropdown-item" data-mode="tools" data-target="{{ target }}">{% trans %}tools.label{% endtrans %}</a></li>
|
|
||||||
{% endmacro %}
|
|
||||||
|
|
||||||
<ul class="nav flex-column">
|
<div class="nav flex-column">
|
||||||
<li id="treeBox-categories">
|
|
||||||
|
{{ tree.treeview_sidebar('sidebar-panel-1', 'categories') }}
|
||||||
|
{{ tree.treeview_sidebar('sidebar-panel-2', 'devices') }}
|
||||||
|
{{ tree.treeview_sidebar('sidebar-panel-3', 'tools') }}
|
||||||
|
|
||||||
|
{# <li id="treeBox-categories">
|
||||||
<div class="input-group input-group-sm mb-2 mt-1">
|
<div class="input-group input-group-sm mb-2 mt-1">
|
||||||
<button class="btn btn-light dropdown-toggle" type="button"
|
<button class="btn btn-light dropdown-toggle" type="button"
|
||||||
data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
|
data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
|
||||||
|
@ -58,5 +50,5 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="tree-tools" data-tree-search="#tree-tools-search"></div>
|
<div id="tree-tools" data-tree-search="#tree-tools-search"></div>
|
||||||
</li>
|
</li> #}
|
||||||
</ul>
|
</div>
|
40
templates/elements/tree_macros.html.twig
Normal file
40
templates/elements/tree_macros.html.twig
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
{% macro sidebar_dropdown() %}
|
||||||
|
{# Format is [mode, route, label] #}
|
||||||
|
{% set data_sources = [
|
||||||
|
['categories', path('tree_category_root'), 'category.labelp'],
|
||||||
|
['locations', path('tree_location_root'), 'storelocation.labelp'],
|
||||||
|
['footprints', path('tree_footprint_root'), 'footprint.labelp'],
|
||||||
|
['manufacturer', path('tree_manufacturer_root'), 'manufacturer.labelp'],
|
||||||
|
['suppliers', path('tree_supplier_root'), 'supplier.labelp'],
|
||||||
|
['devices', path('tree_device_root'), 'device.labelp'],
|
||||||
|
['tools', path('tree_tools'), 'tools.label'],
|
||||||
|
] %}
|
||||||
|
|
||||||
|
<li class="dropdown-header">{% trans %}actions{% endtrans %}</li>
|
||||||
|
<li><button class="tree-btns dropdown-item" {{ stimulus_action('elements/sidebar_tree', 'expandAll') }}">{% trans %}expandAll{% endtrans %}</a></li>
|
||||||
|
<li><button class="tree-btns dropdown-item" {{ stimulus_action('elements/sidebar_tree', 'collapseAll') }}">{% trans %}reduceAll{% endtrans %}</a></li>
|
||||||
|
<li role="separator" class="dropdown-divider"></li>
|
||||||
|
<li class="dropdown-header">{% trans %}datasource{% endtrans %}</li>
|
||||||
|
|
||||||
|
{% for source in data_sources %}
|
||||||
|
<li><button class="tree-btns dropdown-item" data-mode="{{ source[0] }}" data-url="{{ source[1] }}" data-text="{{ source[2] | trans }}"
|
||||||
|
{{ stimulus_action('elements/sidebar_tree', 'changeDataSource') }}
|
||||||
|
>{{ source[2] | trans }}</button></li>
|
||||||
|
{% endfor %}
|
||||||
|
{% endmacro %}
|
||||||
|
|
||||||
|
{% macro treeview_sidebar(id, default_mode) %}
|
||||||
|
<div {{ stimulus_controller('elements/sidebar_tree') }} data-default-mode="{{ default_mode }}" id="{{ id }}">
|
||||||
|
<div class="input-group input-group-sm mb-2 mt-1">
|
||||||
|
<button class="btn btn-light dropdown-toggle" type="button"
|
||||||
|
data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
|
||||||
|
><span class="sidebar-title" {{ stimulus_target('elements/sidebar_tree', 'sourceText') }}>Loading...</span></button>
|
||||||
|
<ul class="dropdown-menu" aria-labelledby="dropdownCat">
|
||||||
|
{{ _self.sidebar_dropdown('tree-categories') }}
|
||||||
|
</ul>
|
||||||
|
<input type="search" class="form-control bg-light border-0" placeholder="{% trans %}search.placeholder{% endtrans %}" {{ stimulus_action('elements/sidebar_tree', 'searchInput') }}>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="tree-categories" {{ stimulus_target('elements/sidebar_tree', 'tree') }}></div>
|
||||||
|
</div>
|
||||||
|
{% endmacro %}
|
Loading…
Add table
Add a link
Reference in a new issue