A darn good search filter function in JavaScript

12 September 2018   4 comments   Web development, Javascript

https://codesandbox.io/s/62x4mmxr0n

Demo here. The demo uses React and a list of blog post titles that get immediately filtered when you type in a search. I.e. you have the whole list but show less when a search term is entered.

That the demo uses React isn't important. What's important is the search function. It looks like this:

function filterList(q, list) {
  function escapeRegExp(s) {
    return s.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
  }
  const words = q
    .split(/\s+/g)
    .map(s => s.trim())
    .filter(s => !!s);
  const hasTrailingSpace = q.endsWith(" ");
  const searchRegex = new RegExp(
    words
      .map((word, i) => {
        if (i + 1 === words.length && !hasTrailingSpace) {
          // The last word - ok with the word being "startswith"-like
          return `(?=.*\\b${escapeRegExp(word)})`;
        } else {
          // Not the last word - expect the whole word exactly
          return `(?=.*\\b${escapeRegExp(word)}\\b)`;
        }
      })
      .join("") + ".+",
    "gi"
  );
  return list.filter(item => {
    return searchRegex.test(item.title);
  });
}

In action
I use this in a single-page content management app. There's a list of records and a search input. Every character you put into the search bar updates the list of records shown.

What it does is that it allows you to search texts based on multiple whole words. But the key feature is that the last word doesn't have to be whole. For example, it will positively match "This is a blog post about JavaScript" if the search is "post javascript" or "post javasc". But it won't match on "pos blog".

The idea is that if a user has typed in a full word followed by a space, all previous words needs to be matched fully. For example if the input is "java " it won't match on "This is a blog post about JavaScript" because the word java, alone, isn't in the search text.

Sure, there are different ways to write this but I think this functionality is good for this kind of filtering search. A different implementation would have a function that returns the regex and then it can be used both for filtering and for highlighting.

Hope it helps.

Comments

Chetan Bhatt

Hi, I wanted to implement something like this. Trying to understand your code, will really appreciate if you can explain the steps you chose.

Like using trim() after split(). I am not able to understand why did you add it.
Please specify the reason, thanks!

Ankan Adhikari [firehawk895]

here is a modified version that takes an additional parameter attribute_list which allows for matching deep within the object using dot notation

```
export function filterList(q, list, attribute_list) {
  /*
  q query string to search for
  list is the list of objects to search for
  attribute_list is the list of attributes of the object to match on, can use dot object notation
  */

  function escapeRegExp(s) {
    return s.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&";;
  }
  const words = q
    .split(/\s+/g)
    .map(s => s.trim())
    .filter(s => !!s);
  const hasTrailingSpace = q.endsWith(" ");
  const searchRegex = new RegExp(
    words
      .map((word, i) => {
        if (i + 1 === words.length && !hasTrailingSpace) {
          // The last word - ok with the word being "startswith"-like
          return `(?=.*\\b${escapeRegExp(word)})`;
        } else {
          // Not the last word - expect the whole word exactly
          return `(?=.*\\b${escapeRegExp(word)}\\b)`;
        }
      })
      .join("") + ".+",
    "gi"
  );
  return list.filter(item => {
    let result = false;
    attribute_list.forEach(attr => {
      // This beautiful stackoverflow answer converts dot notation into an object reference
      // https://stackoverflow.com/a/6394168/1881812
      // 'a.b.etc'.split('.').reduce((o,i)=>o[i], obj)
      result =
        result ||
        searchRegex.test(attr.split(".").reduce((o, i) => o[i], item));
    });
    return result;
  });
}
```

Neal

Do you have a license on this? Would be great if you could explicitly say if you're ok with this being copied!

Peter Bengtsson

Help yourself! If your code is open source, I think a code comment that is a URL back to this would be fair.

Your email will never ever be published

Related posts

Previous:
Replace an item in an array, by number, without mutation in JavaScript (ES6) 23 August 2018
Next:
Merge two arrays without duplicates in JavaScript 20 September 2018
Related by Keyword:
SongSearch autocomplete rate now 2+ per second 11 July 2019
Whatsdeployed rewritten in React 15 April 2019
How to throttle AND debounce an autocomplete input in React 01 March 2018
How to create-react-app with Docker 17 November 2017
Fastest way to match a filename's extension in Python 31 August 2017