Javascript 为什么我的过滤逻辑不能像预期的那样用于预测性搜索?

Javascript 为什么我的过滤逻辑不能像预期的那样用于预测性搜索?,javascript,reactjs,Javascript,Reactjs,我正在创建一个工具,该工具从.json文件中提取属性值,并根据人们的属性值估计我所在地区的潜在税收覆盖的影响。他们应该能够通过街道地址、地块ID或所有者姓名搜索他们的财产。当他们输入时,应用程序会动态地建议潜在的匹配 我对名称字段的过滤逻辑有问题。数据的存储不一致,例如,一些记录的格式为“LEV SAMUEL T”(最后一个中间位置),其他记录的格式仅为“SAMUEL LEV”(最后一个)。我需要人们能够找到他们的财产,无论他们以什么顺序输入他们的名字,所以我使用.split(“”)在有空格时打

我正在创建一个工具,该工具从.json文件中提取属性值,并根据人们的属性值估计我所在地区的潜在税收覆盖的影响。他们应该能够通过街道地址、地块ID或所有者姓名搜索他们的财产。当他们输入时,应用程序会动态地建议潜在的匹配

我对名称字段的过滤逻辑有问题。数据的存储不一致,例如,一些记录的格式为“LEV SAMUEL T”(最后一个中间位置),其他记录的格式仅为“SAMUEL LEV”(最后一个)。我需要人们能够找到他们的财产,无论他们以什么顺序输入他们的名字,所以我使用.split(“”)在有空格时打破标准

它正在工作,但是,在输入完整名称之前,我不会得到结果

例如:记录是“NARKEWICZ DAVID”。如果我搜索“NARKEWICZ”,记录就会显示出来。但是,如果我搜索“NARK”(没有完整填写全名),则不会显示任何内容。我希望记录能显示出来,即使只是部分匹配

其他字段没有这个问题,例如“FLORENCE ROAD”将显示记录,即使我只输入“FLORE”

下面是整个app.js代码:

"use strict";

const e = React.createElement;

//Create CSS styles as JS objects
const containerStyles = {
  width: "100%",
  display: "flex",
  flexDirection: "column",
  justifyContent: "flex-start",
  alignItems: "stretch"
};

const headerStyles = {
  width: "100%",
  display: "flex",
  flexDirection: "column",
  justifyContent: "flex-start",
  alignItems: "center",
  alignSelf: "center"
};

const headerTextStyles = {
  margin: 0
};

const inputStyles = {
  width: "50%",
  borderRadius: "3px",
  padding: "20px",
  height: "15px",
  alignSelf: "center"
};

const dataContainerStyles = {
  width: "100%",
  display: "flex",
  flexDirection: "column",
  justifyContent: "flex-start",
  alignItems: "center",
  marginTop: "20px"
};

const listContainer = {
  position: "relative",
  zIndex: 99
};

const listStyles = {
  width: "100%",
  overflow: "hidden",
  position: "absolute",
  top: 0,
  transform: "translateX(-50%)",
  left: "50%",
  alignSelf: "center",
  boxShadow: "0px 8px 16px 0px rgba(0,0,0,0.2)",
  maxHeight: "400px",
  zIndex: 99,
  backgroundColor: "#fff"
};

const listItemStyles = {
  textAlign: "center",
  color: "white",
  fontWeight: "600",
  padding: "10px"
};

const rowStyles = {
  width: "40%",
  display: "flex",
  flexDirection: "row",
  justifyContent: "space-between",
  alignItems: "center",
  alignSelf: "center"
};
function debounce(func, wait, immediate) {
  var timeout;
  return function() {
    var context = this,
      args = arguments;
    var later = function() {
      timeout = null;
      if (!immediate) func.apply(context, args);
    };
    var callNow = immediate && !timeout;
    clearTimeout(timeout);
    timeout = setTimeout(later, wait);
    if (callNow) func.apply(context, args);
  };
}

