Skip to content

Commit 621988a

Browse files
committed
feat: Support hierarchical mode ✨ (#203)
* feat: Support hierarchical mode * test: Fix tests as per new tree manager constructor
1 parent 2a758ee commit 621988a

File tree

10 files changed

+74
-61
lines changed

10 files changed

+74
-61
lines changed

docs/bundle.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/src/stories/Options/index.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ class WithOptions extends PureComponent {
1616
simpleSelect: false,
1717
showPartiallySelected: false,
1818
disabled: false,
19-
readOnly: false
19+
readOnly: false,
20+
hierarchical: false
2021
}
2122
}
2223

@@ -35,7 +36,7 @@ class WithOptions extends PureComponent {
3536
}
3637

3738
render() {
38-
const { clearSearchOnChange, keepTreeOnSearch, simpleSelect, showPartiallySelected, disabled, readOnly } = this.state
39+
const { clearSearchOnChange, keepTreeOnSearch, simpleSelect, showPartiallySelected, disabled, readOnly, hierarchical } = this.state
3940

4041
return (
4142
<div>
@@ -55,6 +56,7 @@ class WithOptions extends PureComponent {
5556
<Checkbox label="Show Partially Selected" value="showPartiallySelected" checked={showPartiallySelected} onChange={this.onOptionsChange} />
5657
<Checkbox label="Disabled" value="disabled" checked={disabled} onChange={this.onOptionsChange} />
5758
<Checkbox label="Read Only" value="readOnly" checked={readOnly} onChange={this.onOptionsChange} />
59+
<Checkbox label="Hierarchical" value="hierarchical" checked={hierarchical} onChange={this.onOptionsChange} />
5860
</div>
5961
<div>
6062
<DropdownTreeSelect
@@ -68,6 +70,7 @@ class WithOptions extends PureComponent {
6870
showPartiallySelected={showPartiallySelected}
6971
disabled={disabled}
7072
readOnly={readOnly}
73+
hierarchical={hierarchical}
7174
/>
7275
</div>
7376
</div>

src/index.js

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ class DropdownTreeSelect extends Component {
3636
noMatchesText: PropTypes.string,
3737
showPartiallySelected: PropTypes.bool,
3838
disabled: PropTypes.bool,
39-
readOnly: PropTypes.bool
39+
readOnly: PropTypes.bool,
40+
hierarchical: PropTypes.bool
4041
}
4142

4243
static defaultProps = {
@@ -53,8 +54,8 @@ class DropdownTreeSelect extends Component {
5354
}
5455
}
5556

56-
createList = (tree, simple, showPartial) => {
57-
this.treeManager = new TreeManager(tree, simple, showPartial)
57+
createList = ({ data, simpleSelect, showPartiallySelected, hierarchical }) => {
58+
this.treeManager = new TreeManager({ data, simpleSelect, showPartiallySelected, hierarchical })
5859
return this.treeManager.tree
5960
}
6061

@@ -69,7 +70,8 @@ class DropdownTreeSelect extends Component {
6970
}
7071

7172
componentWillMount() {
72-
const tree = this.createList(this.props.data, this.props.simpleSelect, this.props.showPartiallySelected)
73+
const { data, simpleSelect, showPartiallySelected, hierarchical } = this.props
74+
const tree = this.createList({ data, simpleSelect, showPartiallySelected, hierarchical })
7375
const tags = this.treeManager.getTags()
7476
this.setState({ tree, tags })
7577
}
@@ -79,7 +81,8 @@ class DropdownTreeSelect extends Component {
7981
}
8082

8183
componentWillReceiveProps(nextProps) {
82-
const tree = this.createList(nextProps.data, nextProps.simpleSelect, nextProps.showPartiallySelected)
84+
const { data, simpleSelect, showPartiallySelected, hierarchical } = nextProps
85+
const tree = this.createList({ data, simpleSelect, showPartiallySelected, hierarchical })
8386
const tags = this.treeManager.getTags()
8487
this.setState({ tree, tags })
8588
}

src/tree-manager/flatten-tree.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,14 +96,15 @@ const tree = [
9696
* @param {[bool]} showPartialState Whether to show partially checked state
9797
* @return {object} The flattened list
9898
*/
99-
function flattenTree(tree, simple, showPartialState) {
99+
function flattenTree(tree, simple, showPartialState, hierarchical) {
100100
const forest = Array.isArray(tree) ? tree : [tree]
101101

102102
// eslint-disable-next-line no-use-before-define
103103
const { list, defaultValues } = walkNodes({
104104
nodes: forest,
105105
simple,
106-
showPartialState
106+
showPartialState,
107+
hierarchical
107108
})
108109
return { list, defaultValues }
109110
}
@@ -126,7 +127,7 @@ function setInitialStateProps(node, parent = {}) {
126127
}
127128
}
128129

129-
function walkNodes({ nodes, list = new Map(), parent, depth = 0, simple, showPartialState, defaultValues = [] }) {
130+
function walkNodes({ nodes, list = new Map(), parent, depth = 0, simple, showPartialState, defaultValues = [], hierarchical }) {
130131
nodes.forEach((node, i) => {
131132
node._depth = depth
132133

@@ -143,7 +144,7 @@ function walkNodes({ nodes, list = new Map(), parent, depth = 0, simple, showPar
143144
node.checked = true
144145
}
145146

146-
setInitialStateProps(node, parent)
147+
if (!hierarchical) setInitialStateProps(node, parent)
147148

148149
list.set(node._id, node)
149150
if (!simple && node.children) {
@@ -154,7 +155,8 @@ function walkNodes({ nodes, list = new Map(), parent, depth = 0, simple, showPar
154155
parent: node,
155156
depth: depth + 1,
156157
showPartialState,
157-
defaultValues
158+
defaultValues,
159+
hierarchical
158160
})
159161

160162
if (showPartialState && !node.checked) {

src/tree-manager/index.js

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@ import { isEmpty } from '../utils'
44
import flattenTree from './flatten-tree'
55

66
class TreeManager {
7-
constructor(tree, simple, showPartialState) {
8-
this._src = tree
9-
const { list, defaultValues } = flattenTree(JSON.parse(JSON.stringify(tree)), simple, showPartialState)
7+
constructor({ data, simpleSelect, showPartiallySelected, hierarchical }) {
8+
this._src = data
9+
const { list, defaultValues } = flattenTree(JSON.parse(JSON.stringify(data)), simpleSelect, showPartiallySelected, hierarchical)
1010
this.tree = list
1111
this.defaultValues = defaultValues
12-
this.simpleSelect = simple
13-
this.showPartialState = showPartialState
12+
this.simpleSelect = simpleSelect
13+
this.showPartialState = !hierarchical && showPartiallySelected
1414
this.searchMaps = new Map()
15+
this.hierarchical = hierarchical
1516
}
1617

1718
getNodeById(id) {
@@ -118,20 +119,21 @@ class TreeManager {
118119
const node = this.getNodeById(id)
119120
node.checked = checked
120121

122+
// TODO: this can probably be combined with the same check in the else block. investigate in a separate release.
121123
if (this.showPartialState) {
122124
node.partial = false
123125
}
124126

125127
if (this.simpleSelect) {
126128
this.togglePreviousChecked(id)
127129
} else {
128-
this.toggleChildren(id, checked)
130+
if (!this.hierarchical) this.toggleChildren(id, checked)
129131

130132
if (this.showPartialState) {
131133
this.partialCheckParents(node)
132134
}
133135

134-
if (!checked) {
136+
if (!this.hierarchical && !checked) {
135137
this.unCheckParents(node)
136138
}
137139
}
@@ -206,9 +208,12 @@ class TreeManager {
206208
if (visited[key]) return
207209

208210
if (node.checked) {
209-
// Parent node, so no need to walk children
210211
tags.push(node)
211-
markSubTreeVisited(node)
212+
213+
if (!this.hierarchical) {
214+
// Parent node, so no need to walk children
215+
markSubTreeVisited(node)
216+
}
212217
} else {
213218
visited[key] = true
214219
}

src/tree-manager/tests/index.test.js

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ test('should not mutate input', t => {
2323
]
2424
}
2525
/* eslint-disable no-new */
26-
new TreeManager(actual)
26+
new TreeManager({ data: actual })
2727
t.deepEqual(actual, expected)
2828
})
2929

@@ -41,7 +41,7 @@ test('should set initial check state based on parent check state when node check
4141
],
4242
checked: true
4343
}
44-
const manager = new TreeManager(tree)
44+
const manager = new TreeManager({ data: tree })
4545
t.true(manager.getNodeById('c1').checked)
4646
})
4747

@@ -59,7 +59,7 @@ test('should set initial check state based on node check state when node check s
5959
}
6060
]
6161
}
62-
const manager = new TreeManager(tree)
62+
const manager = new TreeManager({ data: tree })
6363
t.true(manager.getNodeById('c1').checked)
6464
})
6565

@@ -77,7 +77,7 @@ test('should set initial check state based on node check state when node check s
7777
}
7878
]
7979
}
80-
const manager = new TreeManager(tree)
80+
const manager = new TreeManager({ data: tree })
8181
t.false(manager.getNodeById('c1').checked)
8282
})
8383

@@ -95,7 +95,7 @@ test('should get tags based on children check state', t => {
9595
}
9696
]
9797
}
98-
const manager = new TreeManager(tree)
98+
const manager = new TreeManager({ data: tree })
9999
t.deepEqual(manager.getTags().map(t => t.label), ['l1c1'])
100100
})
101101

@@ -112,7 +112,7 @@ test('should get tags based on parent check state', t => {
112112
}
113113
]
114114
}
115-
const manager = new TreeManager(tree)
115+
const manager = new TreeManager({ data: tree })
116116
t.deepEqual(manager.getTags().map(t => t.label), ['l1'])
117117
})
118118

