Reputation: 1205
I'm using a Searchable Tree component in Ant Design. I would like to filter treeNodes on search. So that if a search value is found, a treeNode is shown. If not, it is hidden (i. e. filtered).
There is a filterTreeNode
prop in API. Is it possible to filter treeNodes (hide unrelevant treeNodes from search) with this prop? If not, how can achieve the desired result?
Here is my code for filterTreeNode
function:
const filterTreeNode = (node) => {
const title = node.title.props.children[2];
const result = node.key.indexOf(searchValue) !== -1 ? true : false
console.log(searchValue);
console.log(result);
return result;
};
I can see that the result (true or false) is logged in the console, but with the Tree itself nothing happens.
Here is a link to codesandbox and the full code for the component:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import { Tree, Input } from "antd";
import gData from "./gData.js";
const { Search } = Input;
const dataList = [];
const generateList = (data) => {
for (let i = 0; i < data.length; i++) {
const node = data[i];
const { key } = node;
dataList.push({ key, title: key });
if (node.children) {
generateList(node.children);
}
}
};
generateList(gData);
const getParentKey = (key, tree) => {
let parentKey;
for (let i = 0; i < tree.length; i++) {
const node = tree[i];
if (node.children) {
if (node.children.some((item) => item.key === key)) {
parentKey = node.key;
} else if (getParentKey(key, node.children)) {
parentKey = getParentKey(key, node.children);
}
}
}
return parentKey;
};
const SearchTree = () => {
const [expandedKeys, setExpandedKeys] = useState([]);
const [autoExpandParent, setAutoExpandParent] = useState(true);
const [searchValue, setSearchValue] = useState("");
const onExpand = (expandedKeys) => {
setExpandedKeys(expandedKeys);
setAutoExpandParent(false);
};
const onChange = (e) => {
const { value } = e.target;
const expandedKeys = dataList
.map((item) => {
if (item.title.indexOf(value) > -1) {
return getParentKey(item.key, gData);
}
return null;
})
.filter((item, i, self) => item && self.indexOf(item) === i);
if (value) {
setExpandedKeys(expandedKeys);
setSearchValue(value);
setAutoExpandParent(true);
} else {
setExpandedKeys([]);
setSearchValue("");
setAutoExpandParent(false);
}
};
const filterTreeNode = (node) => {
const title = node.title.props.children[2];
const result = title.indexOf(searchValue) !== -1 ? true : false;
console.log(searchValue);
console.log(result);
return result;
};
const loop = (data) =>
data.map((item) => {
const index = item.title.indexOf(searchValue);
const beforeStr = item.title.substr(0, index);
const afterStr = item.title.substr(index + searchValue.length);
const title =
index > -1 ? (
<span>
{beforeStr}
<span className="site-tree-search-value">{searchValue}</span>
{afterStr}
</span>
) : (
<span>{item.title}</span>
);
if (item.children) {
return { title, key: item.key, children: loop(item.children) };
}
return {
title,
key: item.key
};
});
return (
<div>
<Search
style={{ marginBottom: 8 }}
placeholder="Search"
onChange={onChange}
/>
<Tree
onExpand={onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
treeData={loop(gData)}
filterTreeNode={filterTreeNode}
/>
</div>
);
};
ReactDOM.render(<SearchTree />, document.getElementById("container"));
Upvotes: 4
Views: 11666
Reputation: 111
Couple weeks ago, i had to meet same like this client's enhancement. So how i did it was, we had Search over DirectoryTree and inside tree we rendered the nodes with a function
<DirectoryTree
onExpand={handleOperatorExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
onSelect={handleOperatorSelect}
>
{renderTreeNodes(mtoCharts)}
</DirectoryTree>
In search onChange event we saved values to a state, like searchValue. Then searchValue was checked in renderTreeNodes function with regex:
let dynamicRegex = `.*(${searchValue.toUpperCase()}){1}.*`;
if
item.name.match(dynamicRegex)
then we display the node otherwise we dont. SIMPLE !
UPDATE : codes
<Search onChange={handleOperatorChange} />
const handleOperatorChange = e => {
const { value } = e.target;
let temp = value.replace(/[\.,-\/#!$%\^&\*;:{}=\-_`~()"'+@<>?\\\[\]]/g, '');
setSearchValue(temp);
setAutoExpandParent(true);};
and finally renderTreeNodes :
const renderTreeNodes = data =>
data &&
data instanceof Array &&
data.map(item => {
const title = (
<Highlighter
highlightStyle={{ color: '#f50' }}
searchWords={[searchValue]}
autoEscape={true}
textToHighlight={item.name}
/>
);
if (searchValue) {
let dynamicRegex = `.*(${searchValue.toUpperCase()}){1}.*`;
if (!isEmpty(item.children)) {
// let doesChildrenHaveMatchingName = false;
// item.children.forEach(itemChild => {
// if (itemChild.name.match(dynamicRegex)) {
// doesChildrenHaveMatchingName = true;
// }
// });
// if (doesChildrenHaveMatchingName) {
return (
<TreeNode
className={!item.active ? 'dim-category' : ''}
key={item.code}
title={title}
id={item.id}
code={item.code}
level={item.level}
parentId={item.parentId}
parentReference={item.path}
icon={({ expanded }) => (
<Icon type={expanded ? 'folder-open' : 'folder'} style={{ color: '#32327f' }} />
)}
>
{renderTreeNodes(item.children)}
</TreeNode>
);
// }
}
if (item.name.match(dynamicRegex) || item.path.match(dynamicRegex)) {
return (
<TreeNode
className={item.active ? 'false' : 'dim-category'}
key={item.code}
title={!item.leaf ? title : getMtoTitle(item, title)}
{...(item.leaf ? { leaf: item.leaf } : {})}
id={item.id}
code={item.code}
level={item.level}
parentId={item.parentId}
parentReference={item.path}
icon={({ expanded }) => {
return item.leaf ? (
<Icon type="money-collect" style={{ color: '#47bfa5' }} />
) : (
<Icon type={expanded ? 'folder-open' : 'folder'} style={{ color: '#32327f' }} />
);
}}
/>
);
}
} else {
if (!isEmpty(item.children)) {
return (
<TreeNode
className={!item.active ? 'dim-category' : ''}
key={item.code}
title={title}
id={item.id}
code={item.code}
level={item.level}
parentId={item.parentId}
parentReference={item.path}
icon={({ expanded }) => (
<Icon type={expanded ? 'folder-open' : 'folder'} style={{ color: '#32327f' }} />
)}
>
{renderTreeNodes(item.children)}
</TreeNode>
);
}
return (
<TreeNode
className={item.active ? 'false' : 'dim-category'}
key={item.code}
title={!item.leaf ? title : getMtoTitle(item, title)}
{...(item.leaf ? { leaf: item.leaf } : {})}
id={item.id}
code={item.code}
level={item.level}
parentId={item.parentId}
parentReference={item.path}
icon={({ expanded }) => {
return item.leaf ? (
<Icon type="money-collect" style={{ color: '#47bfa5' }} />
) : (
<Icon type={expanded ? 'folder-open' : 'folder'} style={{ color: '#32327f' }} />
);
}}
/>
);
}
});
Upvotes: 2
Reputation: 26075
As mentioned in the API document, filterTreeNode
will highlight tree node, and will not hide it.
filterTreeNode
Defines a function to filter (highlight) treeNodes. When the function returns true, the corresponding treeNode will be highlighted
If you want to hide tree node, you will have to manually filter it first before before passing it to Tree
in loop function, something like:
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import "./index.css";
import { Tree, Input } from "antd";
import gData from "./gData.js";
const { Search } = Input;
const dataList = [];
const generateList = (data) => {
for (let i = 0; i < data.length; i++) {
const node = data[i];
const { key } = node;
dataList.push({ key, title: key });
if (node.children) {
generateList(node.children);
}
}
};
generateList(gData);
const getParentKey = (key, tree) => {
let parentKey;
for (let i = 0; i < tree.length; i++) {
const node = tree[i];
if (node.children) {
if (node.children.some((item) => item.key === key)) {
parentKey = node.key;
} else if (getParentKey(key, node.children)) {
parentKey = getParentKey(key, node.children);
}
}
}
return parentKey;
};
const SearchTree = () => {
const [expandedKeys, setExpandedKeys] = useState([]);
const [autoExpandParent, setAutoExpandParent] = useState(true);
const [searchValue, setSearchValue] = useState("");
const [treeData, setTreeData] = useState(gData);
const onExpand = (expandedKeys) => {
setExpandedKeys(expandedKeys);
setAutoExpandParent(false);
};
const onChange = (e) => {
const value = e.target.value?.toLowerCase();
const expandedKeys = dataList
.map((item) => {
if (item.title.indexOf(value) > -1) {
return getParentKey(item.key, gData);
}
return null;
})
.filter((item, i, self) => item && self.indexOf(item) === i);
if (value) {
const hasSearchTerm = (n) => n.toLowerCase().indexOf(value) !== -1;
const filterData = (arr) =>
arr?.filter(
(n) => hasSearchTerm(n.title) || filterData(n.children)?.length > 0
);
const filteredData = filterData(gData).map((n) => {
return {
...n,
children: filterData(n.children)
};
});
setTreeData(filteredData);
setExpandedKeys(expandedKeys);
setSearchValue(value);
setAutoExpandParent(true);
} else {
setTreeData(gData);
setExpandedKeys([]);
setSearchValue("");
setAutoExpandParent(false);
}
};
const filterTreeNode = (node) => {
const title = node.title.props.children[2];
const result = title.indexOf(searchValue) !== -1 ? true : false;
console.log(searchValue);
console.log(result);
return result;
};
const loop = (data) =>
data.map((item) => {
const index = item.title.indexOf(searchValue);
const beforeStr = item.title.substr(0, index);
const afterStr = item.title.substr(index + searchValue.length);
const title =
index > -1 ? (
<span>
{beforeStr}
<span className="site-tree-search-value">{searchValue}</span>
{afterStr}
</span>
) : (
<span>{item.title}</span>
);
if (item.children) {
return { title, key: item.key, children: loop(item.children) };
}
return {
title,
key: item.key
};
});
return (
<div>
<Search
style={{ marginBottom: 8 }}
placeholder="Search"
onChange={onChange}
/>
<Tree
onExpand={onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
treeData={loop(treeData)}
filterTreeNode={filterTreeNode}
/>
</div>
);
};
ReactDOM.render(<SearchTree />, document.getElementById("container"));
Upvotes: 7