Skip to content

Commit 0b219ff

Browse files
author
Worth Lutz
committed
refactor flattened nodes to contain more info
1 parent 708ac8c commit 0b219ff

File tree

6 files changed

+132
-78
lines changed

6 files changed

+132
-78
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
/lib
22
/node_modules
33
yarn.lock
4+
package-lock.json

examples/dist/index.js

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/js/CheckboxTree.js

Lines changed: 103 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class CheckboxTree extends React.Component {
5050
super(props);
5151

5252
this.id = `rct-${nanoid(7)}`;
53-
this.nodes = {};
53+
this.flatNodes = {};
5454

5555
this.flattenNodes(props.nodes);
5656
this.unserializeLists({
@@ -71,20 +71,23 @@ class CheckboxTree extends React.Component {
7171
}
7272

7373
onCheck(node) {
74+
// node is object from TreeNode
7475
const { noCascade, onCheck } = this.props;
7576

7677
this.toggleChecked(node, node.checked, noCascade);
7778
onCheck(this.serializeList('checked'), node);
7879
}
7980

8081
onExpand(node) {
82+
// node is object from TreeNode
8183
const { onExpand } = this.props;
8284

8385
this.toggleNode('expanded', node, node.expanded);
8486
onExpand(this.serializeList('expanded'), node);
8587
}
8688

8789
getFormattedNodes(nodes) {
90+
// not used
8891
return nodes.map((node) => {
8992
const formatted = { ...node };
9093

@@ -103,6 +106,7 @@ class CheckboxTree extends React.Component {
103106
}
104107

105108
getCheckState(node, noCascade) {
109+
// not used
106110
if (node.children === null || noCascade) {
107111
return node.checked ? 1 : 0;
108112
}
@@ -118,7 +122,27 @@ class CheckboxTree extends React.Component {
118122
return 0;
119123
}
120124

125+
getShallowCheckState(node, noCascade) {
126+
// node is from props.nodes
127+
const flatNode = this.flatNodes[node.value];
128+
129+
if (flatNode.isLeaf || noCascade) {
130+
return flatNode.checked ? 1 : 0;
131+
}
132+
133+
if (node.children.every(child => (this.flatNodes[child.value].checkState === 1))) {
134+
return 1;
135+
}
136+
137+
if (node.children.some(child => (this.flatNodes[child.value].checkState > 0))) {
138+
return 2;
139+
}
140+
141+
return 0;
142+
}
143+
121144
getDisabledState(node, parent, disabledProp, noCascade) {
145+
// node is from props.nodes
122146
if (disabledProp) {
123147
return true;
124148
}
@@ -131,45 +155,56 @@ class CheckboxTree extends React.Component {
131155
}
132156

133157
toggleChecked(node, isChecked, noCascade) {
134-
if (node.children === null || noCascade) {
158+
// node is object from TreeNode
159+
const flatNode = this.flatNodes[node.value];
160+
161+
if (flatNode.isLeaf || noCascade) {
135162
// Set the check status of a leaf node or an uncoupled parent
136163
this.toggleNode('checked', node, isChecked);
137164
} else {
165+
const { children } = flatNode.self;
138166
// Percolate check status down to all children
139-
node.children.forEach((child) => {
140-
this.toggleChecked(child, isChecked);
167+
children.forEach((child) => {
168+
this.toggleChecked(child, isChecked, noCascade);
141169
});
142170
}
143171
}
144172

145173
toggleNode(key, node, toggleValue) {
146-
this.nodes[node.value][key] = toggleValue;
174+
this.flatNodes[node.value][key] = toggleValue;
147175
}
148176

149-
flattenNodes(nodes) {
177+
flattenNodes(nodes, parentNode = {}) {
178+
// nodes are from props.nodes
150179
if (!Array.isArray(nodes) || nodes.length === 0) {
151180
return;
152181
}
153182

154183
nodes.forEach((node) => {
155-
this.nodes[node.value] = {};
156-
this.flattenNodes(node.children);
184+
// set defaults, calculated values and tree references
185+
this.flatNodes[node.value] = {
186+
parent: parentNode,
187+
self: node,
188+
isLeaf: !Array.isArray(node.children) || node.children.length === 0,
189+
showCheckbox: node.showCheckbox !== undefined ? node.showCheckbox : true,
190+
};
191+
this.flattenNodes(node.children, node);
157192
});
158193
}
159194

160195
unserializeLists(lists) {
161196
// Reset values to false
162-
Object.keys(this.nodes).forEach((value) => {
197+
Object.keys(this.flatNodes).forEach((value) => {
163198
Object.keys(lists).forEach((listKey) => {
164-
this.nodes[value][listKey] = false;
199+
this.flatNodes[value][listKey] = false;
165200
});
166201
});
167202

168203
// Unserialize values and set their nodes to true
169204
Object.keys(lists).forEach((listKey) => {
170205
lists[listKey].forEach((value) => {
171-
if (this.nodes[value] !== undefined) {
172-
this.nodes[value][listKey] = true;
206+
if (this.flatNodes[value] !== undefined) {
207+
this.flatNodes[value][listKey] = true;
173208
}
174209
});
175210
});
@@ -178,8 +213,8 @@ class CheckboxTree extends React.Component {
178213
serializeList(key) {
179214
const list = [];
180215

181-
Object.keys(this.nodes).forEach((value) => {
182-
if (this.nodes[value][key]) {
216+
Object.keys(this.flatNodes).forEach((value) => {
217+
if (this.flatNodes[value][key]) {
183218
list.push(value);
184219
}
185220
});
@@ -188,6 +223,7 @@ class CheckboxTree extends React.Component {
188223
}
189224

190225
isEveryChildChecked(node) {
226+
// not used
191227
return node.children.every((child) => {
192228
if (child.children !== null) {
193229
return this.isEveryChildChecked(child);
@@ -198,6 +234,7 @@ class CheckboxTree extends React.Component {
198234
}
199235

200236
isSomeChildChecked(node) {
237+
// not used
201238
return node.children.some((child) => {
202239
if (child.children !== null) {
203240
return this.isSomeChildChecked(child);
@@ -208,6 +245,7 @@ class CheckboxTree extends React.Component {
208245
}
209246

210247
renderTreeNodes(nodes, parent = {}) {
248+
// nodes are props.nodes
211249
const {
212250
disabled,
213251
expandDisabled,
@@ -218,39 +256,57 @@ class CheckboxTree extends React.Component {
218256
showNodeIcon,
219257
onClick,
220258
} = this.props;
259+
221260
const treeNodes = nodes.map((node) => {
222261
const key = `${node.value}`;
223-
const checked = this.getCheckState(node, noCascade);
224-
const isLeaf = node.children === null;
225-
const children = this.renderChildNodes(node);
262+
263+
const flatNode = this.flatNodes[node.value];
264+
265+
let children = null;
266+
if (!flatNode.isLeaf) {
267+
children = this.renderTreeNodes(node.children, node);
268+
}
269+
270+
// set checkState here
271+
// this can be "shallow" because checkState is updated for all
272+
// nested children in the recursive call to renderTreeNodes above
273+
flatNode.checkState = this.getShallowCheckState(node, noCascade);
274+
226275
const nodeDisabled = this.getDisabledState(node, parent, disabled, noCascade);
276+
227277
// Show checkbox only if this is a leaf node or showCheckbox is true
228-
const showCheckbox = onlyLeafCheckboxes ? isLeaf : node.showCheckbox;
229-
230-
return (
231-
<TreeNode
232-
key={key}
233-
checked={checked}
234-
className={node.className}
235-
disabled={nodeDisabled}
236-
expandDisabled={expandDisabled}
237-
expandOnClick={expandOnClick}
238-
expanded={node.expanded}
239-
icon={node.icon}
240-
label={node.label}
241-
optimisticToggle={optimisticToggle}
242-
rawChildren={node.children}
243-
showCheckbox={showCheckbox}
244-
showNodeIcon={showNodeIcon}
245-
treeId={this.id}
246-
value={node.value}
247-
onCheck={this.onCheck}
248-
onClick={onClick}
249-
onExpand={this.onExpand}
250-
>
251-
{children}
252-
</TreeNode>
253-
);
278+
const showCheckbox = onlyLeafCheckboxes ? flatNode.isLeaf : flatNode.showCheckbox;
279+
280+
// root of tree has no parent value and is expanded by default
281+
const parentExpanded = parent.value ? this.flatNodes[parent.value].expanded : true;
282+
if (parentExpanded) {
283+
return (
284+
<TreeNode
285+
key={key}
286+
checked={flatNode.checkState}
287+
className={node.className}
288+
disabled={nodeDisabled}
289+
expandDisabled={expandDisabled}
290+
expandOnClick={expandOnClick}
291+
expanded={flatNode.expanded}
292+
icon={node.icon}
293+
label={node.label}
294+
optimisticToggle={optimisticToggle}
295+
isLeaf={flatNode.isLeaf}
296+
showCheckbox={showCheckbox}
297+
showNodeIcon={showNodeIcon}
298+
treeId={this.id}
299+
value={node.value}
300+
onCheck={this.onCheck}
301+
onClick={onClick}
302+
onExpand={this.onExpand}
303+
>
304+
{children}
305+
</TreeNode>
306+
);
307+
}
308+
309+
return null;
254310
});
255311

256312
return (
@@ -261,6 +317,8 @@ class CheckboxTree extends React.Component {
261317
}
262318

263319
renderChildNodes(node) {
320+
// not used
321+
// node is from props.nodes
264322
if (node.children !== null && node.expanded) {
265323
return this.renderTreeNodes(node.children, node);
266324
}
@@ -295,8 +353,8 @@ class CheckboxTree extends React.Component {
295353
}
296354

297355
render() {
298-
const nodes = this.getFormattedNodes(this.props.nodes);
299-
const treeNodes = this.renderTreeNodes(nodes);
356+
const treeNodes = this.renderTreeNodes(this.props.nodes);
357+
300358
const className = classNames({
301359
'react-checkbox-tree': true,
302360
'rct-disabled': this.props.disabled,

src/js/TreeNode.js

Lines changed: 7 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,26 @@ import PropTypes from 'prop-types';
33
import React from 'react';
44

55
import NativeCheckbox from './NativeCheckbox';
6-
import nodeShape from './nodeShape';
76

87
class TreeNode extends React.Component {
98
static propTypes = {
109
checked: PropTypes.number.isRequired,
1110
disabled: PropTypes.bool.isRequired,
1211
expandDisabled: PropTypes.bool.isRequired,
1312
expanded: PropTypes.bool.isRequired,
13+
isLeaf: PropTypes.bool.isRequired,
1414
label: PropTypes.node.isRequired,
1515
optimisticToggle: PropTypes.bool.isRequired,
1616
showNodeIcon: PropTypes.bool.isRequired,
1717
treeId: PropTypes.string.isRequired,
18-
value: PropTypes.string.isRequired,
18+
value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]).isRequired,
1919
onCheck: PropTypes.func.isRequired,
2020
onExpand: PropTypes.func.isRequired,
2121

2222
children: PropTypes.node,
2323
className: PropTypes.string,
2424
expandOnClick: PropTypes.bool,
2525
icon: PropTypes.node,
26-
rawChildren: PropTypes.arrayOf(nodeShape),
2726
showCheckbox: PropTypes.bool,
2827
onClick: PropTypes.func,
2928
};
@@ -33,7 +32,6 @@ class TreeNode extends React.Component {
3332
className: null,
3433
expandOnClick: false,
3534
icon: null,
36-
rawChildren: null,
3735
showCheckbox: true,
3836
onClick: () => {},
3937
};
@@ -62,7 +60,6 @@ class TreeNode extends React.Component {
6260
this.props.onCheck({
6361
value: this.props.value,
6462
checked: isChecked,
65-
children: this.props.rawChildren,
6663
});
6764
}
6865

@@ -79,14 +76,13 @@ class TreeNode extends React.Component {
7976
}
8077

8178
// Auto expand if enabled
82-
if (this.hasChildren() && this.props.expandOnClick) {
79+
if (!this.props.isLeaf && this.props.expandOnClick) {
8380
this.onExpand();
8481
}
8582

8683
this.props.onClick({
8784
value: this.props.value,
8885
checked: isChecked,
89-
children: this.props.rawChildren,
9086
});
9187
}
9288

@@ -97,14 +93,10 @@ class TreeNode extends React.Component {
9793
});
9894
}
9995

100-
hasChildren() {
101-
return this.props.rawChildren !== null;
102-
}
103-
10496
renderCollapseButton() {
10597
const { expandDisabled } = this.props;
10698

107-
if (!this.hasChildren()) {
99+
if (this.props.isLeaf) {
108100
return (
109101
<span className="rct-collapse">
110102
<span className="rct-icon" />
@@ -151,7 +143,7 @@ class TreeNode extends React.Component {
151143
return this.props.icon;
152144
}
153145

154-
if (!this.hasChildren()) {
146+
if (this.props.isLeaf) {
155147
return <span className="rct-icon rct-icon-leaf" />;
156148
}
157149

@@ -264,8 +256,8 @@ class TreeNode extends React.Component {
264256
const { className, disabled } = this.props;
265257
const nodeClass = classNames({
266258
'rct-node': true,
267-
'rct-node-parent': this.hasChildren(),
268-
'rct-node-leaf': !this.hasChildren(),
259+
'rct-node-parent': !this.props.isLeaf,
260+
'rct-node-leaf': this.props.isLeaf,
269261
'rct-disabled': disabled,
270262
}, className);
271263

0 commit comments

Comments
 (0)