class App extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      selectedAddress: null,
      items: [],
      searchVal: "",
      data: []
    };

    this.handleSearch = this.handleSearch.bind(this);
    this.selectItem = this.selectItem.bind(this);
  }

  componentDidMount() {
    fetch("./values.json")
      .then(response => response.json())
      .then(data => {
        console.log("data", data);
        this.setState({ data });
      })
      .catch(error => {
        console.error(error);
      });
  }

  selectItem(item) {
    this.setState({
      selectedAddress: item,
      items: [],
      searchVal: ""
    });
  }

  highlightString(q, string) {
    return String(string).replace(
      q.toUpperCase(),
      `<span style="font-weight: bold;">${q.toUpperCase()}</span>`
    );
  }

  handleSearch(search) {
    this.setState({ searchVal: search });
    this.filterData(search);
  }

  filterData = debounce(search => {
    let items = [];
    if (search) {
      const q = search.toLowerCase();

      const ownerNameFilter = item => {
        const ownerName1 = item.OWNER1.toLowerCase().split(" ");
        const ownerName2 = item.OWNER2.toLowerCase().split(" ");
        const fullName = 
              item.OWNER1.toLowerCase() + " " + item.OWNER2.toLowerCase();
        //return [ownerName1, ownerName2];
        const fullNameDelim =fullName.toLowerCase().split(" ");
        return [fullNameDelim];

      };

      const addressFilter = item => {
        const addressNum = item.ADDR_NUM.toLowerCase();
        const fullAddress =
          item.ADDR_NUM.toLowerCase() + " " + item.FULL_STR.toLowerCase();
        const streetName = item.FULL_STR.toLowerCase();

        const fullAddressFiltered = fullAddress.toLowerCase().split(" ");

        let abbreviations = "";

        if (fullAddressFiltered.includes("rd")) {
          const abbreviationIndex = fullAddressFiltered.indexOf("rd");

          fullAddressFiltered[abbreviationIndex] = "road";
          abbreviations = fullAddressFiltered.join(" ");
        }

        if (fullAddressFiltered.includes("st")) {
          const abbreviationIndex = fullAddressFiltered.indexOf("st");

          fullAddressFiltered[abbreviationIndex] = "street";
          abbreviations = fullAddressFiltered.join(" ");
        }

        if (fullAddressFiltered.includes("ave")) {
          const abbreviationIndex = fullAddressFiltered.indexOf("ave");

          fullAddressFiltered[abbreviationIndex] = "avenue";
          abbreviations = fullAddressFiltered.join(" ");
        }

        return [streetName, addressNum, fullAddress, abbreviations];
      };

      items = this.state.data.reduce((acc, item) => {
        const searchProps = [
          ...addressFilter(item),
          ...ownerNameFilter(item),

          String(item["PROP ID"]).toLowerCase()
        ];

        for (let prop of searchProps) {
          if (!Array.isArray(prop)) {
            if (prop.includes(q)) {
              let markup = `
                             <span class="autocompleteAddress">${this.highlightString(
                               q,
                               item.ADDR_NUM
                             )} ${this.highlightString(q, item.FULL_STR)}</span>
                             <span class="autocompleteOwner">${this.highlightString(
                               q,
                               item.OWNER1
                             )} ${this.highlightString(
                q,
                item.OWNER2
              )} <span style="display: block;">${this.highlightString(
                q,
                item["PROP ID"] || ""
              )}</span></span>
                        `;

              item.markup = markup;
              acc.push(item);
              break;
            }
          } else {
            const searchTerms = search.split(" ");

            let doesMatch = false;

            if (searchTerms.every(term => prop.includes(term))) {
              doesMatch = true;
            }

            if (doesMatch) {
              let markup = `
                             <span class="autocompleteAddress">${this.highlightString(
                               q,
                               item.ADDR_NUM
                             )} ${this.highlightString(q, item.FULL_STR)}</span>
                             <span class="autocompleteOwner">${this.highlightString(
                               q,
                               item.OWNER1
                             )} ${this.highlightString(
                q,
                item.OWNER2
              )} <span style="display: block;">${this.highlightString(
                q,
                item["PROP ID"] || ""
              )}</span></span>
                        `;

              item.markup = markup;
              acc.push(item);
              break;
            }
          }
        }

        return acc;
      }, []);
    }
    this.setState({
      items: items
    });
  }, 500);

  formatNumber(num) {
    return new Intl.NumberFormat("en-US", {
      style: "currency",
      currency: "USD"
    }).format(num);
  }

  render() {
    return (
      <div id="container" style={containerStyles}>
        <div id="title" style={headerStyles}>
          <img className="logo" src="logo.png" />
          <p style={headerTextStyles}> City of Northampton</p>
          <p style={headerTextStyles}>Tax Override Calculator</p>
        </div>

        <input
          value={this.state.searchVal}
          onChange={e => this.handleSearch(e.target.value)}
          style={inputStyles}
          placeholder="Enter an address or parcel ID..."
        />

        {this.state.items.length !== 0 && (
          <div style={listContainer}>
            <div style={listStyles}>
              {this.state.items.map((item, index) => (
                <p
                  dangerouslySetInnerHTML={{ __html: item.markup }}
                  className="autocomplete-hover"
                  key={item["PROP ID"] || index}
                  onClick={() => this.selectItem(item)}
                  style={listItemStyles}
                />
              ))}
            </div>
          </div>
        )}

        <div style={dataContainerStyles}>
          <div style={rowStyles}>
            <p className="label"> Parcel ID:</p>
            {this.state.selectedAddress ? (
              <p>{`${this.state.selectedAddress["PROP ID"]}`}</p>
            ) : (
              <p>None Selected</p>
            )}
          </div>

          <div style={rowStyles}>
            <p className="label"> Address:</p>
            {this.state.selectedAddress ? (
              <p>{`${this.state.selectedAddress["ADDR_NUM"]} ${
                this.state.selectedAddress["FULL_STR"]
              }`}</p>
            ) : (
              <p>None Selected</p>
            )}
          </div>

          <div style={rowStyles}>
            <p className="label">Owner:</p>
            {this.state.selectedAddress ? (
              <p>{`${this.state.selectedAddress["OWNER1"]} ${
                this.state.selectedAddress["OWNER2"]
              }`}</p>
            ) : (
              <p>None Selected</p>
            )}
          </div>

          <div style={rowStyles}>
            <p className="label">2020 Assessed Value:</p>
            {this.state.selectedAddress ? (
              <p>{`${this.formatNumber(
                parseFloat(this.state.selectedAddress["TOTAL VAL"])
              )}`}</p>
            ) : (
              <p>None Selected</p>
            )}
          </div>

          <div style={rowStyles}>
            <p className="label">Total Annual Impact:</p>
            {this.state.selectedAddress ? (
              <p>{`${this.formatNumber(
                (parseFloat(
                  this.state.selectedAddress["TOTAL VAL"].replace(/,/g, "")
                ) /
                  1000) *
                  0.67
              )}`}</p>
            ) : (
              <p>None Selected</p>
            )}
          </div>
            <div style={rowStyles}>
            <p className="label">Total Quarterly Impact:</p>
            {this.state.selectedAddress ? (
              <p>{`${this.formatNumber(
                (parseFloat(
                  this.state.selectedAddress["TOTAL VAL"].replace(/,/g, "")
                ) /
                  4000) *
                  0.67
              )}`}</p>
            ) : (
              <p>None Selected</p>
            )}
          </div>
        </div>
      </div>
    );
  }
}

