0

I trying to build a product list based on multiple filters. I thought this should be very straight forward but it's not for me at least.

Here is the plunkr http://plnkr.co/edit/vufFfWyef3TwL6ofvniP?p=preview

Checkboxes are dynamically generated from respective model e.g. sizes, colours, categories. Subcategory checkbozes should perform 'OR' query but cross section it should perform 'AND' query.

basically something like

filter:{categories:selectedcategories1} || {categories:selectedcategories2} | filter:{categories:selectedsizes1} || {categories:selectedsizes2}

problem is generating these filters dynamically. I also tried with filter in controller as-

var tempArr = [{'categories':'selectedvalue1'}, {'categories':'selectedvalue2'}];
var OrFilterObjects = tempArr.join('||');
$scope.products = $filter('filter')($scope.products, OrFilterObjects, true);

But couldn't find a way to assign correct value for OrFilterObjects.

Now as latest attempt (which is in plunkr) I am trying to use a custom filter. It's also not returning OR result.

Right now I am using it as productFilter:search.categories:'categories' if it would have returned OR result then I'd planned to use it as-

`productFilter:search.categories:'categories' | productFilter:search.colours:'colours' | productFilter:search.sizes:'sizes'`

Since I am here seeking help, it would be nice to have like productFilter:search.

I've spent considerable amount of time to find solution of this supposedly simple problem but most of examples use 'non-dynamic' checkboxes or flat objects.

May be I am thinking in wrong direction and there is a more elegant and simple Angular way for such scenarios. I would love to be directed towards any solution to similar solution where nested objects can be filtered with automated dynamically generated filters. Seems to me very generic use case for any shopping application but till now no luck getting there.

8
  • Why don't you use one object for your filter seach = {categories: ["cat"], colour: ["red"], sizes: ["L"]}. and your filter will use that object, you will not need to write 3 filters. Commented Jul 5, 2014 at 7:48
  • Well assuming I want to run OR filter on same section, e.g. 'categores' based on selected checkboxes. If 'men' and 'women' both are selected it should return items for 'men' and 'women' both. filter should look like $filter('filter')($scope.products, filter:{categories:'men'} || {categories:'women'}, true);. If I have array as [{categories:'men'},{categories:'women'}] and using it in filter with 'join', filter would look like, $filter('filter')($scope.products, filter:{categories:'men'},{categories:'women'}, true); which off course wouldn't work. That's significance of ||. Commented Jul 5, 2014 at 7:55
  • I've removed that comment with joining arrays. Does that comments is for that search object? Commented Jul 5, 2014 at 7:58
  • no, for earlier comment. Commented Jul 5, 2014 at 7:58
  • @jcubic I am infact using search object in same fashion but checkbox models would be nested as search{categories:{checkedkey:true}, colours:{checkedkey:true}} even if I get checked values as array instead of object as suggested, how would it help? Problem is to dynamically pass these conditions. Commented Jul 5, 2014 at 8:02

1 Answer 1

1

First thing you need to understand: this problem is not, by any definition, simple. You want to find a match based on a property of an object in an array which is a property of an object inside an input array you're supplying, not to mention [OR intra group] + [AND inter group] relations, search properties defined by either .title or .name, as well as criteria selection being completely dynamic. It's a complex problem.

Though it's a common scenario for shopping cart websites, I doubt that any web framework will have this kind of functionality built into its API. It's unfortunate but I don't think we can avoid writing the logic ourselves.

At any rate, since ultimately you want to just declare productFilter:search, here it is:

app.filter('productFilter', function($filter) {
    var helper = function(checklist, item, listName, search) {
        var count = 0;
        var result = false;
        angular.forEach(checklist, function(checked, checkboxName) {
            if (checked) {
                count++;
                var obj = {};
                obj[search] = checkboxName;
                result = result || ($filter('filter')(item[listName], obj, true).length > 0);
            }
        });
        return (count === 0) || result;
    };

    return function(input, definition) {
        var result = [];

        if (angular.isArray(input) && angular.isObject(definition)) {
            angular.forEach(input, function(item) {
                var matched = null;
                angular.forEach(definition, function(checklist, listName) {
                    var tmp;
                    if (listName !== 'colours') {
                        tmp = helper(checklist, item, listName, 'title');
                    } else{
                        tmp = helper(checklist, item, listName, 'name');
                    }
                    matched = (matched === null) ? tmp : matched && tmp;
                });
                if (matched) result.push(item);
            });
        }
        return result;
    };
});

A couple of notes:

  • How to use: ng-repeat="product in products | productFilter:search".
  • The filter only does some basic checks: input must be an array, and definition must be an object. If you need more, you may do so there.
  • I would say that *.name is an exception to the rule (I assume that most of the criteria is defined by *.title). So, we handle that in if/else.
  • The count variable in a helper function is used to track how many checked checkbox(es) we went through for a particular criteria group. If we went through none, it means that whole criteria group is inactive, and we just return true.
  • It's a good design to create a filter that doesn't mutate the states of other objects outside it. That's why using count is better than calling cleanObj(). This is especially crucial when designing common components for other devs to use in a team: you want to minimize the element of surprise as much as possible.
Sign up to request clarification or add additional context in comments.

1 Comment

I implemented your solution with few adjustments according to my need and it indeed worked. It's disheartening to know there is no ready-made solution for this. I am accepting it and thanks a ton for explaining me around solution. It would surely serve me as navigator. Thanks again. :)

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.