From 15164aa2531198f985a9e6af036533de8d6c813b Mon Sep 17 00:00:00 2001 From: Joshua Chan Mun Wei Date: Fri, 22 Mar 2019 23:36:51 +0800 Subject: [PATCH 1/3] feat: support useTopNode option --- src/js/CheckboxTree.js | 34 ++++++++++++++++++++++++++++++---- src/js/NodeModel.js | 7 +++++-- 2 files changed, 35 insertions(+), 6 deletions(-) diff --git a/src/js/CheckboxTree.js b/src/js/CheckboxTree.js index 21f52144..739ad387 100644 --- a/src/js/CheckboxTree.js +++ b/src/js/CheckboxTree.js @@ -36,6 +36,7 @@ class CheckboxTree extends React.Component { onCheck: PropTypes.func, onClick: PropTypes.func, onExpand: PropTypes.func, + useTopNode: PropTypes.bool, }; static defaultProps = { @@ -74,6 +75,7 @@ class CheckboxTree extends React.Component { onCheck: () => {}, onClick: null, 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,10 +171,15 @@ class CheckboxTree extends React.Component { } determineShallowCheckState(node, noCascade) { + const { useTopNode } = this.props const flatNode = this.state.model.getNode(node.value); - if (flatNode.isLeaf || noCascade) { - return flatNode.checked ? 1 : 0; + if (useTopNode) { + if (this.isAnyParentChecked(flatNode)) { + return 2; + } else { + return flatNode.checked ? 1 : 0; + } } if (this.isEveryChildChecked(node)) { @@ -186,6 +193,19 @@ class CheckboxTree extends React.Component { return 0; } + isAnyParentChecked(node) { + if (Object.entries(node.parent).length === 0 && node.parent.constructor === Object) { + return false + } else { + const parent = this.state.model.getNode(node.parent.value) + if (parent.checkState === 1) { + return true + } else { + return this.isAnyParentChecked(parent) + } + } + } + isEveryChildChecked(node) { return node.children.every(child => this.state.model.getNode(child.value).checkState === 1); } @@ -206,6 +226,7 @@ class CheckboxTree extends React.Component { optimisticToggle, showNodeTitle, showNodeIcon, + useTopNode, } = this.props; const { id, model } = this.state; const { icons: defaultIcons } = CheckboxTree.defaultProps; @@ -213,6 +234,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 diff --git a/src/js/NodeModel.js b/src/js/NodeModel.js index 8058f336..646c199d 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); }); } From 8fee8ffe777fe1a34292044fb711a4db5be0c3ff Mon Sep 17 00:00:00 2001 From: Joshua Chan Mun Wei Date: Sat, 23 Mar 2019 00:22:26 +0800 Subject: [PATCH 2/3] fix: useTopNode check in TreeNode and NodeModel --- src/js/CheckboxTree.js | 1 + src/js/NodeModel.js | 2 +- src/js/TreeNode.js | 4 +++- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/js/CheckboxTree.js b/src/js/CheckboxTree.js index 739ad387..5799490e 100644 --- a/src/js/CheckboxTree.js +++ b/src/js/CheckboxTree.js @@ -280,6 +280,7 @@ class CheckboxTree extends React.Component { onCheck={this.onCheck} onClick={onClick && this.onNodeClick} onExpand={this.onExpand} + useTopNode={useTopNode} > {children} diff --git a/src/js/NodeModel.js b/src/js/NodeModel.js index 646c199d..255d91a3 100644 --- a/src/js/NodeModel.js +++ b/src/js/NodeModel.js @@ -125,7 +125,7 @@ class NodeModel { } // Percolate check status down to all children flatNode.children.forEach((child) => { - this.toggleChecked(child, useTopNode ? false : isChecked, noCascade); + this.toggleChecked(child, useTopNode ? false : isChecked, noCascade, useTopNode); }); } diff --git a/src/js/TreeNode.js b/src/js/TreeNode.js index 71ba66d7..9112b6ef 100644 --- a/src/js/TreeNode.js +++ b/src/js/TreeNode.js @@ -35,6 +35,7 @@ class TreeNode extends React.Component { showCheckbox: PropTypes.bool, title: PropTypes.string, onClick: PropTypes.func, + useTopNode: PropTypes.bool, }; static defaultProps = { @@ -56,8 +57,9 @@ 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 }) }); } From c07687a4709f514d1203cc2abca980bfd3071dd6 Mon Sep 17 00:00:00 2001 From: Joshua Chan Mun Wei Date: Sat, 23 Mar 2019 02:42:35 +0800 Subject: [PATCH 3/3] fix: lint to pass CI --- src/js/CheckboxTree.js | 36 +++++++++++++++++------------------- src/js/TreeNode.js | 18 +++++++++++++----- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/src/js/CheckboxTree.js b/src/js/CheckboxTree.js index 5799490e..e3873acc 100644 --- a/src/js/CheckboxTree.js +++ b/src/js/CheckboxTree.js @@ -33,10 +33,10 @@ 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, - useTopNode: PropTypes.bool, }; static defaultProps = { @@ -72,9 +72,9 @@ class CheckboxTree extends React.Component { showExpandAll: false, showNodeIcon: true, showNodeTitle: false, - onCheck: () => {}, + onCheck: () => { }, onClick: null, - onExpand: () => {}, + onExpand: () => { }, useTopNode: false, }; @@ -171,15 +171,15 @@ class CheckboxTree extends React.Component { } determineShallowCheckState(node, noCascade) { - const { useTopNode } = this.props + const { useTopNode } = this.props; const flatNode = this.state.model.getNode(node.value); - if (useTopNode) { - if (this.isAnyParentChecked(flatNode)) { - return 2; - } else { - return flatNode.checked ? 1 : 0; - } + if (useTopNode && this.isAnyParentChecked(flatNode)) { + return 2; + } + + if (useTopNode || flatNode.isLeaf || noCascade) { + return flatNode.checked ? 1 : 0; } if (this.isEveryChildChecked(node)) { @@ -195,15 +195,13 @@ class CheckboxTree extends React.Component { isAnyParentChecked(node) { if (Object.entries(node.parent).length === 0 && node.parent.constructor === Object) { - return false - } else { - const parent = this.state.model.getNode(node.parent.value) - if (parent.checkState === 1) { - return true - } else { - return this.isAnyParentChecked(parent) - } + return false; + } + const parent = this.state.model.getNode(node.parent.value); + if (parent.checkState === 1) { + return true; } + return this.isAnyParentChecked(parent); } isEveryChildChecked(node) { @@ -237,7 +235,7 @@ class CheckboxTree extends React.Component { // Get checkState before traversing tree depth-first if (useTopNode) { - flatNode.checkState = flatNode.checked ? 1 : 0 + flatNode.checkState = flatNode.checked ? 1 : 0; } const children = flatNode.isParent ? this.renderTreeNodes(node.children, node) : null; diff --git a/src/js/TreeNode.js b/src/js/TreeNode.js index 9112b6ef..8064741d 100644 --- a/src/js/TreeNode.js +++ b/src/js/TreeNode.js @@ -34,8 +34,8 @@ class TreeNode extends React.Component { icon: PropTypes.node, showCheckbox: PropTypes.bool, title: PropTypes.string, - onClick: PropTypes.func, useTopNode: PropTypes.bool, + onClick: PropTypes.func, }; static defaultProps = { @@ -45,7 +45,8 @@ class TreeNode extends React.Component { icon: null, showCheckbox: true, title: null, - onClick: () => {}, + useTopNode: false, + onClick: () => { }, }; constructor(props) { @@ -57,9 +58,16 @@ class TreeNode extends React.Component { } onCheck() { - const { value, onCheck, checked, useTopNode } = this.props; + const { + value, + onCheck, + checked, + useTopNode, + } = this.props; - if (checked === 2 && useTopNode) return; + if (checked === 2 && useTopNode) { + return; + } onCheck({ value, checked: this.getCheckState({ toggle: true }) }); } @@ -217,7 +225,7 @@ class TreeNode extends React.Component { id={inputId} indeterminate={checked === 2} onClick={this.onCheck} - onChange={() => {}} + onChange={() => { }} /> {this.renderCheckboxIcon()}