React - 搜索输入未正确过滤

时间:2018-04-30 17:06:52

标签: javascript reactjs

我在这里开发了一个搜索输入: https://codesandbox.io/s/k9448q2v87 有效,但不正确。

输入可以在主文件夹 (名为['。'])和子文件夹(/ bin,/ lib,/ spec),但找不到这些子文件夹中的文件主文件夹

例如,如果您尝试搜索 01_greeting_spec.rb greeting.rb ,搜索将无法正常过滤。

但是,如果你搜索像 / bin 这样的文件夹,输入会过滤文件夹和里面的所有文件。

那么,缺少什么?

考虑附加的Stack Snippet来解决这个问题。

class TextBox extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      content: "Select A Node To See Its Data Structure Here..."
    };
    this.changeContent = this.changeContent.bind(this);
  }

  changeContent(newContent) {
    this.setState({
      content: newContent
    });
  }

  componentWillReceiveProps(nextProps) {
    this.setState({
      content: nextProps.content
    });
  }

  render() {
    return (
      <div className="padd_top">
        <div className="content_box">{this.state.content}</div>
      </div>
    );
  }
}

class SearchEngine extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: ""
    };
    this.inputChange = this.inputChange.bind(this);
  }

  inputChange(e) {
    const content = e.target.value;
    this.props.onChange(content);
  }

  render() {
    return (
      <input className="form-control" placeholder="Search the tree..." onChange={this.inputChange} />
    );
  }
}

let data = [
  {
    type: "directory",
    name: ".",
    contents: [
      {
        type: "directory",
        name: "./bin",
        contents: [{ type: "file", name: "./bin/greet" }]
      },
      {
        type: "directory",
        name: "./lib",
        contents: [{ type: "file", name: "./lib/greeting.rb" }]
      },
      {
        type: "directory",
        name: "./spec",
        contents: [
          { type: "file", name: "./spec/01_greeting_spec.rb" },
          { type: "file", name: "./spec/02_cli_spec.rb" },
          { type: "file", name: "./spec/spec_helper.rb" }
        ]
      },
      { type: "file", name: "./CONTRIBUTING.md" },
      { type: "file", name: "./Gemfile" },
      { type: "file", name: "./Gemfile.lock" },
      { type: "file", name: "./LICENSE.md" },
      { type: "file", name: "./README.md" }
    ]
  }
];

// Icon file image for 'FileTree'
const FileIcon = () => {
  return (
    <div className="svg-icon">
      <svg
        id="icon-file-text2"
        className="icon"
        viewBox="0 0 32 32"
        fill="currentColor"
        width="1em"
        height="1em"
      >
        <path d="M28.681 7.159c-0.694-0.947-1.662-2.053-2.724-3.116s-2.169-2.030-3.116-2.724c-1.612-1.182-2.393-1.319-2.841-1.319h-15.5c-1.378 0-2.5 1.121-2.5 2.5v27c0 1.378 1.122 2.5 2.5 2.5h23c1.378 0 2.5-1.122 2.5-2.5v-19.5c0-0.448-0.137-1.23-1.319-2.841zM24.543 5.457c0.959 0.959 1.712 1.825 2.268 2.543h-4.811v-4.811c0.718 0.556 1.584 1.309 2.543 2.268zM28 29.5c0 0.271-0.229 0.5-0.5 0.5h-23c-0.271 0-0.5-0.229-0.5-0.5v-27c0-0.271 0.229-0.5 0.5-0.5 0 0 15.499-0 15.5 0v7c0 0.552 0.448 1 1 1h7v19.5z" />
        <path d="M23 26h-14c-0.552 0-1-0.448-1-1s0.448-1 1-1h14c0.552 0 1 0.448 1 1s-0.448 1-1 1z" />
        <path d="M23 22h-14c-0.552 0-1-0.448-1-1s0.448-1 1-1h14c0.552 0 1 0.448 1 1s-0.448 1-1 1z" />
        <path d="M23 18h-14c-0.552 0-1-0.448-1-1s0.448-1 1-1h14c0.552 0 1 0.448 1 1s-0.448 1-1 1z" />
      </svg>
    </div>
  );
};

