Skip to content

Refactor #97

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Sep 5, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/lib
/node_modules
yarn.lock
package-lock.json
18 changes: 9 additions & 9 deletions examples/dist/index.js

Large diffs are not rendered by default.

180 changes: 85 additions & 95 deletions src/js/CheckboxTree.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class CheckboxTree extends React.Component {
super(props);

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

this.flattenNodes(props.nodes);
this.unserializeLists({
Expand All @@ -71,54 +71,42 @@ class CheckboxTree extends React.Component {
}

onCheck(node) {
// node is object from TreeNode
const { noCascade, onCheck } = this.props;

this.toggleChecked(node, node.checked, noCascade);
onCheck(this.serializeList('checked'), node);
}

onExpand(node) {
// node is object from TreeNode
const { onExpand } = this.props;

this.toggleNode('expanded', node, node.expanded);
onExpand(this.serializeList('expanded'), node);
}

getFormattedNodes(nodes) {
return nodes.map((node) => {
const formatted = { ...node };
getShallowCheckState(node, noCascade) {
// node is from props.nodes
const flatNode = this.flatNodes[node.value];

formatted.checked = this.nodes[node.value].checked;
formatted.expanded = this.nodes[node.value].expanded;
formatted.showCheckbox = node.showCheckbox !== undefined ? node.showCheckbox : true;

if (Array.isArray(node.children) && node.children.length > 0) {
formatted.children = this.getFormattedNodes(formatted.children);
} else {
formatted.children = null;
}

return formatted;
});
}

getCheckState(node, noCascade) {
if (node.children === null || noCascade) {
return node.checked ? 1 : 0;
if (flatNode.isLeaf || noCascade) {
return flatNode.checked ? 1 : 0;
}

if (this.isEveryChildChecked(node)) {
if (node.children.every(child => (this.flatNodes[child.value].checkState === 1))) {
return 1;
}

if (this.isSomeChildChecked(node)) {
if (node.children.some(child => (this.flatNodes[child.value].checkState > 0))) {
return 2;
}

return 0;
}

getDisabledState(node, parent, disabledProp, noCascade) {
// node is from props.nodes
if (disabledProp) {
return true;
}
Expand All @@ -131,45 +119,56 @@ class CheckboxTree extends React.Component {
}

toggleChecked(node, isChecked, noCascade) {
if (node.children === null || noCascade) {
// node is object from TreeNode
const flatNode = this.flatNodes[node.value];

if (flatNode.isLeaf || noCascade) {
// Set the check status of a leaf node or an uncoupled parent
this.toggleNode('checked', node, isChecked);
} else {
const { children } = flatNode.self;
// Percolate check status down to all children
node.children.forEach((child) => {
this.toggleChecked(child, isChecked);
children.forEach((child) => {
this.toggleChecked(child, isChecked, noCascade);
});
}
}

toggleNode(key, node, toggleValue) {
this.nodes[node.value][key] = toggleValue;
this.flatNodes[node.value][key] = toggleValue;
}

flattenNodes(nodes) {
flattenNodes(nodes, parentNode = {}) {
// nodes are from props.nodes
if (!Array.isArray(nodes) || nodes.length === 0) {
return;
}

nodes.forEach((node) => {
this.nodes[node.value] = {};
this.flattenNodes(node.children);
// set defaults, calculated values and tree references
this.flatNodes[node.value] = {
parent: parentNode,
self: node,
isLeaf: !Array.isArray(node.children) || node.children.length === 0,
showCheckbox: node.showCheckbox !== undefined ? node.showCheckbox : true,
};
this.flattenNodes(node.children, node);
});
}

unserializeLists(lists) {
// Reset values to false
Object.keys(this.nodes).forEach((value) => {
Object.keys(this.flatNodes).forEach((value) => {
Object.keys(lists).forEach((listKey) => {
this.nodes[value][listKey] = false;
this.flatNodes[value][listKey] = false;
});
});

// Unserialize values and set their nodes to true
Object.keys(lists).forEach((listKey) => {
lists[listKey].forEach((value) => {
if (this.nodes[value] !== undefined) {
this.nodes[value][listKey] = true;
if (this.flatNodes[value] !== undefined) {
this.flatNodes[value][listKey] = true;
}
});
});
Expand All @@ -178,36 +177,17 @@ class CheckboxTree extends React.Component {
serializeList(key) {
const list = [];

Object.keys(this.nodes).forEach((value) => {
if (this.nodes[value][key]) {
Object.keys(this.flatNodes).forEach((value) => {
if (this.flatNodes[value][key]) {
list.push(value);
}
});

return list;
}

isEveryChildChecked(node) {
return node.children.every((child) => {
if (child.children !== null) {
return this.isEveryChildChecked(child);
}

return child.checked;
});
}

isSomeChildChecked(node) {
return node.children.some((child) => {
if (child.children !== null) {
return this.isSomeChildChecked(child);
}

return child.checked;
});
}

renderTreeNodes(nodes, parent = {}) {
// nodes are props.nodes
const {
disabled,
expandDisabled,
Expand All @@ -218,39 +198,57 @@ class CheckboxTree extends React.Component {
showNodeIcon,
onClick,
} = this.props;

const treeNodes = nodes.map((node) => {
const key = `${node.value}`;
const checked = this.getCheckState(node, noCascade);
const isLeaf = node.children === null;
const children = this.renderChildNodes(node);

const flatNode = this.flatNodes[node.value];

let children = null;
if (!flatNode.isLeaf) {
children = this.renderTreeNodes(node.children, node);
}

// set checkState here
// this can be "shallow" because checkState is updated for all
// nested children in the recursive call to renderTreeNodes above
flatNode.checkState = this.getShallowCheckState(node, noCascade);

const nodeDisabled = this.getDisabledState(node, parent, disabled, noCascade);

// Show checkbox only if this is a leaf node or showCheckbox is true
const showCheckbox = onlyLeafCheckboxes ? isLeaf : node.showCheckbox;

return (
<TreeNode
key={key}
checked={checked}
className={node.className}
disabled={nodeDisabled}
expandDisabled={expandDisabled}
expandOnClick={expandOnClick}
expanded={node.expanded}
icon={node.icon}
label={node.label}
optimisticToggle={optimisticToggle}
rawChildren={node.children}
showCheckbox={showCheckbox}
showNodeIcon={showNodeIcon}
treeId={this.id}
value={node.value}
onCheck={this.onCheck}
onClick={onClick}
onExpand={this.onExpand}
>
{children}
</TreeNode>
);
const showCheckbox = onlyLeafCheckboxes ? flatNode.isLeaf : flatNode.showCheckbox;

// root of tree has no parent value and is expanded by default
const parentExpanded = parent.value ? this.flatNodes[parent.value].expanded : true;
if (parentExpanded) {
return (
<TreeNode
key={key}
checked={flatNode.checkState}
className={node.className}
disabled={nodeDisabled}
expandDisabled={expandDisabled}
expandOnClick={expandOnClick}
expanded={flatNode.expanded}
icon={node.icon}
label={node.label}
optimisticToggle={optimisticToggle}
isLeaf={flatNode.isLeaf}
showCheckbox={showCheckbox}
showNodeIcon={showNodeIcon}
treeId={this.id}
value={node.value}
onCheck={this.onCheck}
onClick={onClick}
onExpand={this.onExpand}
>
{children}
</TreeNode>
);
}

return null;
});

return (
Expand All @@ -260,14 +258,6 @@ class CheckboxTree extends React.Component {
);
}

renderChildNodes(node) {
if (node.children !== null && node.expanded) {
return this.renderTreeNodes(node.children, node);
}

return null;
}

renderHiddenInput() {
if (this.props.name === undefined) {
return null;
Expand Down Expand Up @@ -295,8 +285,8 @@ class CheckboxTree extends React.Component {
}

render() {
const nodes = this.getFormattedNodes(this.props.nodes);
const treeNodes = this.renderTreeNodes(nodes);
const treeNodes = this.renderTreeNodes(this.props.nodes);

const className = classNames({
'react-checkbox-tree': true,
'rct-disabled': this.props.disabled,
Expand Down
Loading