2

This is a simplified example of a problem I have on a website.

I have an array with some items like this:

var testArr = ["Jeremy", "John", "Hank", "Hal"];

If I know what the filters are, I can filter it like this:

var testArr2 = testArr.filter(function (item){
    return item.length < 5 &&
            item.startsWith("H");
});

On my website, I have an interface where the user can select several filters. In this example, the user would be able to decide to filter by either length or what the value starts with. I need to be able to add conditions to that return dynamically or find some other way to filter. I tried some of the answers at he SO thread javascript filter array multiple conditions, but I am having trouble applying that to my example.

Thanks in advance!

mrcoulson
  • 1,331
  • 6
  • 20
  • 35

6 Answers6

19

You could store the single filters in an array as function and filter by checking each filter function with the actual value.

var array = ["Jeremy", "John", "Hank", "Hal", "Hermine"],
    filters = {
        smallerThanFive: item => item.length < 5,
        startsWithH: item => item.startsWith('H')
    }
    selected = [filters.smallerThanFive, filters.startsWithH],
    result = array.filter(item => selected.every(f => f(item)));

console.log(result);
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • This one did it for me! I am able to add filters dynamically by pushing them into the array of filters. Thanks! – mrcoulson Jan 06 '20 at 16:17
  • This works when my data source is flat or just object in object but what could we modify in the above code to cater for "const filters = { greaterThanPrice: item => item.rooms.rPrice > 200 }" where rooms is an array of objects and rPrice is well the price of one of those objects :-) So my data structure is {property{ pId: 1, rooms: [ {rPrice:50}, {rPrice:100} ] }}, {property{ pId: 2, rooms: [ {rPrice:75}, {rPrice:125} ] }} – ssedwards Apr 18 '20 at 10:47
  • @ssedwards, that is more than to aswer another question in the comment section. please ask a new question along with your try. – Nina Scholz Apr 18 '20 at 10:58
  • Ok, I think I figured it out by using the following: {greaterThanPrice: item => item.rooms.some(r => r.rPrice > 180)} – ssedwards Apr 18 '20 at 11:03
  • @NinaScholz thanks for coming back to me though :-) – ssedwards Apr 18 '20 at 11:04
1

Can't you just ask the user the two parameters, the length and the starting value, storing them in two variables and then using them as the values for the filter() function?
Something like this:

var maxLength = userLengthInput; //Get this value from the user.
var startValue = userStartValueInput; //Get this value from the user.

var testArr2 = testArr.filter(function (item){
    return item.length < maxLength && item.startsWith(startValue);
});
CrystalSpider
  • 377
  • 7
  • 16
1

You could isolate the cases and return true or false individually, or you can combine all conditions into one big boolean expression.

For example:

var testArr2 = testArr.filter(function (item){
    if (checkBoxCheckedForRejectStringWithJustOneCharacter() &&
        item.length === 1) return false;
    if (checkBoxCheckedForAcceptingAllStringsBeginningWithZ() &&
        item[0].toLowerCase() === "z") return true;
    return item.length < 5 &&
            item.startsWith("H");
});

It just depends on your UI and you can customize the conditions in your code.

nonopolarity
  • 146,324
  • 131
  • 460
  • 740
1

Just ask.

const testArr = ["Jeremy", "John", "Hank", "Hal"];

const filterIt = (arr,len,sw) => {
  return arr.filter(item => {
    let okLength = len === null || (len && len > 0 && item.length<len);
    let okSw = !sw  || (sw && item.startsWith(sw));
    return okLength && okSw;
  })
}

console.log(filterIt(testArr,null,"H"))
console.log(filterIt(testArr,5))
console.log(filterIt(testArr,4,"H"))
mplungjan
  • 169,008
  • 28
  • 173
  • 236
1

For simple usage it's better to have a class Filter containing all filtering logic. Then instantiate it with filters we want and apply them to the input we already have.

Take a look at the code below. It's more readable.

For the sake of extendability, you can also put all filtering logics into individual class and just use those classes in your main Filter class.

var testArr = ["Jeremy", "John", "Hank", "Hal"];

function Filter(filters) {
  this.filters = filters;
}

Filter.prototype.filterByLength = function(items, value) {
  return items.filter(function(item) {
    return item.length < value
  })
};

Filter.prototype.filterByValue = function(items, value) {
  return items.filter(function(item) {
    return item.startsWith(value);
  })
};

Filter.prototype.filter = function(items) {
  return Object.entries(this.filters)
    .reduce(function (final, [key, value]) {
    return final && this[key](items, value)
  }.bind(this), true)
};

var instance = new Filter({
  filterByLength: 5,
  filterByValue: 'H'
})

var result = instance.filter(testArr)
console.log(result)
agtabesh
  • 548
  • 3
  • 15
  • 1
    Would it not be more readable if the filterBy were members rather than protoype assignments? – mplungjan Jan 07 '20 at 05:28
  • @mplungjan yes if you have only these two filters. Because of dynamicity, I separate them into prototypes to be used as separate classes later on. Thank you for comment. – agtabesh Jan 07 '20 at 12:43
1

Here an example with HTML/CSS/JS:

https://codepen.io/Karkael/pen/zYxpWqR

function showFilteredList() {
  var filterLength = formFilter["string-length"].value;
  var filterStarts = formFilter["string-starts"].value;
  showList(testArr.filter(function (item) {
    if (filterLength.length && item.length >= parseInt(filterLength)) return false;
    if (filterStarts.length && !item.startsWith(filterStarts)) return false;
    return true;
  }));
}

Hope it helps you!

karkael
  • 431
  • 2
  • 9