const domContainer = document.querySelector("#root");
ReactDOM.render(<App />, domContainer);
“严格使用”;
const e=React.createElement;
//将CSS样式创建为JS对象
常量集装箱样式={
宽度:“100%”,
显示:“flex”,
flexDirection:“列”,
justifyContent:“灵活启动”,
对齐项目:“拉伸”
};
const headerStyles={
宽度:“100%”,
显示:“flex”,
flexDirection:“列”,
justifyContent:“灵活启动”,
对齐项目:“中心”,
自我定位:“中心”
};
常量headerTextStyles={
保证金:0
};
常量输入样式={
宽度:“50%”,
边界半径:“3px”,
填充:“20px”,
高度:“15px”,
自我定位:“中心”
};
常量dataContainerStyles={
宽度:“100%”,
显示:“flex”,
flexDirection:“列”,
justifyContent:“灵活启动”,
对齐项目:“中心”,
玛金托普:“20px”
};
常量listContainer={
职位:“相对”,
zIndex:99
};
常量列表样式={
宽度:“100%”,
溢出:“隐藏”,
位置:“绝对”,
排名:0,
转换:“translateX(-50%)”,
左:“50%”,
对准自己:“居中”,
盒子阴影:“0px 8px 16px 0px rgba(0,0,0,0.2)”,
最大高度:“400px”,
zIndex:99,
背景颜色:“fff”
};
常量listItemStyles={
textAlign:“居中”,
颜色:“白色”,
重量:“600”,
填充:“10px”
};
常量行样式={
宽度:“40%”,
显示:“flex”,
flexDirection:“行”,
辩护内容:“间隔空间”,
对齐项目:“中心”,
自我定位:“中心”
};
函数解盎司(函数、等待、立即){
var超时;
返回函数(){
var context=this,
args=参数;
var later=function(){
超时=空;
如果(!immediate)函数应用(上下文,参数);
};
var callNow=立即&&!超时;
clearTimeout(超时);
超时=设置超时(稍后,等待);
if(callNow)funct.apply(上下文,参数);
};
}
类应用程序扩展了React.Component{
建造师(道具){
超级(道具);
此.state={
selectedAddress:null,
项目:[],
searchVal:“,
数据:[]
};
this.handleSearch=this.handleSearch.bind(this);
this.selectItem=this.selectItem.bind(this);
}
componentDidMount(){
获取(“./values.json”)
.then(response=>response.json())
。然后(数据=>{
控制台日志(“数据”,数据);
this.setState({data});
})
.catch(错误=>{
控制台错误(error);
});
}
选择项目(项目){
这是我的国家({
所选地址:项,
项目:[],
searchVal:“
});
}
highlightString(q,string){
返回字符串(字符串)。替换(
q、 toUpperCase(),
`${q.toUpperCase()}`
);
}
handleSearch(搜索){
this.setState({searchVal:search});
此.filterData(搜索);
}
filterData=debounce(搜索=>{
设项目=[];
如果(搜索){
const q=search.toLowerCase();
const ownerNameFilter=项目=>{
const ownerName1=item.OWNER1.toLowerCase().split(“”);
const ownerName2=item.OWNER2.toLowerCase().split(“”);
常量全名=
item.OWNER1.toLowerCase()+“”+item.OWNER2.toLowerCase();
//返回[ownerName1,ownerName2];
const fullNameDelim=fullName.toLowerCase().split(“”);
返回[fullNameDelim];
};
const addressFilter=项目=>{
const addressNum=item.ADDR_NUM.toLowerCase();
常量完整地址=
item.ADDR_NUM.toLowerCase()+“”+item.FULL_STR.toLowerCase();
const streetName=item.FULL_STR.toLowerCase();
const fullAddressFiltered=fullAddress.toLowerCase().split(“”);
让缩写为“”;
if(fullAddressFiltered.includes(“rd”)){
const abbreviationIndex=fullAddressFiltered.indexOf(“rd”);
fullAddressFiltered[abbreviationIndex]=“道路”;
缩写=fullAddressFiltered.join(“”);
}
if(fullAddressFiltered.includes(“st”)){
const abbreviationIndex=fullAddressFiltered.indexOf(“st”);
fullAddressFiltered[abbreviationIndex]=“street”;
缩写=fullAddressFiltered.join(“”);
}
if(fullAddressFiltered.includes(“ave”)){
const abbreviationIndex=fullAddressFiltered.indexOf(“ave”);
fullAddressFiltered[abbreviationIndex]=“avenue”;
缩写=fullAddressFiltered.join(“”);
}
return[街道名称、地址编号、完整地址、缩写];
};
items=此.state.data.reduce((acc,item)=>{
const searchProps=[
…地址过滤器(项目),
…所有者名称过滤器(项目),
字符串(项目[“属性ID]”)。toLowerCase()
];
for(让搜索道具中的道具){
如果(!Array.isArray(prop)){
如果(项目包括(q)){
让标记=