// Icon folder image for 'FileTree'
 const FolderIcon = () => {
  return (
    <div className="svg-icon">
      <svg
        id="icon-folder"
        className="icon"
        viewBox="0 0 32 32"
        fill="currentColor"
        height="1em"
        width="1em"
      >
        <path d="M14 4l4 4h14v22h-32v-26z" />
      </svg>
    </div>
  );
};

// Icon arrow image for 'FileTree'
 const TriangleDown = () => {
  return (
    <div className="svg-icon">
      <svg
        id="svg__icon--triangle-down"
        viewBox="0 0 9 4.5"
        fill="currentColor"
        height="1em"
        width="1em"
      >
        <path d="M0,0,4.5,4.5,9,0Z" />
      </svg>
    </div>
  );
};

// Filters file 'name' and adds '/'
const formatName = name => {
  return name.substr(name.lastIndexOf("/") + 1);
};

// Dummy data set
var root = data[0];

// Construction of FileTree
 class FileTree extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      activeNode: null
    };
    this.setActiveNode = this.setActiveNode.bind(this);
  }

  componentWillReceiveProps({ searchTerm }) {
    this.setState({ searchTerm });
  }

  setActiveNode(name) {
    this.setState({ activeNode: name });
    this.props.liftStateUp(name);
  }

  render() {
    return (
      <div className="padd_top">
        {renderTree(
          this.props.root || root,
          this.setActiveNode,
          this.state.activeNode,
          null,
          this.state.searchTerm
        )}
      </div>
    );
  }
}

class Directory extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      expanded: true,
    };
    this.toggleDirectory = this.toggleDirectory.bind(this);
  }

  toggleDirectory() {
    this.setState({ expanded: !this.state.expanded });
  }

  hasMatchingNodes() {
    const filteredNodes = this.props.node.contents.filter(
      (node) => {
      return (
        node.name.toLowerCase().indexOf(this.props.searchTerm.toLowerCase()) >
        -1
      );
    });

    console.log(this.props.searchTerm);
    return filteredNodes.length > 0;
  }
  render() {
    let node = this.props.node;

    if (this.props.searchTerm && !this.hasMatchingNodes()) return null;
    return (
      <div className="directory-container">
        <div className="directory">
          <div
            className=
            {`directory__toggle ${ this.state.expanded ? "expanded" : ""}`}
          >
            <div onClick={this.toggleDirectory}>
              <TriangleDown />
            </div>
          </div>

          <div className="directory__icon" onClick={this.toggleDirectory}>
            <FolderIcon />
          </div>

          <div className="directory__name" onClick={this.toggleDirectory}>
            <div>{formatName(node.name)}</div>
          </div>
        </div>
        {this.state.expanded
          ? node.contents.map((content, index) =>
              renderTree(
                content,
                this.props.setActiveNode,
                this.props.activeNode,
                index,
                this.props.searchTerm
              )
            )
          : ""}
      </div>
    );
  }
}

// Set class Active do selected file
const File = ({ name, setActiveNode, activeNode, searchTerm }) => {
  if (searchTerm && name.toLowerCase().indexOf(searchTerm.toLowerCase()) < 0)
    return null;
  let isActive = activeNode === name;
  let className = isActive ? "active" : "";

  return (
    <div className={className + " file"} onClick={() => setActiveNode(name)}>
      <div className="file__icon">
        <FileIcon />
      </div>
      <div className="file__name">{formatName(name)}</div>
      {isActive && <div className="file__options">...</div>}
    </div>
  );
};

