11

I can't figure out how to filter an array with multiple conditions. I have a search filter form with 2 select, 1 checkboxes fieldset and 1 radio button fieldset. I have functions that return items that match chosen conditions. They work only separately. What is the best approach to find objects that match all conditions?

I tried to make if statements for all possible options, but code doesn't work correctly and it looks like there should be some better options to do so.

Here is function examples:

    function chooseRating(hotel) {
      return hotel.rating == e.target.value;
    }

    function chooseMeal(hotel) {
      return hotel.mealType == e.target.value;
    }


    function choosePlace(hotel)  {
      for (let l = 0; l < chosenPlace.length; l++) {
      if(chosenPlace[l].checked) {
        return hotel.region == e.target.value;
      }
              }
    }

How should I filter the array with that?

  let filteredCards = hotels.filter(function(hotel, index, hotels) {
    // ??
  });

Screenshot

User chooses his requirements for hotel and he should get hotels, that match all requirements. And if some of them not chosen, then they are don't count by default.

Tonya
  • 258
  • 1
  • 4
  • 12

5 Answers5

16

You can chain your filter() calls, like this:

// first filter
function filterRating(hotel) {
  return hotel.rating >= filters.rating;
}

// second filter
function filterMeal(hotel) {
  return !filters.mealType.length || hotel.mealType == filters.mealType;
}

// apply both filters to initial array
function update() {
  let filteredCards = hotels.filter(filterRating).filter(filterMeal);
};

Full example:

var filters = {
  rating: 4,
  mealType: ""
};

rating.value = filters.rating;
mealtype.value = filters.mealType;

rating.addEventListener("input", function() {
  filters.rating = rating.value;
  update();
});
mealtype.addEventListener("input", function() {
  filters.mealType = mealtype.value;
  update();
});

function filterRating(hotel) {
  return hotel.rating >= filters.rating;
}

function filterMeal(hotel) {
  return !filters.mealType.length || hotel.mealType == filters.mealType;
}

function update() {
  let filteredCards = getHotels().filter(filterRating).filter(filterMeal);
  console.log(filters);
  output.innerHTML = filteredCards.map(hotel => `<span>${hotel.name}</span>`).join("");
};
update();

function getHotels() {
  return [{
      name: "A",
      rating: 5,
      mealType: "full"
    },
    {
      name: "B",
      rating: 4,
      mealType: "full"
    },
    {
      name: "C",
      rating: 4,
      mealType: "breakfast"
    },
    {
      name: "D",
      rating: 5,
      mealType: "breakfast"
    }
  ];
}
input,
select {
  margin: 1em 0
}

#rating {
  width: 3em
}

#output span {
  display: inline-block;
  border: 1px solid black;
  padding: 0.5em;
  margin: 0.5em;
}
Rating &gt;= <input id="rating" value="5" type="number"><br> Meal:
<select id="mealtype">
  <option value="">any</option>
  <option>breakfast</option>
  <option>full</option>
</select><br>
<p id="output"></p>
  • For reference: https://stackoverflow.com/questions/31831651/javascript-filter-array-multiple-conditions –  Sep 26 '18 at 15:06
6

A clean and functional solution

const combineFilters = (...filters) => (item) => {
    return filters.map((filter) => filter(item)).every((x) => x === true);
};

then you use it like so:

const filteredArray = arr.filter(combineFilters(filterFunc1, filterFunc2));

and filterFunc1 for example might look like this:

const filterFunc1 = (item) => {
  return item === 1 ? true : false;
};
4

If I've understood the problem correctly, you can just pass all your matching functions to the filter() test function and require them all to be truthy using AND operators:

let filteredCards = hotels.filter(hotel => chooseRating(hotel) && chooseMeal(hotel) && choosePlace(hotel))

This is the same as:

let filteredCards = hotels.filter(hotel => {
  return chooseRating(hotel) && chooseMeal(hotel) && choosePlace(hotel)
})

This is also the same:

let filteredCards = hotels.filter(function(hotel) {
  return chooseRating(hotel) && chooseMeal(hotel) && choosePlace(hotel)
})
camslice
  • 571
  • 6
  • 8
2

So lets say you have something like this:

<select id="rating">Rating options here ...</select>
<select id="meals">Meals options here ...</select>

<fieldset id="places">
<input type="checkbox" value="one">place one</input>
<input type="checkbox" value="two">place two</input>
</fieldset>

<input type="radio" name="rad" value="rad_one">radio one</input>
<input type="radio" name="rad" value="rad_two">radio two</input>

You can filter the hotels Array with something like:

let selected_rating = document.querySelector('#rating').value;
let selected_meal = document.querySelector('#meals').value;
let selected_places = [].map.call(document.querySelectorAll('#places input[type="checkbox"]:checked'),function(elem){ return elem.value});
let selected_radio = document.querySelector('[name="rad"]:checked').value;

let filteredCards = hotels.filter(function(hotel){
    return hotel.rating == selected_rating
        && hotel.mealType == selected_meal
        && selected_places.indexOf(hotel.place) != -1
        && hotel.region == selected_radio;
});
Iván Nokonoko
  • 4,888
  • 2
  • 19
  • 27
1

I had to do a filter for a code challenge using 2 inputs.

My first approach as you said was writing the if conditions inside one filter and didn't work.

What I did was create a new array filtering by the first condition, and then filter that returned array using the second condition and that worked.

Hope this solves your problem :D

pakman198
  • 123
  • 6
  • I think it could be possible only with the specific order of filters, that can't be changed. Here, for example, user can choose region first and only then mealtype and vice versa :) – Tonya Sep 26 '18 at 14:37
  • 1
    The order doesn't matter. –  Sep 26 '18 at 14:38
  • As Chris says, the order doesn't matter, if you first filter by the first input and the user didnt change anything, you'll get the same array and then filter using the second input. – pakman198 Sep 26 '18 at 14:47