Skip to content

Commit d47226e

Browse files
committed
Change API for updating tree data, and add comments for the props
1 parent 6227022 commit d47226e

File tree

3 files changed

+102
-73
lines changed

3 files changed

+102
-73
lines changed

src/examples/basicExample/app.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ class App extends Component {
266266

267267
<SortableTree
268268
treeData={treeData}
269-
updateTreeData={this.updateTreeData}
269+
onChange={this.updateTreeData}
270270
maxDepth={5}
271271
searchQuery={searchString}
272272
searchFocusOffset={searchFocusIndex}

src/react-sortable-tree.js

Lines changed: 101 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import React, { Component, PropTypes } from 'react';
88
import { AutoSizer, List } from 'react-virtualized';
99
import 'react-virtualized/styles.css';
1010
import TreeNode from './tree-node';
11+
import NodeRendererDefault from './node-renderer-default';
1112
import {
1213
walk,
1314
getFlatDataFromTree,
@@ -22,8 +23,6 @@ import {
2223
} from './utils/generic-utils';
2324
import {
2425
defaultGetNodeKey,
25-
defaultToggleChildrenVisibility,
26-
defaultMoveNode,
2726
defaultSearchMethod,
2827
} from './utils/default-handlers';
2928
import {
@@ -36,28 +35,7 @@ class ReactSortableTree extends Component {
3635
constructor(props) {
3736
super(props);
3837

39-
if (process.env.NODE_ENV === 'development') {
40-
/* eslint-disable no-console */
41-
const usesDefaultHandlers = (
42-
!props.toggleChildrenVisibility
43-
);
44-
45-
if (!props.updateTreeData && usesDefaultHandlers) {
46-
console.warn('Need to add specify updateTreeData prop if default event handlers are used');
47-
}
48-
/* eslint-enable */
49-
}
50-
51-
// Fall back to default event listeners if necessary and bind them to the tree
52-
this.getNodeKey = (props.getNodeKey || defaultGetNodeKey).bind(this);
53-
this.moveNode = (props.moveNode || defaultMoveNode).bind(this);
54-
this.toggleChildrenVisibility = (
55-
props.toggleChildrenVisibility || defaultToggleChildrenVisibility
56-
).bind(this);
57-
this.nodeContentRenderer = dndWrapSource(
58-
props.nodeContentRenderer ||
59-
require('./node-renderer-default').default // eslint-disable-line global-require
60-
);
38+
this.nodeContentRenderer = dndWrapSource(props.nodeContentRenderer);
6139

6240
this.state = {
6341
draggingTreeData: null,
@@ -69,6 +47,8 @@ class ReactSortableTree extends Component {
6947
searchFocusTreeIndex: null,
7048
};
7149

50+
this.toggleChildrenVisibility = this.toggleChildrenVisibility.bind(this);
51+
this.moveNode = this.moveNode.bind(this);
7252
this.startDrag = this.startDrag.bind(this);
7353
this.dragHover = this.dragHover.bind(this);
7454
this.endDrag = this.endDrag.bind(this);
@@ -80,6 +60,41 @@ class ReactSortableTree extends Component {
8060
this.ignoreOneTreeUpdate = false;
8161
}
8262

63+
toggleChildrenVisibility({ node: targetNode, path, treeIndex: _treeIndex }) {
64+
const treeData = changeNodeAtPath({
65+
treeData: this.props.treeData,
66+
path,
67+
newNode: ({ node }) => ({ ...node, expanded: !node.expanded }),
68+
getNodeKey: this.props.getNodeKey,
69+
});
70+
71+
this.props.onChange(treeData);
72+
73+
if (this.props.onVisibilityToggle) {
74+
this.props.onVisibilityToggle({
75+
treeData,
76+
node: targetNode,
77+
expanded: !targetNode.expanded,
78+
});
79+
}
80+
}
81+
82+
moveNode({ node, depth, minimumTreeIndex }) {
83+
const treeData = insertNode({
84+
treeData: this.state.draggingTreeData,
85+
newNode: node,
86+
depth,
87+
minimumTreeIndex,
88+
expandParent: true,
89+
}).treeData;
90+
91+
this.props.onChange(treeData);
92+
93+
if (this.props.onMoveNode) {
94+
this.props.onMoveNode({ treeData, node });
95+
}
96+
}
97+
8398
componentWillReceiveProps(nextProps) {
8499
this.setState({ searchFocusTreeIndex: null });
85100
if (this.props.treeData !== nextProps.treeData) {
@@ -110,15 +125,15 @@ class ReactSortableTree extends Component {
110125
getRows(treeData) {
111126
return getFlatDataFromTree({
112127
ignoreCollapsed: true,
113-
getNodeKey: this.getNodeKey,
128+
getNodeKey: this.props.getNodeKey,
114129
treeData,
115130
});
116131
}
117132

118133
search(props = this.props, seekIndex = true, expand = true, singleSearch = false) {
119134
const {
120135
treeData,
121-
updateTreeData,
136+
onChange,
122137
searchFinishCallback,
123138
searchQuery,
124139
searchMethod,
@@ -144,7 +159,7 @@ class ReactSortableTree extends Component {
144159
treeData: expandedTreeData,
145160
matches: searchMatches,
146161
} = find({
147-
getNodeKey: this.getNodeKey,
162+
getNodeKey: this.props.getNodeKey,
148163
treeData,
149164
searchQuery,
150165
searchMethod: searchMethod || defaultSearchMethod,
@@ -156,7 +171,7 @@ class ReactSortableTree extends Component {
156171
// Update the tree with data leaving all paths leading to matching nodes open
157172
if (expand) {
158173
this.ignoreOneTreeUpdate = true; // Prevents infinite loop
159-
updateTreeData(expandedTreeData);
174+
onChange(expandedTreeData);
160175
}
161176

162177
if (searchFinishCallback) {
@@ -181,7 +196,7 @@ class ReactSortableTree extends Component {
181196
const draggingTreeData = removeNodeAtPath({
182197
treeData: this.props.treeData,
183198
path,
184-
getNodeKey: this.getNodeKey,
199+
getNodeKey: this.props.getNodeKey,
185200
});
186201

187202
this.setState({
@@ -213,7 +228,7 @@ class ReactSortableTree extends Component {
213228
treeData: this.state.draggingTreeData,
214229
path: expandedParentPath.slice(0, -1),
215230
newNode: ({ node }) => ({ ...node, expanded: true }),
216-
getNodeKey: this.getNodeKey,
231+
getNodeKey: this.props.getNodeKey,
217232
}),
218233
});
219234
}
@@ -238,7 +253,7 @@ class ReactSortableTree extends Component {
238253
loadLazyChildren(props = this.props) {
239254
walk({
240255
treeData: props.treeData,
241-
getNodeKey: this.getNodeKey,
256+
getNodeKey: this.props.getNodeKey,
242257
callback: ({ node, path, lowerSiblingCounts, treeIndex }) => {
243258
// If the node has children defined by a function, and is either expanded
244259
// or set to load even before expansion, run the function.
@@ -254,15 +269,15 @@ class ReactSortableTree extends Component {
254269
treeIndex,
255270

256271
// Provide a helper to append the new data when it is received
257-
done: childrenArray => this.props.updateTreeData(changeNodeAtPath({
272+
done: childrenArray => this.props.onChange(changeNodeAtPath({
258273
treeData: this.props.treeData,
259274
path,
260275
newNode: ({ node: oldNode }) => (
261276
// Only replace the old node if it's the one we set off to find children
262277
// for in the first place
263278
oldNode === node ? { ...oldNode, children: childrenArray } : oldNode
264279
),
265-
getNodeKey: this.getNodeKey,
280+
getNodeKey: this.props.getNodeKey,
266281
})),
267282
});
268283
}
@@ -373,46 +388,84 @@ class ReactSortableTree extends Component {
373388
}
374389

375390
ReactSortableTree.propTypes = {
391+
// Tree data in the following format:
392+
// [{title: 'main', subtitle: 'sub'}, { title: 'value2', expanded: true, children: [{ title: 'value3') }] }]
393+
// `title` is the primary label for the node
394+
// `subtitle` is a secondary label for the node
395+
// `expanded` shows children of the node if true, or hides them if false. Defaults to false.
396+
// `children` is an array of child nodes belonging to the node.
376397
treeData: PropTypes.arrayOf(PropTypes.object).isRequired,
377398

378-
// Callback for move operation.
379-
// Called as moveNode({ node, path, parentPath, minimumTreeIndex })
380-
moveNode: PropTypes.func,
381-
382399
// Style applied to the container wrapping the tree (style defaults to {height: '100%'})
383400
style: PropTypes.object,
401+
402+
// Class name for the container wrapping the tree
384403
className: PropTypes.string,
385404

386405
// Style applied to the inner, scrollable container (for padding, etc.)
387406
innerStyle: PropTypes.object,
388407

389-
// Height of each node row, used for react-virtualized
408+
// Used by react-virtualized
409+
// Either a fixed row height (number) or a function that returns the
410+
// height of a row given its index: `({ index: number }): number`
390411
rowHeight: PropTypes.oneOfType([ PropTypes.number, PropTypes.func ]),
391412

413+
// The width of the blocks containing the lines representing the structure of the tree.
392414
scaffoldBlockPxWidth: PropTypes.number,
393415

416+
// Maximum depth nodes can be inserted at. Defaults to infinite.
394417
maxDepth: PropTypes.number,
395418

396-
// Search stuff
397-
searchQuery: PropTypes.any,
398-
searchFocusOffset: PropTypes.number,
399-
searchMethod: PropTypes.func, // eslint-disable-line react/no-unused-prop-types
419+
// The method used to search nodes.
420+
// Defaults to a function that uses the `searchQuery` string to search for nodes with
421+
// matching `title` or `subtitle` values.
422+
// NOTE: Changing `searchMethod` will not update the search, but changing the `searchQuery` will.
423+
searchMethod: PropTypes.func, // eslint-disable-line react/no-unused-prop-types
424+
425+
// Used by the `searchMethod` to highlight and scroll to matched nodes.
426+
// Should be a string for the default `searchMethod`, but can be anything when using a custom search.
427+
searchQuery: PropTypes.any,
428+
429+
// Outline the <`searchFocusOffset`>th node and scroll to it.
430+
searchFocusOffset: PropTypes.number,
431+
432+
// Get the nodes that match the search criteria. Used for counting total matches, etc.
400433
searchFinishCallback: PropTypes.func, // eslint-disable-line react/no-unused-prop-types
401434

435+
// Generate an object with additional props to be passed to the node renderer.
436+
// Use this for adding buttons via the `buttons` key,
437+
// or additional `style` / `className` settings.
438+
generateNodeProps: PropTypes.func,
439+
440+
// Override the default component for rendering nodes (but keep the scaffolding generator)
441+
// This is an advanced option for complete customization of the appearance.
442+
// It is best to copy the component in `node-renderer-default.js` to use as a base, and customize as needed.
402443
nodeContentRenderer: PropTypes.any,
403-
generateNodeProps: PropTypes.func,
404444

405-
getNodeKey: PropTypes.func,
406-
updateTreeData: PropTypes.func,
407-
toggleChildrenVisibility: PropTypes.func,
445+
// Determine the unique key used to identify each node and
446+
// generate the `path` array passed in callbacks.
447+
// By default, returns the index in the tree (omitting hidden nodes).
448+
getNodeKey: PropTypes.func,
449+
450+
// Called whenever tree data changed.
451+
// Just like with React input elements, you have to update your
452+
// own component's data to see the changes reflected.
453+
onChange: PropTypes.func.isRequired,
454+
455+
// Called after node move operation.
456+
onMoveNode: PropTypes.func,
457+
458+
// Called after children nodes collapsed or expanded.
459+
onVisibilityToggle: PropTypes.func,
408460
};
409461

410462
ReactSortableTree.defaultProps = {
463+
getNodeKey: defaultGetNodeKey,
464+
nodeContentRenderer: NodeRendererDefault,
411465
rowHeight: 62,
466+
scaffoldBlockPxWidth: 44,
412467
style: {},
413468
innerStyle: {},
414-
scaffoldBlockPxWidth: 44,
415-
loadCollapsedLazyChildren: false,
416469
searchQuery: null,
417470
};
418471

src/utils/default-handlers.js

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,7 @@
1-
import {
2-
changeNodeAtPath,
3-
insertNode,
4-
} from './tree-data-utils';
5-
61
export function defaultGetNodeKey({ node: _node, treeIndex }) {
72
return treeIndex;
83
}
94

10-
export function defaultToggleChildrenVisibility({ node: _node, path, treeIndex: _treeIndex }) {
11-
this.props.updateTreeData(changeNodeAtPath({
12-
treeData: this.props.treeData,
13-
path,
14-
newNode: ({ node }) => ({ ...node, expanded: !node.expanded }),
15-
getNodeKey: this.getNodeKey,
16-
}));
17-
}
18-
19-
export function defaultMoveNode({ node: newNode, depth, minimumTreeIndex }) {
20-
this.props.updateTreeData(insertNode({
21-
treeData: this.state.draggingTreeData,
22-
newNode,
23-
depth,
24-
minimumTreeIndex,
25-
expandParent: true,
26-
}).treeData);
27-
}
28-
295
// Cheap hack to get the text of a react object
306
function getReactElementText(parent) {
317
if (typeof parent === 'string') {

0 commit comments

Comments
 (0)