Tree view
Records with a parent of the same table – projects with phases and work packages, organisational units, categories – can be shown as a tree: children are indented under their parent and can be opened and closed. The tree is a view next to the table and the cards, with the same columns, filters, search, actions and selection.
public function collection(Collection $collection): Collection{ return $collection ->columns([ TextColumn::make('title')->sortable()->searchable(), TextColumn::make('status')->labels(self::STATUSES)->badge(), ImageColumn::make('owner_id')->label('Responsible')->user(), TextColumn::make('due_on')->date(), ]) ->views([Collection::VIEW_TABLE, Collection::VIEW_TREE], default: Collection::VIEW_TREE) ->tree(parent: 'parent_id', order: 'position', expand: 1, maxDepth: 4, reorderable: true);}Settings
Section titled “Settings”| Argument | Default | Meaning |
|---|---|---|
parent |
– | the column with the id of the parent record; null for roots |
order |
none | a number column for the order among siblings |
expand |
1 |
how many levels are open when the list is loaded: 0 only the roots, 1 the roots open with their children visible, … |
maxDepth |
10 |
the most levels a record can be moved into (at most 20) |
reorderable |
false |
users who may update records move them, see Moving records |
Collection::VIEW_TREE in views() without tree() throws a
LogicException. The model needs parent and order in its
$allowedFields when records are moved.
What users see
Section titled “What users see”-
Opening and closing with the arrow in front of the first column, or with the keyboard. A closed record shows the number of its children. What a user opens and closes is sent with every request of the list, so it stays when they sort, change the page or move a record.
-
Pages count the roots: 25 per page means 25 roots with their opened children.
-
Sorting a column orders the siblings, not the whole list. Without a sorted column siblings follow the
ordercolumn, then their creation. -
Search and filters show the matching records together with their ancestors, so every match is seen in its place. Ancestors that do not match are greyed out; pages count the matches. Moving is not offered while searching or filtering.
-
Add below: when the form has a field for the parent column with
fromQuery(), each row offers a button that opens the create page with the parent preset:BelongsTo::make('parent_id')->relationship(WorkPackageModel::class, 'title')->fromQuery('parent'), -
Grouping does not apply to the tree; the tree is not offered in related lists, which belong to one parent already.
Keyboard
Section titled “Keyboard”The tree is a treegrid: rows carry aria-level and aria-expanded.
| Key | Effect |
|---|---|
| ↑ ↓, Home End | previous, next, first, last row |
| → | open the record, or go to its first child |
| ← | close the record, or go to its parent |
| Enter | open the detail page |
| Alt + arrow | move the record up, down, in, out |
Moving records
Section titled “Moving records”With reorderable: true each row has a Move menu for users who may update
the record:
| Move | Result |
|---|---|
| Up, Down | swaps places with the sibling before or after it (only with order) |
| Indent | becomes the last child of the sibling before it |
| Outdent | follows its former parent, among the siblings of the parent |
Without order siblings follow the sorting of the list, e.g. by name: records
are only indented and outdented, which changes their parent.
Children move along. The server checks every move again:
- the user must be allowed to update the record and, when its parent changes, the new parent (permission and policies);
- moves that are not possible – the first record up, a root out, more levels
than
maxDepth– are refused (422); - with
order, the order column of the siblings is numbered 1, 2, 3, …; only records whose place changes are saved, each withResource::updateColumn(), so validation and the audit log apply. These moves cannot create a cycle.
Opened levels are loaded with one query per level; to count the children of
closed records one more level is loaded. Records are read through the
resource – tenant, scopeQuery() and the policies of single records apply,
and a record whose parent the user may not see becomes a root. At most 1,000
records are loaded per request; beyond that a hint asks to close records or
to search. The tree suits structures of a few thousand records, not hundreds
of thousands.