Skip to content

Commit c376796

Browse files
committed
Document AbstractNodeTreeView
Closes #573
1 parent a838470 commit c376796

2 files changed

Lines changed: 250 additions & 0 deletions

File tree

‎docs/php/api/node_tree_views.md‎

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
# Node Tree Views
2+
3+
Node tree views are a generic solution for the rendering of hierarchical, tree-shaped data structures such as categories or menu items.
4+
In addition to rendering, the node tree view also takes care of drag and drop sorting and integrates with the [Interactions](interactions.md) system.
5+
6+
The implementation essentially offers the following advantages:
7+
8+
1. A uniform appearance and usability for the user.
9+
2. An easy way for developers to create their own node tree views.
10+
3. An easy way for developers to extend existing node tree views with interactions.
11+
12+
## Usage
13+
14+
### AbstractNodeTreeView
15+
16+
A node tree view obtains its data from a `\RecursiveIteratorIterator` of `IObjectTreeNode` instances and renders them as a nested list.
17+
18+
Example:
19+
20+
```php
21+
<?php
22+
23+
namespace wcf\system\nodeTreeView\admin;
24+
25+
use wcf\data\IObjectTreeNode;
26+
use wcf\system\nodeTreeView\AbstractNodeTreeView;
27+
28+
final class ExampleNodeTreeView extends AbstractNodeTreeView
29+
{
30+
public function __construct(public readonly int $menuID)
31+
{
32+
$this->setSetPositionsEndpoint("core/menus/{$this->menuID}/items/positions");
33+
}
34+
35+
#[\Override]
36+
protected function createNodeIterator(): \RecursiveIteratorIterator
37+
{
38+
$nodeTree = new ExampleNodeTree($this->menuID);
39+
40+
return $nodeTree->getIterator();
41+
}
42+
43+
#[\Override]
44+
public function getNodeLink(IObjectTreeNode $node): string
45+
{
46+
\assert($node instanceof ExampleNode);
47+
48+
return LinkHandler::getInstance()->getControllerLink(
49+
ExampleEditForm::class,
50+
['id' => $node->getObjectID()]
51+
);
52+
}
53+
54+
#[\Override]
55+
public function isAccessible(): bool
56+
{
57+
return WCF::getSession()->hasPermission('admin.example.canManage');
58+
}
59+
60+
#[\Override]
61+
public function getParameters(): array
62+
{
63+
return ['menuID' => $this->menuID];
64+
}
65+
}
66+
```
67+
68+
The following methods must be implemented:
69+
70+
- `createNodeIterator(): \RecursiveIteratorIterator` returns the iterator that traverses the nodes of the tree.
71+
The traversed objects have to implement `IObjectTreeNode` and have to extend `DatabaseObject` (or decorate one via `DatabaseObjectDecorator`).
72+
- `getNodeLink(IObjectTreeNode $node): string` returns the link to the edit form of the given node.
73+
74+
The following methods can be overridden:
75+
76+
- `isAccessible(): bool` defines whether the node tree view is accessible for the active user. Defaults to `true`.
77+
- `getNodeIcon(IObjectTreeNode $node): string` returns the icon of the given node, or an empty string if the node has no icon.
78+
- `getParameters(): array` returns additional parameters which uniquely identify this view instance (see [Additional Parameters](#additional-parameters)).
79+
80+
### AbstractNodeTreeViewPage
81+
82+
A node tree view can be displayed on a page by inheriting from `AbstractNodeTreeViewPage`.
83+
84+
Example:
85+
86+
```php
87+
<?php
88+
89+
namespace wcf\acp\page;
90+
91+
use wcf\page\AbstractNodeTreeViewPage;
92+
use wcf\system\nodeTreeView\AbstractNodeTreeView;
93+
use wcf\system\nodeTreeView\admin\ExampleNodeTreeView;
94+
95+
/**
96+
* @extends AbstractNodeTreeViewPage<ExampleNodeTreeView>
97+
*/
98+
final class ExampleListPage extends AbstractNodeTreeViewPage
99+
{
100+
#[\Override]
101+
protected function createNodeTreeView(): AbstractNodeTreeView
102+
{
103+
return new ExampleNodeTreeView();
104+
}
105+
}
106+
```
107+
108+
```smarty
109+
{include file='header'}
110+
111+
<div class="section">
112+
{unsafe:$nodeTreeView->render()}
113+
</div>
114+
115+
{include file='footer'}
116+
```
117+
118+
## Nodes
119+
120+
Every node in the tree must implement `wcf\data\IObjectTreeNode`.
121+
The interface extends `\RecursiveIterator`, `\Countable` and `IIDObject` and provides methods like `getDepth()`, `getParentNode()` and `isLastSibling()` that are required for rendering.
122+
123+
Typical implementations either extend an existing tree node class such as `CategoryNode` or `MenuItemNode`, or decorate a `DatabaseObject` via `DatabaseObjectDecorator` and implement the interface on top.
124+
125+
## Customization
126+
127+
### Node Icon
128+
129+
An icon can be assigned to each node by overriding `getNodeIcon()`.
130+
The method receives the current node and is expected to return the HTML for the icon, or an empty string if no icon should be displayed.
131+
132+
```php
133+
#[\Override]
134+
public function getNodeIcon(IObjectTreeNode $node): string
135+
{
136+
\assert($node instanceof ExampleNode);
137+
138+
return $node->getDecoratedObject()->getIcon();
139+
}
140+
```
141+
142+
### Additional Parameters
143+
144+
A node tree view can be provided with additional parameters, e.g. to render the items of a specific menu.
145+
Parameters returned by `getParameters()` are passed to the JavaScript component and are used to derive a unique id for the view (see `getID()`), which is required so that drag and drop and interactions can be attached to the correct DOM element.
146+
147+
```php
148+
final class ExampleNodeTreeView extends AbstractNodeTreeView
149+
{
150+
public function __construct(public readonly int $menuID)
151+
{
152+
}
153+
154+
#[\Override]
155+
public function getParameters(): array
156+
{
157+
return ['menuID' => $this->menuID];
158+
}
159+
}
160+
```
161+
162+
```php
163+
/**
164+
* @extends AbstractNodeTreeViewPage<ExampleNodeTreeView>
165+
*/
166+
final class ExampleListPage extends AbstractNodeTreeViewPage
167+
{
168+
public int $menuID = 0;
169+
170+
#[\Override]
171+
public function readParameters()
172+
{
173+
parent::readParameters();
174+
175+
if (isset($_REQUEST['menuID'])) {
176+
$this->menuID = \intval($_REQUEST['menuID']);
177+
}
178+
}
179+
180+
#[\Override]
181+
protected function createNodeTreeView(): AbstractNodeTreeView
182+
{
183+
return new ExampleNodeTreeView($this->menuID);
184+
}
185+
186+
#[\Override]
187+
protected function getBaseUrlParameters(): array
188+
{
189+
return ['menuID' => $this->menuID];
190+
}
191+
}
192+
```
193+
194+
## Drag and Drop
195+
196+
Nodes can be reordered and re-parented via drag and drop.
197+
The new positions are sent to an RPC API endpoint that is configured via `setSetPositionsEndpoint()`.
198+
199+
```php
200+
$this->setSetPositionsEndpoint("core/menus/{$this->menuID}/items/positions");
201+
```
202+
203+
The endpoint receives the new order of nodes including their parent ids and is responsible for persisting the change.
204+
If no endpoint is set, drag and drop is disabled and the items are rendered in a static order.
205+
206+
## Interactions
207+
208+
Node tree views integrate with the [Interactions](interactions.md) system in two ways: an interaction provider, which is rendered as a context menu next to each node, and quick interactions, which are rendered inline.
209+
210+
### Interaction Provider
211+
212+
An interaction provider can be set via `setInteractionProvider()`.
213+
The interactions defined by the provider are rendered as a context menu next to each node.
214+
215+
```php
216+
final class ExampleNodeTreeView extends AbstractNodeTreeView
217+
{
218+
public function __construct()
219+
{
220+
$provider = new ExampleInteractions();
221+
$provider->addInteractions([
222+
new Divider(),
223+
new EditInteraction(ExampleEditForm::class),
224+
]);
225+
$this->setInteractionProvider($provider);
226+
}
227+
}
228+
```
229+
230+
### Quick Interactions
231+
232+
Quick interactions are rendered inline next to each node and are intended for frequently used actions such as toggling the enabled state of an item.
233+
They are added individually via `addQuickInteraction()`.
234+
235+
```php
236+
final class ExampleNodeTreeView extends AbstractNodeTreeView
237+
{
238+
public function __construct()
239+
{
240+
$this->addQuickInteraction(
241+
new ToggleInteraction(
242+
'enable',
243+
'core/examples/%s/enable',
244+
'core/examples/%s/disable'
245+
)
246+
);
247+
}
248+
}
249+
```

‎mkdocs.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ nav:
6060
- 'Grid Views': 'php/api/grid_views.md'
6161
- 'Interactions': 'php/api/interactions.md'
6262
- 'List Views': 'php/api/list_views.md'
63+
- 'Node Tree Views': 'php/api/node_tree_views.md'
6364
- 'Package Installation Plugins': 'php/api/package_installation_plugins.md'
6465
- 'Show Order': 'php/api/show_order.md'
6566
- 'RPC API':

0 commit comments

Comments
 (0)