Skip to content

Commit 7afe2d4

Browse files
committed
Merge branch 'refactor' of https://github.com/worthlutz/react-checkbox-tree into worthlutz-refactor
# Conflicts: # examples/dist/index.js # src/js/TreeNode.js
2 parents 04664cb + 64e39c8 commit 7afe2d4

File tree

6 files changed

+106
-120
lines changed

6 files changed

+106
-120
lines changed

.gitignore

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

examples/dist/index.js

Lines changed: 2 additions & 2 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: 85 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class CheckboxTree extends React.Component {
5151
super(props);
5252

5353
this.id = `rct-${nanoid(7)}`;
54-
this.nodes = {};
54+
this.flatNodes = {};
5555

5656
this.flattenNodes(props.nodes);
5757
this.unserializeLists({
@@ -72,54 +72,42 @@ class CheckboxTree extends React.Component {
7272
}
7373

7474
onCheck(node) {
75+
// node is object from TreeNode
7576
const { noCascade, onCheck } = this.props;
7677

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

8182
onExpand(node) {
83+
// node is object from TreeNode
8284
const { onExpand } = this.props;
8385

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

88-
getFormattedNodes(nodes) {
89-
return nodes.map((node) => {
90-
const formatted = { ...node };
90+
getShallowCheckState(node, noCascade) {
91+
// node is from props.nodes
92+
const flatNode = this.flatNodes[node.value];
9193

92-
formatted.checked = this.nodes[node.value].checked;
93-
formatted.expanded = this.nodes[node.value].expanded;
94-
formatted.showCheckbox = node.showCheckbox !== undefined ? node.showCheckbox : true;
95-
96-
if (Array.isArray(node.children) && node.children.length > 0) {
97-
formatted.children = this.getFormattedNodes(formatted.children);
98-
} else {
99-
formatted.children = null;
100-
}
101-
102-
return formatted;
103-
});
104-
}
105-
106-
getCheckState(node, noCascade) {
107-
if (node.children === null || noCascade) {
108-
return node.checked ? 1 : 0;
94+
if (flatNode.isLeaf || noCascade) {
95+
return flatNode.checked ? 1 : 0;
10996
}
11097

111-
if (this.isEveryChildChecked(node)) {
98+
if (node.children.every(child => (this.flatNodes[child.value].checkState === 1))) {
11299
return 1;
113100
}
114101

115-
if (this.isSomeChildChecked(node)) {
102+
if (node.children.some(child => (this.flatNodes[child.value].checkState > 0))) {
116103
return 2;
117104
}
118105

119106
return 0;
120107
}
121108

122109
getDisabledState(node, parent, disabledProp, noCascade) {
110+
// node is from props.nodes
123111
if (disabledProp) {
124112
return true;
125113
}
@@ -132,45 +120,56 @@ class CheckboxTree extends React.Component {
132120
}
133121

134122
toggleChecked(node, isChecked, noCascade) {
135-
if (node.children === null || noCascade) {
123+
// node is object from TreeNode
124+
const flatNode = this.flatNodes[node.value];
125+
126+
if (flatNode.isLeaf || noCascade) {
136127
// Set the check status of a leaf node or an uncoupled parent
137128
this.toggleNode('checked', node, isChecked);
138129
} else {
130+
const { children } = flatNode.self;
139131
// Percolate check status down to all children
140-
node.children.forEach((child) => {
141-
this.toggleChecked(child, isChecked);
132+
children.forEach((child) => {
133+
this.toggleChecked(child, isChecked, noCascade);
142134
});
143135
}
144136
}
145137

146138
toggleNode(key, node, toggleValue) {
147-
this.nodes[node.value][key] = toggleValue;
139+
this.flatNodes[node.value][key] = toggleValue;
148140
}
149141

150-
flattenNodes(nodes) {
142+
flattenNodes(nodes, parentNode = {}) {
143+
// nodes are from props.nodes
151144
if (!Array.isArray(nodes) || nodes.length === 0) {
152145
return;
153146
}
154147

155148
nodes.forEach((node) => {
156-
this.nodes[node.value] = {};
157-
this.flattenNodes(node.children);
149+
// set defaults, calculated values and tree references
150+
this.flatNodes[node.value] = {
151+
parent: parentNode,
152+
self: node,
153+
isLeaf: !Array.isArray(node.children) || node.children.length === 0,
154+
showCheckbox: node.showCheckbox !== undefined ? node.showCheckbox : true,
155+
};
156+
this.flattenNodes(node.children, node);
158157
});
159158
}
160159

161160
unserializeLists(lists) {
162161
// Reset values to false
163-
Object.keys(this.nodes).forEach((value) => {
162+
Object.keys(this.flatNodes).forEach((value) => {
164163
Object.keys(lists).forEach((listKey) => {
165-
this.nodes[value][listKey] = false;
164+
this.flatNodes[value][listKey] = false;
166165
});
167166
});
168167

169168
// Unserialize values and set their nodes to true
170169
Object.keys(lists).forEach((listKey) => {
171170
lists[listKey].forEach((value) => {
172-
if (this.nodes[value] !== undefined) {
173-
this.nodes[value][listKey] = true;
171+
if (this.flatNodes[value] !== undefined) {
172+
this.flatNodes[value][listKey] = true;
174173
}
175174
});
176175
});
@@ -179,36 +178,17 @@ class CheckboxTree extends React.Component {
179178
serializeList(key) {
180179
const list = [];
181180

182-
Object.keys(this.nodes).forEach((value) => {
183-
if (this.nodes[value][key]) {
181+
Object.keys(this.flatNodes).forEach((value) => {
182+
if (this.flatNodes[value][key]) {
184183
list.push(value);
185184
}
186185
});
187186

188187
return list;
189188
}
190189

191-
isEveryChildChecked(node) {
192-
return node.children.every((child) => {
193-
if (child.children !== null) {
194-
return this.isEveryChildChecked(child);
195-
}
196-
197-
return child.checked;
198-
});
199-
}
200-
201-
isSomeChildChecked(node) {
202-
return node.children.some((child) => {
203-
if (child.children !== null) {
204-
return this.isSomeChildChecked(child);
205-
}
206-
207-
return child.checked;
208-
});
209-
}
210-
211190
renderTreeNodes(nodes, parent = {}) {
191+
// nodes are props.nodes
212192
const {
213193
disabled,
214194
expandDisabled,
@@ -219,39 +199,57 @@ class CheckboxTree extends React.Component {
219199
showNodeIcon,
220200
onClick,
221201
} = this.props;
202+
222203
const treeNodes = nodes.map((node) => {
223204
const key = `${node.value}`;
224-
const checked = this.getCheckState(node, noCascade);
225-
const isLeaf = node.children === null;
226-
const children = this.renderChildNodes(node);
205+
206+
const flatNode = this.flatNodes[node.value];
207+
208+
let children = null;
209+
if (!flatNode.isLeaf) {
210+
children = this.renderTreeNodes(node.children, node);
211+
}
212+
213+
// set checkState here
214+
// this can be "shallow" because checkState is updated for all
215+
// nested children in the recursive call to renderTreeNodes above
216+
flatNode.checkState = this.getShallowCheckState(node, noCascade);
217+
227218
const nodeDisabled = this.getDisabledState(node, parent, disabled, noCascade);
219+
228220
// Show checkbox only if this is a leaf node or showCheckbox is true
229-
const showCheckbox = onlyLeafCheckboxes ? isLeaf : node.showCheckbox;
230-
231-
return (
232-
<TreeNode
233-
key={key}
234-
checked={checked}
235-
className={node.className}
236-
disabled={nodeDisabled}
237-
expandDisabled={expandDisabled}
238-
expandOnClick={expandOnClick}
239-
expanded={node.expanded}
240-
icon={node.icon}
241-
label={node.label}
242-
optimisticToggle={optimisticToggle}
243-
rawChildren={node.children}
244-
showCheckbox={showCheckbox}
245-
showNodeIcon={showNodeIcon}
246-
treeId={this.id}
247-
value={node.value}
248-
onCheck={this.onCheck}
249-
onClick={onClick}
250-
onExpand={this.onExpand}
251-
>
252-
{children}
253-
</TreeNode>
254-
);
221+
const showCheckbox = onlyLeafCheckboxes ? flatNode.isLeaf : flatNode.showCheckbox;
222+
223+
// root of tree has no parent value and is expanded by default
224+
const parentExpanded = parent.value ? this.flatNodes[parent.value].expanded : true;
225+
if (parentExpanded) {
226+
return (
227+
<TreeNode
228+
key={key}
229+
checked={flatNode.checkState}
230+
className={node.className}
231+
disabled={nodeDisabled}
232+
expandDisabled={expandDisabled}
233+
expandOnClick={expandOnClick}
234+
expanded={flatNode.expanded}
235+
icon={node.icon}
236+
label={node.label}
237+
optimisticToggle={optimisticToggle}
238+
isLeaf={flatNode.isLeaf}
239+
showCheckbox={showCheckbox}
240+
showNodeIcon={showNodeIcon}
241+
treeId={this.id}
242+
value={node.value}
243+
onCheck={this.onCheck}
244+
onClick={onClick}
245+
onExpand={this.onExpand}
246+
>
247+
{children}
248+
</TreeNode>
249+
);
250+
}
251+
252+
return null;
255253
});
256254

257255
return (
@@ -261,14 +259,6 @@ class CheckboxTree extends React.Component {
261259
);
262260
}
263261

264-
renderChildNodes(node) {
265-
if (node.children !== null && node.expanded) {
266-
return this.renderTreeNodes(node.children, node);
267-
}
268-
269-
return null;
270-
}
271-
272262
renderHiddenInput() {
273263
if (this.props.name === undefined) {
274264
return null;
@@ -296,8 +286,8 @@ class CheckboxTree extends React.Component {
296286
}
297287

298288
render() {
299-
const nodes = this.getFormattedNodes(this.props.nodes);
300-
const treeNodes = this.renderTreeNodes(nodes);
289+
const treeNodes = this.renderTreeNodes(this.props.nodes);
290+
301291
const className = classNames({
302292
'react-checkbox-tree': true,
303293
'rct-disabled': this.props.disabled,

src/js/TreeNode.js

Lines changed: 6 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,14 @@ 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,
@@ -26,7 +26,6 @@ class TreeNode extends React.Component {
2626
className: PropTypes.string,
2727
expandOnClick: PropTypes.bool,
2828
icon: PropTypes.node,
29-
rawChildren: PropTypes.arrayOf(nodeShape),
3029
showCheckbox: PropTypes.bool,
3130
onClick: PropTypes.func,
3231
};
@@ -36,7 +35,6 @@ class TreeNode extends React.Component {
3635
className: null,
3736
expandOnClick: false,
3837
icon: null,
39-
rawChildren: null,
4038
showCheckbox: true,
4139
onClick: () => {},
4240
};
@@ -65,7 +63,6 @@ class TreeNode extends React.Component {
6563
this.props.onCheck({
6664
value: this.props.value,
6765
checked: isChecked,
68-
children: this.props.rawChildren,
6966
});
7067
}
7168

@@ -82,14 +79,13 @@ class TreeNode extends React.Component {
8279
}
8380

8481
// Auto expand if enabled
85-
if (this.hasChildren() && this.props.expandOnClick) {
82+
if (!this.props.isLeaf && this.props.expandOnClick) {
8683
this.onExpand();
8784
}
8885

8986
this.props.onClick({
9087
value: this.props.value,
9188
checked: isChecked,
92-
children: this.props.rawChildren,
9389
});
9490
}
9591

@@ -100,14 +96,10 @@ class TreeNode extends React.Component {
10096
});
10197
}
10298

103-
hasChildren() {
104-
return this.props.rawChildren !== null;
105-
}
106-
10799
renderCollapseButton() {
108100
const { expandDisabled } = this.props;
109101

110-
if (!this.hasChildren()) {
102+
if (this.props.isLeaf) {
111103
return (
112104
<span className="rct-collapse">
113105
<span className="rct-icon" />
@@ -154,7 +146,7 @@ class TreeNode extends React.Component {
154146
return this.props.icon;
155147
}
156148

157-
if (!this.hasChildren()) {
149+
if (this.props.isLeaf) {
158150
return <span className="rct-icon rct-icon-leaf" />;
159151
}
160152

@@ -267,8 +259,8 @@ class TreeNode extends React.Component {
267259
const { className, disabled } = this.props;
268260
const nodeClass = classNames({
269261
'rct-node': true,
270-
'rct-node-parent': this.hasChildren(),
271-
'rct-node-leaf': !this.hasChildren(),
262+
'rct-node-parent': !this.props.isLeaf,
263+
'rct-node-leaf': this.props.isLeaf,
272264
'rct-disabled': disabled,
273265
}, className);
274266

0 commit comments

Comments
 (0)