jupiteror
jupiteror

Reputation: 1205

Filter treeNodes in Ant Design Tree

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

Answers (2)

Rikesh Lal Shrestha
Rikesh Lal Shrestha

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

Dipen Shah
Dipen Shah

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

Related Questions