react-checkbox-tree
Advanced tools
Comparing version 0.5.2 to 0.6.0
# CHANGELOG | ||
## [v0.6.0](https://github.com/jakezatecky/react-checkbox-tree/compare/v0.5.2...v0.6.0) (2017-05-06) | ||
### New Features | ||
* [#32]: Allow customization of `className` at the node level | ||
* [#30]: Add `showNodeIcon` property to optionally remove node icons | ||
### Other | ||
* [#14]: Component performance when rendering and updating a large number of nodes has been significantly increased | ||
## [v0.5.2](https://github.com/jakezatecky/react-checkbox-tree/compare/v0.5.1...v0.5.2) (2017-05-03) | ||
@@ -4,0 +15,0 @@ |
{ | ||
"name": "react-checkbox-tree", | ||
"version": "0.5.2", | ||
"version": "0.6.0", | ||
"description": "A simple and elegant checkbox tree for React.", | ||
@@ -62,2 +62,3 @@ "author": "Jake Zatecky", | ||
"classnames": "^2.2.5", | ||
"lodash": "^4.17.4", | ||
"prop-types": "^15.5.8", | ||
@@ -64,0 +65,0 @@ "shortid": "^2.2.6" |
@@ -88,2 +88,4 @@ # react-checkbox-tree | ||
All node objects **must** have a unique `value`. This value is serialized into the `checked` and `expanded` arrays and is also used for performance optimizations. | ||
### Properties | ||
@@ -96,7 +98,8 @@ | ||
| `expanded` | array | An array of expanded node values. | `[]` | | ||
| `onCheck` | function | onCheck handler: `function(checked) {}` | `() => {}` | | ||
| `onExpand` | function | onExpand handler: `function(expanded) {}` | `() => {}` | | ||
| `name` | string | Optional name for the hidden `<input>` element. | `undefined` | | ||
| `nameAsArray` | bool | If true, the hidden `<input>` will encode its values as an array rather than a joined string. | `false` | | ||
| `optimisticToggle` | bool | If true, toggling a partially-checked node will select all children. If false, it will deselect. | `true` | | ||
| `showNodeIcon` | bool | If true, each node will show a parent or leaf icon. | `true` | | ||
| `onCheck` | function | onCheck handler: `function(checked) {}` | `() => {}` | | ||
| `onExpand` | function | onExpand handler: `function(expanded) {}` | `() => {}` | | ||
@@ -107,7 +110,8 @@ #### Node Properties | ||
| Property | Type | Description | | ||
| ---------- | ------ | ------------------------------- | | ||
| `label` | string | **Required**. The node's label. | | ||
| `value` | mixed | **Required**. The node's value. | | ||
| `children` | array | An array of child nodes. | | ||
| `icon` | mixed | A custom icon for the node. | | ||
| Property | Type | Description | | ||
| ----------- | ------ | ------------------------------- | | ||
| `label` | string | **Required**. The node's label. | | ||
| `value` | mixed | **Required**. The node's value. | | ||
| `children` | array | An array of child nodes. | | ||
| `className` | string | A className to add to the node. | | ||
| `icon` | mixed | A custom icon for the node. | |
@@ -0,1 +1,2 @@ | ||
import { isEqual } from 'lodash'; | ||
import PropTypes from 'prop-types'; | ||
@@ -12,7 +13,8 @@ import React from 'react'; | ||
checked: PropTypes.arrayOf(React.PropTypes.string), | ||
expanded: PropTypes.arrayOf(React.PropTypes.string), | ||
checked: PropTypes.arrayOf(PropTypes.string), | ||
expanded: PropTypes.arrayOf(PropTypes.string), | ||
name: PropTypes.string, | ||
nameAsArray: PropTypes.bool, | ||
optimisticToggle: PropTypes.bool, | ||
showNodeIcon: PropTypes.bool, | ||
onCheck: PropTypes.func, | ||
@@ -29,2 +31,3 @@ onExpand: PropTypes.func, | ||
optimisticToggle: true, | ||
showNodeIcon: true, | ||
onCheck: () => {}, | ||
@@ -37,28 +40,43 @@ onExpand: () => {}, | ||
this.id = `rct-${shortid.generate()}`; | ||
this.nodes = {}; | ||
this.flattenNodes(props.nodes); | ||
this.unserializeLists({ | ||
checked: props.checked, | ||
expanded: props.expanded, | ||
}); | ||
this.onCheck = this.onCheck.bind(this); | ||
this.onExpand = this.onExpand.bind(this); | ||
} | ||
this.id = `rct-${shortid.generate()}`; | ||
componentWillReceiveProps({ nodes, checked, expanded }) { | ||
if (!isEqual(this.props.nodes, nodes)) { | ||
this.flattenNodes(nodes); | ||
} | ||
this.unserializeLists({ checked, expanded }); | ||
} | ||
onCheck(node) { | ||
const { checked, onCheck } = this.props; | ||
const { onCheck } = this.props; | ||
onCheck(this.toggleChecked([...checked], node, node.checked)); | ||
this.toggleChecked(node, node.checked); | ||
onCheck(this.serializeList('checked')); | ||
} | ||
onExpand(node) { | ||
const { expanded, onExpand } = this.props; | ||
const { onExpand } = this.props; | ||
onExpand(this.toggleNode([...expanded], node, node.expanded)); | ||
this.toggleNode('expanded', node, node.expanded); | ||
onExpand(this.serializeList('expanded')); | ||
} | ||
getFormattedNodes(nodes) { | ||
const { checked, expanded } = this.props; | ||
return nodes.map((node) => { | ||
const formatted = { ...node }; | ||
formatted.checked = checked.indexOf(node.value) > -1; | ||
formatted.expanded = expanded.indexOf(node.value) > -1; | ||
formatted.checked = this.nodes[node.value].checked; | ||
formatted.expanded = this.nodes[node.value].expanded; | ||
@@ -91,25 +109,54 @@ if (Array.isArray(node.children) && node.children.length > 0) { | ||
toggleChecked(checked, node, isChecked) { | ||
toggleChecked(node, isChecked) { | ||
if (node.children !== null) { | ||
// Percolate check status down to all children | ||
node.children.forEach((child) => { | ||
this.toggleChecked(checked, child, isChecked); | ||
this.toggleChecked(child, isChecked); | ||
}); | ||
} else { | ||
// Set leaf to check/unchecked state | ||
this.toggleNode(checked, node, isChecked); | ||
this.toggleNode('checked', node, isChecked); | ||
} | ||
} | ||
return checked; | ||
toggleNode(key, node, toggleValue) { | ||
this.nodes[node.value][key] = toggleValue; | ||
} | ||
toggleNode(list, node, toggleValue) { | ||
const index = list.indexOf(node.value); | ||
if (index > -1 && !toggleValue) { | ||
list.splice(index, 1); | ||
} else if (index === -1 && toggleValue) { | ||
list.push(node.value); | ||
flattenNodes(nodes) { | ||
if (!Array.isArray(nodes) || nodes.length === 0) { | ||
return; | ||
} | ||
nodes.forEach((node) => { | ||
this.nodes[node.value] = {}; | ||
this.flattenNodes(node.children); | ||
}); | ||
} | ||
unserializeLists(lists) { | ||
// Reset values to false | ||
Object.keys(this.nodes).forEach((value) => { | ||
Object.keys(lists).forEach((listKey) => { | ||
this.nodes[value][listKey] = false; | ||
}); | ||
}); | ||
// Unserialize values and set their nodes to true | ||
Object.keys(lists).forEach((listKey) => { | ||
lists[listKey].forEach((value) => { | ||
this.nodes[value][listKey] = true; | ||
}); | ||
}); | ||
} | ||
serializeList(key) { | ||
const list = []; | ||
Object.keys(this.nodes).forEach((value) => { | ||
if (this.nodes[value][key]) { | ||
list.push(value); | ||
} | ||
}); | ||
return list; | ||
@@ -139,4 +186,4 @@ } | ||
renderTreeNodes(nodes) { | ||
const treeNodes = nodes.map((node, index) => { | ||
const key = `${index}-${node.value}`; | ||
const treeNodes = nodes.map((node) => { | ||
const key = `${node.value}`; | ||
const checked = this.getCheckState(node); | ||
@@ -149,2 +196,3 @@ const children = this.renderChildNodes(node); | ||
checked={checked} | ||
className={node.className} | ||
expanded={node.expanded} | ||
@@ -155,2 +203,3 @@ icon={node.icon} | ||
rawChildren={node.children} | ||
showNodeIcon={this.props.showNodeIcon} | ||
treeId={this.id} | ||
@@ -157,0 +206,0 @@ value={node.value} |
@@ -13,2 +13,3 @@ import classNames from 'classnames'; | ||
optimisticToggle: PropTypes.bool.isRequired, | ||
showNodeIcon: PropTypes.bool.isRequired, | ||
treeId: PropTypes.string.isRequired, | ||
@@ -20,2 +21,3 @@ value: PropTypes.string.isRequired, | ||
children: PropTypes.node, | ||
className: PropTypes.string, | ||
icon: PropTypes.node, | ||
@@ -27,2 +29,3 @@ rawChildren: PropTypes.arrayOf(nodeShape), | ||
children: null, | ||
className: null, | ||
icon: null, | ||
@@ -70,10 +73,2 @@ rawChildren: null, | ||
renderCollapseIcon() { | ||
if (!this.props.expanded) { | ||
return <span className="rct-icon rct-icon-expand-close" />; | ||
} | ||
return <span className="rct-icon rct-icon-expand-open" />; | ||
} | ||
renderCollapseButton() { | ||
@@ -101,2 +96,10 @@ if (!this.hasChildren()) { | ||
renderCollapseIcon() { | ||
if (!this.props.expanded) { | ||
return <span className="rct-icon rct-icon-expand-close" />; | ||
} | ||
return <span className="rct-icon rct-icon-expand-open" />; | ||
} | ||
renderCheckboxIcon() { | ||
@@ -139,3 +142,3 @@ if (this.props.checked === 0) { | ||
render() { | ||
const { checked, treeId, label, value } = this.props; | ||
const { checked, className, treeId, label, showNodeIcon, value } = this.props; | ||
const inputId = `${treeId}-${value}`; | ||
@@ -146,3 +149,3 @@ const nodeClass = classNames({ | ||
'rct-node-leaf': !this.hasChildren(), | ||
}); | ||
}, className); | ||
@@ -158,5 +161,7 @@ return ( | ||
</span> | ||
<span className="rct-node-icon"> | ||
{this.renderNodeIcon()} | ||
</span> | ||
{showNodeIcon ? ( | ||
<span className="rct-node-icon"> | ||
{this.renderNodeIcon()} | ||
</span> | ||
) : null} | ||
<span className="rct-title"> | ||
@@ -163,0 +168,0 @@ {label} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1433
115
121966
5
22
+ Addedlodash@^4.17.4
+ Addedlodash@4.17.21(transitive)