var renderTree = (node, setActiveNode, activeNode, index, searchTerm) => {
  if (node.type === "file") {
    return (
      <File
        key={index}
        name={node.name}
        setActiveNode={setActiveNode}
        activeNode={activeNode}
        searchTerm={searchTerm}
      />
    );
  } else if (node.type === "directory") {
    return (
      <Directory
        key={index}
        node={node}
        setActiveNode={setActiveNode}
        activeNode={activeNode}
        searchTerm={searchTerm}
      />
    );
  } else {
    return null;
  }
};

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      activeNode: ""
    };

    this.onChange = this.onChange.bind(this);
  }

  liftStateUp = (data) => {
    this.setState({ activeNode: data });
  };

  onChange(data) {
    this.setState({ searchTerm: data });
  }
  render() {
    return (
      <div>
        <div className="col-md-12">
          <SearchEngine className="form-control" onChange={this.onChange} />
        </div>
        <div className="col-md-6">
          <FileTree
            liftStateUp={this.liftStateUp}
            searchTerm={this.state.searchTerm}
          />
        </div>
        <div className="col-md-6">
          <TextBox content={this.state.activeNode} />
        </div>
      </div>
    );
  }
}

ReactDOM.render(<App />, document.getElementById("app"));
* {
  font-family: Helvetica;
  background-color: #212830;
  color: #9ea1b2 !important;
}

.directory {
  padding-left: 10px;
  padding-top: 1px;
  padding-bottom: 1px;
  display: flex;
  flex-direction: row;
  align-items: center;
}

.directory__toggle {
  padding-left: 10px;
  transform: rotate(-90deg);
}

.directory__icon {
  padding-left: 10px;
}

.directory__icon {
  padding-left: 10px;
}

.directory__name {
  padding-left: 10px;
}

.directory-container {
  padding-left: 10px;
}

.file {
  padding-left: 50px;
  padding-top: 1px;
  padding-bottom: 1px;
  display: flex;
}

.file__icon {
  padding-left: 10px;
}

.file__name {
  padding-left: 10px;
}

.file__options {
  align-self: flex-end;
}

.active {
  color: #ffffff;
  background-color: #31343f;
}

.icon {
  display: inline-block;
  width: 1em;
  height: 1em;
  stroke-width: 0;
  stroke: currentColor;
  fill: currentColor;
}

.svg-icon {
  width: 1em;
  height: 1em;
}

.expanded {
  transform: rotate(0deg);
}

/** CONTENT BOX **/

.padd_top {
  padding-top: 20px;
}

.content_box {
  font-size: 12px;
  white-space: pre-wrap;
  background-color: #282c34;
  border: solid 1px black;
  padding: 20px;
  color: #9da5ab;
  min-height: 250px;
  margin-bottom: 15px;
}

.text_color {
  color: #21252b !important;
}

/** SEARCH **/

.form-control {
  margin-top: 15px;
}

/** ARROW ANIMATION **/

.rotate {
  animation: rotate-keyframes 1s;
}

@keyframes rotate-keyframes {
  from {
    transform: rotate(-90deg);
  }

  to {
    transform: rotate(0deg);
  }
}
<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
	<meta name="theme-color" content="#000000">
	<!--
      manifest.json provides metadata used when your web app is added to the
      homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
    -->
	<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
	<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
	<!--
      Notice the use of %PUBLIC_URL% in the tags above.
      It will be replaced with the URL of the `public` folder during the build.
      Only files inside the `public` folder can be referenced from the HTML.

      Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
      work correctly both with client-side routing and a non-root public URL.
      Learn how to configure a non-root public URL by running `npm run build`.
    -->
	<title>React App</title>
</head>

<body>
	<noscript>
		You need to enable JavaScript to run this app.
	</noscript>
	<div id="app"></div>
	<!--
      This HTML file is a template.
      If you open it directly in the browser, you will see an empty page.

      You can add webfonts, meta tags, or analytics to this file.
      The build step will place the bundled scripts into the <body> tag.

      To begin the development, run `npm start` or `yarn start`.
      To create a production bundle, use `npm run build` or `yarn build`.
    -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
</body>

</html>

感谢您的帮助。

1 个答案:

答案 0 :(得分:2)

您的useFactory: (HttpLoaderFactory) 方法不会递归检查子目录。它仅检查作为根目录hasMatchingNodes

的直接子项的文件和文件夹的名称

您应该测试文件节点的名称,如果节点是目录,则以递归方式调用匹配函数,如果节点是文件,则将搜索项与节点名称进行比较。

.

在此处查看更新的应用:https://codesandbox.io/s/245nv8n3j