@@ -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 ,
0 commit comments