3

I have an array of objects created by constructor function. These objects have more key-value pairs then this example. But that's all working fine, so not relevant. Let's try to keep it concise :)

Example array:

let contactsArr = [
  {id: 1, firstName: "Lucas", email: "lucas@fake.com"},
  {id: 2, firstName: "Adrian", email: "adrian@fake.com"},
  {id: 3, firstName: "Betrand", email: "zorro@fake.com"}
  }
];

In the html, I have a search field #search. You gessed it. This field is intended to search the array of contact objects for any matching values.

The content of this field is trimmed copied to an array of strings, divided by (1 ore more) spaces.

const $input = $("#search").val().toLowerCase().trim().split(/[\s]+/);

No problem there. For next step I wanted to find and return any values of the objects inside contactsArr that are equal to (or contain part of) a string from $input. First version, I came up with this code:

const filteredArr = contactsArr.filter(contact => {
            return contact.firstName.toLowerCase().includes($input) ||
                contact.email.toLowerCase().includes($input) ||
                ... // and more key-value pairs to check
        });

That works fine when $input returns a string, or an array with only 1 string. If the array contains more strings, only results for the first string will be searched and returned. But it's also a bit messy, considering the objects might have a lot more key-value pairs in the future. Hence version 2:

const filteredArr = contactsArr.filter(contact => {
            return Object.values(contact).some(x => {
                if (typeof x === "number") x = x.toString();
                return x.toLowerCase().includes($input);
            });
        });

Version 2 returns exaclty the same result as version 1, only it works for a lot more key-value pairs then the ones listed inside the code. Great!! But when the $input array has more then 1 value, the second value is still ignored. After lots of trail and error, I hope someone can point out my mistake.

This is version 3: (or maybe even 33) :)

const filteredArr = contactsArr.filter(contact => {
            return Object.values(contact).some(x => {
                // contact.id number to string
                if (typeof x === "number") x = x.toString();
                // match with lowercase (same as $input)
                x = x.toLocaleLowerCase();
                // check if includes and return true or false
                return $input.some(word => x.includes(word));
            });
        });

Expected result: The aim is to have all the contacts that match any of the strings in $input.

Many thanks for any and all tips and insights!

TVBZ
  • 564
  • 1
  • 4
  • 15
  • Can you clarify what _"match (part of) a string"_ means? Some examples would be great – Phil Mar 19 '19 at 23:27
  • Thanks @CertainPerformance. I skipped it too fast. Corrected. – TVBZ Mar 19 '19 at 23:28
  • @Phil I have corrected description. I hope it's more clear now? – TVBZ Mar 19 '19 at 23:32
  • Possible duplicate of [Javascript: How to filter object array based on attributes?](https://stackoverflow.com/questions/2722159/javascript-how-to-filter-object-array-based-on-attributes) – evolutionxbox Mar 20 '19 at 00:04

1 Answers1

2

What I've done in the past is create a big index string by joining all the values together. Then you can use Array.prototype.some() on your $input array

let contactsArr = [
  {id: 1, firstName: "Lucas", email: "lucas@fake.com"},
  {id: 2, firstName: "Adrian", email: "adrian@fake.com"},
  {id: 3, firstName: "Betrand", email: "zorro@fake.com"}
];
let searchVal = 'lu rian'
const $input = searchVal.toLowerCase().trim().split(/\s+/);

const filteredArr = contactsArr.filter(contact => {
  // create an index string by joining all the values
  const indexString = Object.values(contact).join(' ').toLowerCase()
  
  // now you can perform a single search operation
  return $input.some(word => indexString.includes(word))
})

console.info(filteredArr)
Phil
  • 157,677
  • 23
  • 242
  • 245
  • Hah. I understand. That is a very interesting aproach, a real eye opener.. :) **Thanks!!** Still, I would like to understand how to do it with .filter() or .reduce(). – TVBZ Mar 19 '19 at 23:35
  • @TVBZ I've added a snippet now (forgot to lower-case the `indexString`). I'm not sure what your comment means as I have definitely used `filter()` – Phil Mar 19 '19 at 23:37
  • I have tried your solution. It makes sence. But for some reason I still have the same result as before. Huh. I 'm a bit lost now. Could you take a look at my [codepen](https://codepen.io/TVBZ/pen/RdLmJp)? The search function starts around rule 260. When searching for _Olson silas_, I would expect the first 2 contacts to return. Many thanks! – TVBZ Mar 20 '19 at 14:02
  • 1
    @TVBZ the issue is in your _other_ logic. You only do anything with the filtered array if `$input.length` is `1`. Fix your `if...else if...` logic, remove the first `else` – Phil Mar 21 '19 at 00:13
  • Aaah.. Yes! I focussed so hard on the sorting logic while the issue was actually in the return logic. :) Many thanks @Phil. Thumbs up! – TVBZ Mar 22 '19 at 08:55