diff --git a/src/js/CheckboxTree.js b/src/js/CheckboxTree.js index 21f52144..e3873acc 100644 --- a/src/js/CheckboxTree.js +++ b/src/js/CheckboxTree.js @@ -33,6 +33,7 @@ class CheckboxTree extends React.Component { showExpandAll: PropTypes.bool, showNodeIcon: PropTypes.bool, showNodeTitle: PropTypes.bool, + useTopNode: PropTypes.bool, onCheck: PropTypes.func, onClick: PropTypes.func, onExpand: PropTypes.func, @@ -71,9 +72,10 @@ class CheckboxTree extends React.Component { showExpandAll: false, showNodeIcon: true, showNodeTitle: false, - onCheck: () => {}, + onCheck: () => { }, onClick: null, - onExpand: () => {}, + onExpand: () => { }, + useTopNode: false, }; constructor(props) { @@ -125,11 +127,11 @@ class CheckboxTree extends React.Component { } onCheck(nodeInfo) { - const { noCascade, onCheck } = this.props; + const { noCascade, onCheck, useTopNode } = this.props; const model = this.state.model.clone(); const node = model.getNode(nodeInfo.value); - model.toggleChecked(nodeInfo, nodeInfo.checked, noCascade); + model.toggleChecked(nodeInfo, nodeInfo.checked, noCascade, useTopNode); onCheck(model.serializeList('checked'), { ...node, ...nodeInfo }); } @@ -169,9 +171,14 @@ class CheckboxTree extends React.Component { } determineShallowCheckState(node, noCascade) { + const { useTopNode } = this.props; const flatNode = this.state.model.getNode(node.value); - if (flatNode.isLeaf || noCascade) { + if (useTopNode && this.isAnyParentChecked(flatNode)) { + return 2; + } + + if (useTopNode || flatNode.isLeaf || noCascade) { return flatNode.checked ? 1 : 0; } @@ -186,6 +193,17 @@ class CheckboxTree extends React.Component { return 0; } + isAnyParentChecked(node) { + if (Object.entries(node.parent).length === 0 && node.parent.constructor === Object) { + return false; + } + const parent = this.state.model.getNode(node.parent.value); + if (parent.checkState === 1) { + return true; + } + return this.isAnyParentChecked(parent); + } + isEveryChildChecked(node) { return node.children.every(child => this.state.model.getNode(child.value).checkState === 1); } @@ -206,6 +224,7 @@ class CheckboxTree extends React.Component { optimisticToggle, showNodeTitle, showNodeIcon, + useTopNode, } = this.props; const { id, model } = this.state; const { icons: defaultIcons } = CheckboxTree.defaultProps; @@ -213,6 +232,11 @@ class CheckboxTree extends React.Component { const treeNodes = nodes.map((node) => { const key = node.value; const flatNode = model.getNode(node.value); + + // Get checkState before traversing tree depth-first + if (useTopNode) { + flatNode.checkState = flatNode.checked ? 1 : 0; + } const children = flatNode.isParent ? this.renderTreeNodes(node.children, node) : null; // Determine the check state after all children check states have been determined @@ -254,6 +278,7 @@ class CheckboxTree extends React.Component { onCheck={this.onCheck} onClick={onClick && this.onNodeClick} onExpand={this.onExpand} + useTopNode={useTopNode} > {children} </TreeNode> diff --git a/src/js/NodeModel.js b/src/js/NodeModel.js index 8058f336..255d91a3 100644 --- a/src/js/NodeModel.js +++ b/src/js/NodeModel.js @@ -109,7 +109,7 @@ class NodeModel { return this; } - toggleChecked(node, isChecked, noCascade) { + toggleChecked(node, isChecked, noCascade, useTopNode) { const flatNode = this.flatNodes[node.value]; if (flatNode.isLeaf || noCascade) { @@ -120,9 +120,12 @@ class NodeModel { // Set the check status of a leaf node or an uncoupled parent this.toggleNode(node.value, 'checked', isChecked); } else { + if (useTopNode) { + this.toggleNode(node.value, 'checked', isChecked); + } // Percolate check status down to all children flatNode.children.forEach((child) => { - this.toggleChecked(child, isChecked, noCascade); + this.toggleChecked(child, useTopNode ? false : isChecked, noCascade, useTopNode); }); } diff --git a/src/js/TreeNode.js b/src/js/TreeNode.js index 71ba66d7..8064741d 100644 --- a/src/js/TreeNode.js +++ b/src/js/TreeNode.js @@ -34,6 +34,7 @@ class TreeNode extends React.Component { icon: PropTypes.node, showCheckbox: PropTypes.bool, title: PropTypes.string, + useTopNode: PropTypes.bool, onClick: PropTypes.func, }; @@ -44,7 +45,8 @@ class TreeNode extends React.Component { icon: null, showCheckbox: true, title: null, - onClick: () => {}, + useTopNode: false, + onClick: () => { }, }; constructor(props) { @@ -56,8 +58,16 @@ class TreeNode extends React.Component { } onCheck() { - const { value, onCheck } = this.props; + const { + value, + onCheck, + checked, + useTopNode, + } = this.props; + if (checked === 2 && useTopNode) { + return; + } onCheck({ value, checked: this.getCheckState({ toggle: true }) }); } @@ -215,7 +225,7 @@ class TreeNode extends React.Component { id={inputId} indeterminate={checked === 2} onClick={this.onCheck} - onChange={() => {}} + onChange={() => { }} /> <span className="rct-checkbox"> {this.renderCheckboxIcon()}