CoolCMS API

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

$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

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
string

getBodySnippetNames()

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|null

getTreeLabel()

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
string

getTreePath()

public getTreePath(object $entity) : array{full: string, short: string, segments: list}|null
Parameters
$entity : object
Return values
array{full: string, short: string, segments: list}|null —

row 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
bool

isTreePathVisible()

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
bool

isTreeViewChosen()

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
bool

buildSubtreeFilters()

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
int

findTreeParentIds()

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>|null

finishTreeSignal()

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
string

getTreeSessionSection()

private getTreeSessionSection() : SessionSection
Return values
SessionSection

loadExpandedIds()

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|null

storeExpandedIds()

private storeExpandedIds(array<int, int> $ids) : void
Parameters
$ids : array<int, int>
On this page

Search results