5

How can I dynamically declare a set of filters criteria without having to specify the number of filters?

For example, if I have a set of data, like this:

var data = [ 
  { item: { type: 'wood', size: 10 } }, 
  { item: { type: 'wood', size: 8 } },
  { item: { type: 'metal', size: 8 } } 
]

I Know that I can use JS .filter() to get all of the items that have type wood and size 8:

function filterItems() {
  return data.filter(function(val) {
    return val['item'].type == 'wood' && 
           val['item'].size == 8;
  }
}

But what if I want to filter the items with an unknown number of filters, and have .filter() return all data items that match those criterion?

Here is a codepen that reflects the above code.

3
  • how would these dynamic filter criteria be passed to your code? Commented Jul 1, 2015 at 20:06
  • I have a service Javascript file that accepts dynamic inputs and then returns the filtered data based on the input criterion. Is this what you wanted to know? Commented Jul 1, 2015 at 20:07
  • i would write a simple comparator function and then chain my criteria in a series of filters, ex: r.filter(comp, ["type","wood"]).filter(comp, ["size",8); Commented Jul 1, 2015 at 20:30

4 Answers 4

14

You could pass an array of conditions to the filterItems() function. Try this:

function filterItems(filters) {
  return data.filter(function(val) {
    for(var i = 0; i < filters.length; i++)
      if(val['item'][filters[i][0]] != filters[i][1])
        return false;
    return true;
  }
}
filterItems([['type', 'wood'], ['size', 8], ['someother', 'value']]);

The same idea can be applied in various formats, such as using objects instead of an array for increased readability.

Sign up to request clarification or add additional context in comments.

8 Comments

I think I understand what you're trying to show me, can you clean up your code, please?
What cleanup are you looking for?
Soo.. it's fine? or not? I don't understand (And if not, please be specific, what don't you "like"?)
This is good and helpful. Just out of curiosity, is it possible to put the for loop outside of the data.filter?
No. You need to test any val against all conditions. You need that inside the filter function.
|
4

I just did some one line refactor on Amit's answer for any data structure and nested properties support

// the function
filterItems = (data, filters) => data.filter(item => !filters.find(x => x.key.split('.').reduce((keys, key) => keys[key], item) !== x.value))

// how to use it
filterItems(data, [{ key: 'type', value: 'wood' }, { key: 'some.nested.prop', value: 'value' }])

Comments

1
function isSingle(filter) {
    return (filter && 'o' in filter && 'm' in filter && 'v' in filter);
}

function isComposite(filter) {
    return (filter && 'lo' in filter);
}

function createBody(filter) {

    if (isComposite(filter)) {
        var bdy = "";
        if (filter.v.length > 1) {
            var o = filter.lo;
            return "(" + createBody(filter.v.shift()) + " " + o + " " + createBody({ lo: filter.lo, v: filter.v }) + ")";
        } else if (filter.v.length == 1) {
            return createBody(filter.v.shift());
        }
        return bdy;
    } else if (isSingle(filter)) {
        var o = filter.o;
        if (typeof filter.v == "string") filter.v = "'" + filter.v + "'"
        return "item." + filter.m + " " + o + "  " + filter.v;
    }
}
var createFunc = function (filter) {

    var body = createBody(filter);
    var f = new Function("item", " return " + body + ";");
    return f;
}

function applyFilter(input, filter) {
    if (filter == undefined) {
        return input;
    }

    var fun = createFunc(filter);
    var output = input.filter(fun);
    return output;
};
//m:member,o:operator,v:value.

var filterQuery1 = { m: "item.type", o: "==", v: "metal" };//simpe query
var filterQuery2 = { m: "item.size", o: ">", v: 8 };
var filterQuery3 = {
    lo: "&&", v: [
        { m: "item.type", o: "==", v: "metal" },
        { m: "item.size", o: "<", v: 9 }]
}; //composite query
var data = [
  { item: { type: 'wood', size: 10 } },
  { item: { type: 'wood', size: 8 } },
  { item: { type: 'metal', size: 8 } }
]
var result = applyFilter(data, filterQuery1);// or filterQuery2,filterQuery3

console.log(result);

https://jsfiddle.net/kd0kL098/

1 Comment

Please summarize your answer in words
1

Amit's answer is great, but I wanted to add to it. In my case, I needed to return all parameters, else return none/false. Here is the edited code from Amit's

function filterItems(filters) {
  return data.filter(function(val) {
    let result = true;
    for(var i = 0; i < filters.length; i++)
      if(val['item'][filters[i][0]] != filters[i][1])
        result = false;
    return result;
  }
}
filterItems([['type', 'wood'], ['size', 8], ['someother', 'value']]);

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.