Skip to content

Commit c5a0af4

Browse files
committed
chore: expand docs for data providers
1 parent 920374c commit c5a0af4

File tree

4 files changed

+297
-10
lines changed

4 files changed

+297
-10
lines changed

packages/core/src/controlledEnvironment/useCanDropAt.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export const useCanDropAt = () => {
1818
} else {
1919
const resolvedItem = environment.items[draggingPosition.targetItem];
2020
if (
21+
!resolvedItem ||
2122
(!environment.canDropOnFolder && resolvedItem.isFolder) ||
2223
(!environment.canDropOnNonFolder && !resolvedItem.isFolder)
2324
) {

packages/core/src/stories/CustomDataProvider.stories.tsx

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,16 @@
11
import { Meta } from '@storybook/react';
2-
import { useMemo, useState } from 'react';
2+
import { useEffect, useMemo } from 'react';
33
import * as React from 'react';
4-
import { longTree, shortTree } from 'demodata';
5-
import { action } from '@storybook/addon-actions';
4+
import { shortTree } from 'demodata';
65
import { Tree } from '../tree/Tree';
76
import { StaticTreeDataProvider } from '../uncontrolledEnvironment/StaticTreeDataProvider';
87
import { UncontrolledTreeEnvironment } from '../uncontrolledEnvironment/UncontrolledTreeEnvironment';
9-
import { buildTestTree } from '../../test/helpers';
108
import {
119
Disposable,
12-
ExplicitDataSource,
1310
TreeDataProvider,
1411
TreeItem,
1512
TreeItemIndex,
1613
} from '../types';
17-
import { EventEmitter } from '../EventEmitter';
1814

1915
export default {
2016
title: 'Core/Data Provider',
@@ -44,6 +40,12 @@ export const InjectingDataFromOutside = () => {
4440
dataProvider.onDidChangeTreeDataEmitter.emit(['root']);
4541
};
4642

43+
useEffect(() => {
44+
dataProvider.onDidChangeTreeData(changedItemIds => {
45+
console.log(changedItemIds);
46+
});
47+
}, [dataProvider]);
48+
4749
return (
4850
<UncontrolledTreeEnvironment<string>
4951
canDragAndDrop
@@ -75,7 +77,6 @@ class CustomDataProviderImplementation implements TreeDataProvider {
7577
[];
7678

7779
public async getTreeItem(itemId: TreeItemIndex) {
78-
console.log(this.data);
7980
return this.data[itemId];
8081
}
8182

packages/docs/docs/guides/custom-data-provider.mdx

Lines changed: 286 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,157 @@
22
sidebar_position: 2
33
---
44

5-
# Custom Data Provider
5+
# Data Provider
66

7-
When using an uncontrolled environment, you need to provide your data by supplying a data provider.
7+
When using an uncontrolled environment, you need to provide your data by supplying a data provider. The
8+
easiest way to get started is using the [Static Tree Data Provider](/docs/api/classes/StaticTreeDataProvider).
9+
It allows you to provide your data as record which maps item ids to tree items, and gives you the possibility
10+
to react to changes in the tree structure, as well as inject your own changes through change events.
11+
12+
# Static Tree Data Provider
13+
14+
The following example gives a good example of what is possible with static tree data providers. We will look
15+
into the details of the data provider below.
16+
17+
```jsx live
18+
function App() {
19+
const items = useMemo(() => ({ ...shortTree.items }), []);
20+
const dataProvider = useMemo(
21+
() =>
22+
new StaticTreeDataProvider(items, (item, data) => ({
23+
...item,
24+
data,
25+
})),
26+
[items]
27+
);
28+
29+
const injectItem = () => {
30+
const rand = `${Math.random()}`;
31+
items[rand] = { data: 'New Item', index: rand };
32+
items.root.children.push(rand);
33+
dataProvider.onDidChangeTreeDataEmitter.emit(['root']);
34+
};
35+
36+
const removeItem = () => {
37+
if (items.root.children.length === 0) return;
38+
items.root.children.pop();
39+
dataProvider.onDidChangeTreeDataEmitter.emit(['root']);
40+
};
41+
42+
return (
43+
<UncontrolledTreeEnvironment
44+
canDragAndDrop
45+
canDropOnFolder
46+
canReorderItems
47+
dataProvider={dataProvider}
48+
getItemTitle={item => item.data}
49+
viewState={{
50+
'tree-1': {
51+
expandedItems: [],
52+
},
53+
}}
54+
>
55+
<button type="button" onClick={injectItem}>
56+
Inject item
57+
</button>
58+
<button type="button" onClick={removeItem}>
59+
Remove item
60+
</button>
61+
<Tree treeId="tree-1" rootItem="root" treeLabel="Tree Example" />
62+
</UncontrolledTreeEnvironment>
63+
);
64+
}
65+
```
66+
67+
## Creating the data provider with data
68+
69+
First, create the data provider. You want to make sure it isn't recreated on re-renders, so memoize
70+
it in the component in which it is defined.
71+
72+
```tsx
73+
const dataProvider = useMemo(
74+
() =>
75+
new StaticTreeDataProvider(items, (item, data) => ({
76+
...item,
77+
data,
78+
})),
79+
[items]
80+
);
81+
```
82+
83+
The items is a record mapping item ids to tree items, for example:
84+
85+
```typescript
86+
const items = [
87+
{
88+
index: "item-id",
89+
data: { arbitraryData: 123, name: "Hello" },
90+
children: ["item-id-1", "item-id-2"],
91+
isFolder: true
92+
}
93+
]
94+
```
95+
96+
Note that, whatever you provide to the `getItemTitle` prop is used to infer the item display name.
97+
98+
```ts jsx
99+
<UncontrolledTreeEnvironment
100+
getItemTitle={item => item.data.name}
101+
/>
102+
```
103+
104+
## Apply changes from outside
105+
106+
You can apply changes to the underlying data source. Just make sure to let RCT know about that by
107+
emitting a change event on the affected items. Note that, if you add or remove items, the affected item
108+
is the parent item, not the added or removed items.
109+
110+
```ts
111+
const injectItem = () => {
112+
const rand = `${Math.random()}`;
113+
items[rand] = { data: 'New Item', index: rand };
114+
items.root.children.push(rand);
115+
dataProvider.onDidChangeTreeDataEmitter.emit(['root']);
116+
};
117+
118+
const removeItem = () => {
119+
if (items.root.children.length === 0) return;
120+
items.root.children.pop();
121+
dataProvider.onDidChangeTreeDataEmitter.emit(['root']);
122+
};
123+
```
124+
125+
## Reacting to Drag Events
126+
127+
Drag changes are always immediately applied to the visualization, so make sure to implement the `canDropAt`
128+
prop to customize if that should not work in all cases. The static tree data emits tree change events similar
129+
to the ones you would emit when applying changes from outside, so you can react to them in the same way.
130+
131+
```typescript
132+
dataProvider.onDidChangeTreeData(changedItemIds => {
133+
console.log(changedItemIds);
134+
});
135+
```
136+
137+
## Reacting to Rename Events
138+
139+
The second (optional) parameter of the static tree data provider lets you react to rename events. Note that
140+
you can customize whether renaming is possible in the first place through the `canRename` prop.
141+
142+
```typescript
143+
const dataProvider = new StaticTreeDataProvider(items, (item, newName) => {
144+
// Return the patched item with new item name here
145+
return {
146+
...item,
147+
data: { ...item.data, name: newName },
148+
};
149+
});
150+
`
151+
```
152+
153+
## Custom Data Provider
154+
155+
In more complex scenarios, it's probably easiest to implement your own data provider.
8156
This provider must implement the [TreeDataProvider interface](/docs/api/interfaces/TreeDataProvider), i.e.
9157

10158
```typescript
@@ -27,3 +175,139 @@ tree structure should be handled, i.e. by renaming an item or moving items from
27175
another. You still need to enable this functionality in the environment by providing the respective
28176
flags. Look into the [TreeCapabilities interface](/docs/api/interfaces/TreeCapabilities) for more details
29177
on the necessary flags.
178+
179+
You can use this implementation as baseline:
180+
181+
```typescript
182+
183+
class CustomDataProviderImplementation implements TreeDataProvider {
184+
private data: Record<TreeItemIndex, TreeItem> = { ...shortTree.items };
185+
186+
private treeChangeListeners: ((changedItemIds: TreeItemIndex[]) => void)[] =
187+
[];
188+
189+
public async getTreeItem(itemId: TreeItemIndex) {
190+
return this.data[itemId];
191+
}
192+
193+
public async onChangeItemChildren(
194+
itemId: TreeItemIndex,
195+
newChildren: TreeItemIndex[]
196+
) {
197+
this.data[itemId].children = newChildren;
198+
this.treeChangeListeners.forEach(listener => listener([itemId]));
199+
}
200+
201+
public onDidChangeTreeData(
202+
listener: (changedItemIds: TreeItemIndex[]) => void
203+
): Disposable {
204+
this.treeChangeListeners.push(listener);
205+
return {
206+
dispose: () =>
207+
this.treeChangeListeners.splice(
208+
this.treeChangeListeners.indexOf(listener),
209+
1
210+
),
211+
};
212+
}
213+
214+
public async onRenameItem(item: TreeItem<any>, name: string): Promise<void> {
215+
this.data[item.index].data = name;
216+
}
217+
218+
// custom handler for directly manipulating the tree data
219+
public injectItem(name: string) {
220+
const rand = `${Math.random()}`;
221+
this.data[rand] = { data: name, index: rand } as TreeItem;
222+
this.data.root.children?.push(rand);
223+
this.treeChangeListeners.forEach(listener => listener(['root']));
224+
}
225+
}
226+
```
227+
228+
## Reacting to Drag Events
229+
230+
RCT will call `onChangeItemChildren` when a drag operation is finished. You can use this directly
231+
to update your data source. Note that, if you add or remove items, the affected item
232+
is the parent item, not the added or removed items.
233+
234+
In the exemplary implementation above, this emits an event on the `treeChangeListeners` listeners,
235+
where you could register a custom listener to react to changes.
236+
237+
## Reacting to Rename Events
238+
239+
RCT will call `onRenameItem` when a rename operation is finished. Implement your rename logic there.
240+
241+
## Custom Provider Live Demo
242+
243+
```jsx live
244+
function App() {
245+
const dataProvider = useMemo(
246+
() => {
247+
class CustomDataProviderImplementation {
248+
data = { ...shortTree.items };
249+
250+
treeChangeListeners = [];
251+
252+
async getTreeItem(itemId) {
253+
return this.data[itemId];
254+
}
255+
256+
async onChangeItemChildren(itemId, newChildren) {
257+
this.data[itemId].children = newChildren;
258+
this.treeChangeListeners.forEach(listener => listener([itemId]));
259+
}
260+
261+
onDidChangeTreeData(listener) {
262+
this.treeChangeListeners.push(listener);
263+
return {
264+
dispose: () =>
265+
this.treeChangeListeners.splice(
266+
this.treeChangeListeners.indexOf(listener),
267+
1
268+
),
269+
};
270+
}
271+
272+
async onRenameItem(item, name) {
273+
this.data[item.index].data = name;
274+
}
275+
276+
injectItem(name) {
277+
const rand = `${Math.random()}`;
278+
this.data[rand] = { data: name, index: rand };
279+
this.data.root.children.push(rand);
280+
this.treeChangeListeners.forEach(listener => listener(['root']));
281+
}
282+
}
283+
return new CustomDataProviderImplementation()
284+
},
285+
[]
286+
);
287+
288+
return (
289+
<UncontrolledTreeEnvironment
290+
canDragAndDrop
291+
canDropOnFolder
292+
canReorderItems
293+
dataProvider={dataProvider}
294+
getItemTitle={item => item.data}
295+
viewState={{
296+
'tree-2': {
297+
expandedItems: [],
298+
},
299+
}}
300+
>
301+
<button
302+
type="button"
303+
onClick={() =>
304+
dataProvider.injectItem(window.prompt('Item name') || 'New item')
305+
}
306+
>
307+
Inject item
308+
</button>
309+
<Tree treeId="tree-2" rootItem="root" treeLabel="Tree Example" />
310+
</UncontrolledTreeEnvironment>
311+
);
312+
}
313+
```

packages/docs/docs/guides/uncontrolled-environment.mdx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ can be loaded by React Complex Tree. Alternatively, you can just provide a
1414
reference to all available data.
1515

1616
You can read more about implementing a custom TreeDataProvider
17-
[implementing a custom TreeDataProvider here](/docs/guides/custom-data-provider).
17+
[implementing a custom TreeDataProvider here](/docs/guides/custom-data-provider), as well as
18+
more details on how to use the static tree data provider.
1819

1920
An example using a StaticTreeDataProvider looks like this:
2021

0 commit comments

Comments
 (0)