Alex Curtis created react-treebeard a data driven tree view for React. I needed to enable users to filter the tree's nodes based on input text. To accomplish this I transform the tree's data using 2 functions when the filter changes and rerender the view:
filterTree
Given a tree return a new tree keeping only the nodes that meet one of the following conditions:- Has matching text
- Has a parent with matching text
- Has a child with matching text
expandNodesWithMatchingDescendants
- Given a tree mark all the nodes with matching descendants as toggled
Here are the aformentioned functions.
var defaultMatcher = (filterText, node) => {
return node.name.toLowerCase().indexOf(filterText.toLowerCase()) !== -1
}
var nodeMatchesOrHasMatchingDescendants = (node, filter, matcher) => {
return matcher(filter, node) || //i match
(node.children && //or i have decendents and one of them match
node.children.length &&
!!node.children.find(childNode => nodeMatchesOrHasMatchingDescendants(childNode, filter, matcher)))
}
var filterTree = (node, filter, matcher=defaultMatcher) => {
if(matcher(filter, node)){ //if im an exact match then all my children get to stay
return node
}
else { //if not then only keep the ones that match or have matching descendants
var filteredChildren
if(node.children) {
filteredChildren = node.children.filter(child => nodeMatchesOrHasMatchingDescendants(child, filter, matcher))
}
if(filteredChildren && filteredChildren.length){
filteredChildren = filteredChildren.map(child => filterTree(child, filter, matcher))
}
return Object.assign({}, node, {
children: filteredChildren
});
}
}
var expandNodesWithMatchingDescendants = (node, filter, matcher=defaultMatcher) => {
var children = node.children
var shouldExpand = false
if(children && children.length){
var childrenWithMatches = node.children.filter(child => nodeMatchesOrHasMatchingDescendants(child, filter, matcher))
shouldExpand = !!childrenWithMatches.length //I expand if any of my kids match
if(shouldExpand) {//if im going to expand
//go through all the matches and see if thier children need to expand
children = childrenWithMatches.map(child => expandNodesWithMatchingDescendants(child, filter, matcher))
}
}
return Object.assign({}, node, {children: children, toggled: shouldExpand})
}
Put the beard and filter together by rendering the input box and the tree. Filter the data as the user types (onKeyUp
).
render() {
return (
<div>
<input onKeyUp={this.handleFilterMouseUp.bind(this)} />
<Treebeard data={this.state.data} />
</div>
)
}
handleFilterMouseUp(e){
const filter = e.target.value.trim()
if(filter){
var data = filterTree(unfilteredTreeData, filter)
data = expandNodesWithMatchingDescendants(data, filter)
this.setState({data})
}
else {
this.setState({data: unfilteredTreeData})
}
}
I modded the example that ships with react-treebeard. The changes can be seen in my fork.
Thank you, it helps me save a lot of time. 😄