TreeStateTrait
State of a tree grid and its signals.
- Expanded nodes live in the session per grid (presenter, component and scope), not in the URL. Until the user expands or collapses anything, the default applies: a tree up to DataGrid::TREE_FULL_LIMIT nodes is fully expanded, a larger one shows only roots.
- Focus (
treeRoot, a persistent parameter) makes the grid show only the subtree of a node with breadcrumbs upward; the URL is shareable. - A non-collapsible tree (DataGrid::setTreeCollapsible()
false, menu) is always fully expanded, without session and focus; above the limit a safeguard treats it as collapsible. - The flat list of a tree (search or column ordering) shows a "Path" column (row ancestors in one query) and a "Show in tree" action (cancels the search, focuses the parent, highlights the row).
The signals expand!, collapse!, expandAll!, collapseAll! and treeRefresh! only change
the display state and write no data. AJAX (naja) redraws the grid body (snippet treeBody or
treeList); in the picker modal and without JS they redirect, which picker.js follows to
redraw the whole modal (see HandleTrait::handleInlineUpdate()).
Table of Contents
Properties
- $highlightId : int|null
- Highlighted row, the target of the "Show in tree" action. The `highlight` parameter is one-shot (not persistent): only the action link carries it, other grid links do not, and the JS removes it from the URL after scrolling the row into view, so a reload does not highlight.
- $treeLimitLogged : bool
- The non-collapsible tree safeguard has already logged (render and signal in one request).
- $treeNodeCount : int|null
-
$treePaths
: array<int, array{full: string, short: string, segments: list
}> - Paths of the rows of the current flat list page ({@see loadTreePaths()}).
- $treeRootMissing : bool
- The focused node from the URL does not exist (deleted or foreign scope); the template shows a notice.
- $treeRootNode : object|null
- $treeRootResolved : bool
Methods
- buildShowInTreeHref() : string
- Builds the "Show in tree" link: cancels search, ordering and page, focuses the parent of the row (a root means no focus; a non-collapsible tree has no focus) and highlights the row once.
- getBodySnippetNames() : array{string, string}
- Returns the whole body snippet and the area with `row-<id>` rows for the current view.
- getHighlightId() : int|null
- getTreeLabel() : string
- Returns the node label for breadcrumbs: the value of the indented column, otherwise the picker label.
-
getTreePath()
: array{full: string, short: string, segments: list
}|null - handleCollapse() : void
- handleCollapseAll() : void
- Collapses everything, also outside the focus (nothing visible there is lost by collapsing).
- handleExpand() : void
- handleExpandAll() : void
- Expands the whole current (focused) subtree, only up to {@see DataGrid::TREE_FULL_LIMIT} nodes in total; above it a "focus a smaller branch" message is shown and the state stays unchanged.
- handleTreeRefresh() : void
- Only redraws the tree body; the JS calls it after a rejected move (`TREE_STALE`) or a parent change.
- isTreeCollapsible() : bool
- Tells whether the tree can be collapsed.
- isTreePathVisible() : bool
- Tells whether the flat list shows the "Path" column and the "Show in tree" action: only a tree grid with a right bound (ancestors are found from `lft/rgt`) outside the tree view, where indentation and breadcrumbs carry the path.
- isTreeViewChosen() : bool
- Tells whether the tree view is chosen: the `view` URL parameter (stored in the session), otherwise the session, otherwise the grid default. The picker modal (parent selection) and a tree without a right bound ("has children" cannot be told, so always whole) always use the tree.
- buildSubtreeFilters() : array<string|int, Filter>
- Returns filters of the focused node subtree (descendants have `lft` inside its bounds); empty without focus.
- buildTreeCrumbs() : array<int, array{id: int|null, label: string}>
- Returns the breadcrumbs of the focused node: ancestors from the bounds (`lft < x and rgt > x`, one query).
- countTreeNodes() : int
- Returns the number of tree nodes within the scope (one count query, cached per request).
- findTreeParentIds() : array<int, int>|null
- Returns ids of nodes with children within the filters, or null when more than {@see DataGrid::TREE_FULL_LIMIT} nodes are in range (expanding all would render too much).
- finishTreeSignal() : void
- Finishes a tree signal: AJAX outside picker redraws the body (plus flash); otherwise it redirects, which `picker.js` follows.
- getNodeBounds() : array{int, int}
- getTreeSessionKey() : string
- State key: presenter, component and scope (each menu has its own).
- getTreeSessionSection() : SessionSection
- loadExpandedIds() : array<int, int>|null
- loadTreePaths() : void
- Loads the ancestors of the flat list page rows in one query within the scope: `lft < max(lft) and rgt > min(rgt) and (lft < l1 and rgt > r1 or ...)`, one pair of bounds per parent ({@see TreePathBuilder::pickQueryBounds()}).
- resolveExpandedIds() : array<int, int>
- Returns the expanded nodes: the session, otherwise the default state (a small tree means all nodes with children).
- resolveTreeRootNode() : object|null
- Returns the focused node (`treeRoot`) as an entity, or null for the whole tree.
- storeExpandedIds() : void
Properties
$highlightId
Highlighted row, the target of the "Show in tree" action. The `highlight` parameter is one-shot (not persistent): only the action link carries it, other grid links do not, and the JS removes it from the URL after scrolling the row into view, so a reload does not highlight.
private
int|null
$highlightId
= null
$treeLimitLogged
The non-collapsible tree safeguard has already logged (render and signal in one request).
private
bool
$treeLimitLogged
= false
$treeNodeCount
private
int|null
$treeNodeCount
= null
$treePaths
Paths of the rows of the current flat list page ({@see loadTreePaths()}).
private
array<int, array{full: string, short: string, segments: list}>
$treePaths
= []
$treeRootMissing
The focused node from the URL does not exist (deleted or foreign scope); the template shows a notice.
private
bool
$treeRootMissing
= false
$treeRootNode
private
object|null
$treeRootNode
= null
$treeRootResolved
private
bool
$treeRootResolved
= false
Methods
buildShowInTreeHref()
Builds the "Show in tree" link: cancels search, ordering and page, focuses the parent of the row (a root means no focus; a non-collapsible tree has no focus) and highlights the row once.
public
buildShowInTreeHref(object $entity) : string
In the picker modal it is a plain link that picker.js handles by swapping the modal, like
breadcrumbs.
Parameters
- $entity : object
Return values
stringgetBodySnippetNames()
Returns the whole body snippet and the area with `row-<id>` rows for the current view.
public
getBodySnippetNames() : array{string, string}
The table and the sort view have separate names in DataGrid.latte (Latte does not allow the
same name in two branches). IMPORTANT: invalidate only what is drawn; otherwise
SnippetRuntime would also render a block from the branch that is not rendered.
Return values
array{string, string} —[body snippet, rows area]
getHighlightId()
public
getHighlightId() : int|null
Return values
int|nullgetTreeLabel()
Returns the node label for breadcrumbs: the value of the indented column, otherwise the picker label.
public
getTreeLabel(object $entity) : string
Parameters
- $entity : object
Return values
stringgetTreePath()
public
getTreePath(object $entity) : array{full: string, short: string, segments: list}|null
Parameters
- $entity : object
Return values
array{full: string, short: string, segments: listrow path; null for a root (no ancestors)
handleCollapse()
public
handleCollapse(int $id) : void
Parameters
- $id : int
handleCollapseAll()
Collapses everything, also outside the focus (nothing visible there is lost by collapsing).
public
handleCollapseAll() : void
handleExpand()
public
handleExpand(int $id) : void
Parameters
- $id : int
handleExpandAll()
Expands the whole current (focused) subtree, only up to {@see DataGrid::TREE_FULL_LIMIT} nodes in total; above it a "focus a smaller branch" message is shown and the state stays unchanged.
public
handleExpandAll() : void
handleTreeRefresh()
Only redraws the tree body; the JS calls it after a rejected move (`TREE_STALE`) or a parent change.
public
handleTreeRefresh() : void
isTreeCollapsible()
Tells whether the tree can be collapsed.
public
isTreeCollapsible() : bool
A non-collapsible grid (menu) behaves as collapsible once it outgrows the limit, otherwise it
would silently render thousands of rows. Without a right bound (rgt) "has children" cannot
be told without loading the children, so the whole tree is always shown.
Return values
boolisTreePathVisible()
Tells whether the flat list shows the "Path" column and the "Show in tree" action: only a tree grid with a right bound (ancestors are found from `lft/rgt`) outside the tree view, where indentation and breadcrumbs carry the path.
public
isTreePathVisible() : bool
Return values
boolisTreeViewChosen()
Tells whether the tree view is chosen: the `view` URL parameter (stored in the session), otherwise the session, otherwise the grid default. The picker modal (parent selection) and a tree without a right bound ("has children" cannot be told, so always whole) always use the tree.
public
isTreeViewChosen() : bool
Return values
boolbuildSubtreeFilters()
Returns filters of the focused node subtree (descendants have `lft` inside its bounds); empty without focus.
private
buildSubtreeFilters(object|null $rootNode) : array<string|int, Filter>
Parameters
- $rootNode : object|null
Return values
array<string|int, Filter>buildTreeCrumbs()
Returns the breadcrumbs of the focused node: ancestors from the bounds (`lft < x and rgt > x`, one query).
private
buildTreeCrumbs(object $rootNode) : array<int, array{id: int|null, label: string}>
Parameters
- $rootNode : object
Return values
array<int, array{id: int|null, label: string}> —from the root down to the parent (the node itself excluded)
countTreeNodes()
Returns the number of tree nodes within the scope (one count query, cached per request).
private
countTreeNodes() : int
Return values
intfindTreeParentIds()
Returns ids of nodes with children within the filters, or null when more than {@see DataGrid::TREE_FULL_LIMIT} nodes are in range (expanding all would render too much).
private
findTreeParentIds(array<string|int, Filter> $extraFilters) : array<int, int>|null
Parameters
- $extraFilters : array<string|int, Filter>
Return values
array<int, int>|nullfinishTreeSignal()
Finishes a tree signal: AJAX outside picker redraws the body (plus flash); otherwise it redirects, which `picker.js` follows.
private
finishTreeSignal([bool $withFlash = false ]) : void
Parameters
- $withFlash : bool = false
getNodeBounds()
private
getNodeBounds(object $node) : array{int, int}
Parameters
- $node : object
Return values
array{int, int} —[lft, rgt]
getTreeSessionKey()
State key: presenter, component and scope (each menu has its own).
private
getTreeSessionKey() : string
Return values
stringgetTreeSessionSection()
private
getTreeSessionSection() : SessionSection
Return values
SessionSectionloadExpandedIds()
private
loadExpandedIds() : array<int, int>|null
Return values
array<int, int>|null —null when the user has not changed the state yet (the default applies)
loadTreePaths()
Loads the ancestors of the flat list page rows in one query within the scope: `lft < max(lft) and rgt > min(rgt) and (lft < l1 and rgt > r1 or ...)`, one pair of bounds per parent ({@see TreePathBuilder::pickQueryBounds()}).
private
loadTreePaths(array<int, object> $items) : void
The envelope alone would return almost the whole tree for scattered search results; the OR conditions return exactly the ancestors. Assigning them to rows by intervals happens in PHP.
Parameters
- $items : array<int, object>
resolveExpandedIds()
Returns the expanded nodes: the session, otherwise the default state (a small tree means all nodes with children).
private
resolveExpandedIds() : array<int, int>
Return values
array<int, int>resolveTreeRootNode()
Returns the focused node (`treeRoot`) as an entity, or null for the whole tree.
private
resolveTreeRootNode() : object|null
A missing or foreign node resets the parameter and raises a notice flag. A 404 would break
the picker modal (picker.js inserts the response into the dialog) and a shared link to a
deleted node; the right reaction is to show the whole tree and say why.
Return values
object|nullstoreExpandedIds()
private
storeExpandedIds(array<int, int> $ids) : void
Parameters
- $ids : array<int, int>