@@ -141,7 +141,7 @@ test('should get tags based on multiple parent check state', t => {
141141
]
142142
}
143143
]
144-
const manager = new TreeManager(tree)
144+
const manager = new TreeManager({ data: tree })
145145
t.deepEqual(manager.getTags().map(t => t.label), ['l1', 'l2'])
146146
})
147147

@@ -170,7 +170,7 @@ test('should get tags based on multiple parent/child check state', t => {
170170
]
171171
}
172172
]
173-
const manager = new TreeManager(tree)
173+
const manager = new TreeManager({ data: tree })
174174
t.deepEqual(manager.getTags().map(t => t.label), ['l1', 'l2c2'])
175175
})
176176

@@ -187,7 +187,7 @@ test('should toggle children when checked', t => {
187187
}
188188
]
189189
}
190-
const manager = new TreeManager(tree)
190+
const manager = new TreeManager({ data: tree })
191191
manager.setNodeCheckedState('i1', true)
192192
t.true(manager.getNodeById('c1').checked)
193193
})
@@ -206,7 +206,7 @@ test('should toggle children when unchecked', t => {
206206
}
207207
]
208208
}
209-
const manager = new TreeManager(tree)
209+
const manager = new TreeManager({ data: tree })
210210
manager.setNodeCheckedState('i1', false)
211211
t.false(manager.getNodeById('c1').checked)
212212
})
@@ -225,7 +225,7 @@ test('should uncheck parent when unchecked', t => {
225225
}
226226
]
227227
}
228-
const manager = new TreeManager(tree)
228+
const manager = new TreeManager({ data: tree })
229229
manager.setNodeCheckedState('c1', false)
230230
t.false(manager.getNodeById('i1').checked)
231231
})
@@ -250,7 +250,7 @@ test('should uncheck all parents when unchecked', t => {
250250
}
251251
]
252252
}
253-
const manager = new TreeManager(tree)
253+
const manager = new TreeManager({ data: tree })
254254
manager.setNodeCheckedState('c2', false)
255255
t.false(manager.getNodeById('c1').checked)
256256
t.false(manager.getNodeById('i1').checked)
@@ -279,7 +279,7 @@ test('should collapse all children when collapsed', t => {
279279
}
280280
]
281281
}
282-
const manager = new TreeManager(tree)
282+
const manager = new TreeManager({ data: tree })
283283
manager.toggleNodeExpandState('i1')
284284
t.false(manager.getNodeById('c1').expanded)
285285
t.false(manager.getNodeById('c2').expanded)
@@ -305,7 +305,7 @@ test('should expand node (and not children) when expanded', t => {
305305
}
306306
]
307307
}
308-
const manager = new TreeManager(tree)
308+
const manager = new TreeManager({ data: tree })
309309
manager.toggleNodeExpandState('i1')
310310
t.true(manager.getNodeById('i1').expanded)
311311
t.falsy(manager.getNodeById('c1').expanded)
@@ -332,7 +332,7 @@ test('should get matching nodes when searched', t => {
332332
}
333333
]
334334
}
335-
const manager = new TreeManager(tree)
335+
const manager = new TreeManager({ data: tree })
336336
const { allNodesHidden, tree: matchTree } = manager.filterTree('search')
337337
t.false(allNodesHidden)
338338
const nodes = ['i1', 'c1']
@@ -360,7 +360,7 @@ test('should hide all nodes when search term is not found', t => {
360360
}
361361
]
362362
}
363-
const manager = new TreeManager(tree)
363+
const manager = new TreeManager({ data: tree })
364364
const { allNodesHidden } = manager.filterTree('bla-bla')
365365
t.true(allNodesHidden)
366366
})
@@ -392,7 +392,7 @@ test('should use cached results for subsequent searches', t => {
392392
value: 'sears'
393393
}
394394
]
395-
const manager = new TreeManager(tree)
395+
const manager = new TreeManager({ data: tree })
396396
const { allNodesHidden } = manager.filterTree('sea')
397397
manager.filterTree('sear')
398398
manager.filterTree('on')
@@ -428,7 +428,7 @@ test('should restore nodes', t => {
428428
value: 'sears'
429429
}
430430
]
431-
const manager = new TreeManager(tree)
431+
const manager = new TreeManager({ data: tree })
432432
manager.filterTree('search')
433433
manager.restoreNodes()
434434
const visibleNodes = ['i1', 'i2', 'c1', 'c2']
@@ -455,7 +455,7 @@ test('should get matching nodes with mixed case when searched', t => {
455455
}
456456
]
457457
}
458-
const manager = new TreeManager(tree)
458+
const manager = new TreeManager({ data: tree })
459459
const { allNodesHidden, tree: matchTree } = manager.filterTree('SearCH')
460460
t.false(allNodesHidden)
461461
const nodes = ['i1', 'c1']
@@ -490,7 +490,7 @@ test('should uncheck previous node in simple select mode', t => {
490490
]
491491
}
492492
]
493-
const manager = new TreeManager(tree, true)
493+
const manager = new TreeManager({ data: tree, simpleSelect: true })
494494
manager.setNodeCheckedState('i1', true)
495495
t.true(manager.getNodeById('i1').checked)
496496

@@ -532,7 +532,7 @@ test('should restore default values', t => {
532532
]
533533
}
534534
]
535-
const manager = new TreeManager(tree)
535+
const manager = new TreeManager({ data: tree })
536536
manager.setNodeCheckedState('c1', false)
537537
t.false(manager.getNodeById('c1').checked)
538538

0 commit comments

Comments
 (0)