From 985af66897c8db2bdaee2e584b1a560ecb0d6eaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Radim=20Vaculi=CC=81k?= Date: Sun, 22 Feb 2026 21:44:52 +0100 Subject: [PATCH] Fix: TreeView - use icon swap instead of CSS transform, prevent getChildren on collapse - Add collapsed/expanded/loading icons to template, toggled via CSS state classes - Prevent AJAX request when collapsing (intercept interact event) - Show spinner icon while loading, chevron-down when expanded --- assets/css/datagrid.css | 28 ++++++++++++++---- assets/plugins/features/treeView.ts | 45 ++++++++++++++++++++++------- src/templates/datagrid_tree.latte | 4 ++- 3 files changed, 60 insertions(+), 17 deletions(-) diff --git a/assets/css/datagrid.css b/assets/css/datagrid.css index bcc4d35ff..cf3a9437e 100644 --- a/assets/css/datagrid.css +++ b/assets/css/datagrid.css @@ -351,7 +351,6 @@ text-align: center; position: relative; margin: 0 5px 0 -27px; - transition: transform 0.2s ease-in-out } [data-datagrid-name] .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-left > .chevron:hover { @@ -360,15 +359,34 @@ box-shadow: 0px 0px 3px 0px #b4b4b4 } -[data-datagrid-name] .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-left > .chevron.toggle-rotate { - transform: rotate(90deg) -} - [data-datagrid-name] .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-left > .chevron .fa { font-size: 10px; transform: translate(1px, 0) } +[data-datagrid-name] .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-left > .chevron [data-tree-icon="expanded"], +[data-datagrid-name] .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-left > .chevron [data-tree-icon="loading"] { + display: none; +} + +[data-datagrid-name] .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-left > .chevron.is-loading [data-tree-icon="collapsed"], +[data-datagrid-name] .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-left > .chevron.is-loading [data-tree-icon="expanded"] { + display: none; +} + +[data-datagrid-name] .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-left > .chevron.is-loading [data-tree-icon="loading"] { + display: inline-block; +} + +[data-datagrid-name] .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-left > .chevron.is-expanded [data-tree-icon="collapsed"], +[data-datagrid-name] .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-left > .chevron.is-expanded [data-tree-icon="loading"] { + display: none; +} + +[data-datagrid-name] .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-left > .chevron.is-expanded [data-tree-icon="expanded"] { + display: inline-block; +} + [data-datagrid-name] .datagrid-tree .datagrid-tree-item .datagrid-tree-item-content .datagrid-tree-item-right { position: relative; order: 2; diff --git a/assets/plugins/features/treeView.ts b/assets/plugins/features/treeView.ts index 13610cc23..be82b8b02 100644 --- a/assets/plugins/features/treeView.ts +++ b/assets/plugins/features/treeView.ts @@ -3,27 +3,43 @@ import { Datagrid } from "../.."; export class TreeViewPlugin implements DatagridPlugin { onDatagridInit(datagrid: Datagrid): boolean { - datagrid.ajax.addEventListener("success", ({detail: {payload}}) => { - if (!payload._datagrid_tree) return; + datagrid.ajax.addEventListener("interact", (event) => { + const element = event.detail.element; + if (!element.classList.contains('chevron')) return; - const id = payload._datagrid_tree; - const rowBlock = document.querySelector(`.datagrid-tree-item[data-id="${id}"]`); + const rowBlock = element.closest('.datagrid-tree-item'); const childrenBlock = rowBlock?.querySelector('.datagrid-tree-item-children'); if (!childrenBlock) return; - const isExpanded = childrenBlock.classList.contains('showed'); - const chevron = rowBlock?.querySelector('a.chevron'); - - if (isExpanded) { + if (childrenBlock.classList.contains('showed')) { childrenBlock.innerHTML = ''; childrenBlock.classList.remove('showed'); - if (chevron) chevron.style.transform = "rotate(0deg)"; + element.classList.remove('is-expanded'); + event.preventDefault(); return; } + element.classList.add('is-loading'); + }); + + datagrid.ajax.addEventListener("success", ({detail: {payload}}) => { + if (!payload._datagrid_tree) return; + + const id = payload._datagrid_tree; + const rowBlock = document.querySelector(`.datagrid-tree-item[data-id="${id}"]`); + const childrenBlock = rowBlock?.querySelector('.datagrid-tree-item-children'); + + if (!childrenBlock) return; + + const chevron = rowBlock?.querySelector('a.chevron'); + childrenBlock.classList.add('showed'); - if (chevron) chevron.style.transform = "rotate(90deg)"; + + if (chevron) { + chevron.classList.remove('is-loading'); + chevron.classList.add('is-expanded'); + } const childrenHtml: string[] = []; for (const snippetName in payload.snippets) { @@ -39,7 +55,14 @@ export class TreeViewPlugin implements DatagridPlugin { } childrenBlock.innerHTML = childrenHtml.join(''); - }) + }); + + datagrid.ajax.addEventListener("error", () => { + document.querySelectorAll('a.chevron.is-loading').forEach((chevron) => { + chevron.classList.remove('is-loading'); + }); + }); + return true; } } diff --git a/src/templates/datagrid_tree.latte b/src/templates/datagrid_tree.latte index 6b2b560c9..d8537cee6 100644 --- a/src/templates/datagrid_tree.latte +++ b/src/templates/datagrid_tree.latte @@ -66,7 +66,9 @@
- + + + {foreach $columns as $key => $column} {var $col = 'col-' . $key}