0

I have built a filter based in this post. The problem is that in some objects I have properties that can have multiple values (an array of values) at the same time (so for example, a type of wine can be at the same time the category "wine" and "champagne"). I have been trying to modify the filter with no luck. Here is my html:

<div ng-controller="myCtrl">
<div ng-repeat="(prop, ignoredValue) in wines[0]" ng-init="filter[prop]={}">
    <b>{{prop}}:</b><br />
    <span class="quarter" ng-repeat="opt in getOptionsFor(prop)">
        <b><input type="checkbox" ng-model="filter[prop][opt]" />&nbsp;{{opt}}</b>
    </span>
    <hr />
</div>
<div ng-repeat="w in filtered=(wines | filter:filterByProperties)">
    {{w.name}} ({{w.category}})
</div>

And my angular logic:

var app = angular.module('myApp', []);
app.controller('myCtrl', function ($scope) {
$scope.wines = [
    { name: "Wine A", category: ["red"] },
    { name: "Wine B", category: ["red"] },
    { name: "wine C", category: ["white"] },
    { name: "Wine D", category: ["red"] },
    { name: "Wine E", category: ["red"] },
    { name: "wine F", category: ["champagne","white"] },
    { name: "wine G", category: ["champagne"]},
    { name: "wine H", category: ["champagne"] }    
];
$scope.filter = {};

$scope.getOptionsFor = function (propName) {
    return ($scope.wines || []).map(function (w) {
        return w[propName];
    }).filter(function (w, idx, arr) {
        return arr.indexOf(w) === idx;
    });
};

$scope.filterByProperties = function (wine) {
    // Use this snippet for matching with AND
    var matchesAND = true;
    for (var prop in $scope.filter) {
        if (noSubFilter($scope.filter[prop])) continue;
        if (!$scope.filter[prop][wine[prop]]) {
            matchesAND = false;
            break;
        }
    }
    return matchesAND;
};

function noSubFilter(subFilterObj) {
    for (var key in subFilterObj) {
        if (subFilterObj[key]) return false;
    }
    return true;
}
});

I would like to make it work so when a object has a property with an array that more than one value (like in the example white and champagne), the object would be selected with just selecting one of them (or both). Thanks in advance.

I have updated the JsFiddle here

3 Answers 3

1

I guess you are trying to filter objects for each property. If this is what you want, I changed your code which lists all properties and all possible values for each property as checkboxes.

First, it passes on each wine to get all available filter options and set it as an array variable. Also removes duplicates.

Then when each checkbox is checked, filterByCurrentFilter method finds all selected filters and checks if a wine supports all those criteria.

Here is a demo.

var app = angular.module('myApp', []);
app.controller('myCtrl', function ($scope) {
    $scope.wines = [
        { name: "Wine A", category: "red" },
        { name: "Wine B", category: "red" },
        { name: "wine C", category: "white" },
        { name: "Wine D", category: "red" },
        { name: "Wine E", category: "red" },
        { name: "wine F", category: ["champagne","white"] },
        { name: "wine G", category: "champagne"},
        { name: "wine H", category: "champagne" }    
    ];
    $scope.filter = {};

    $scope.getFilterOptions = function() { 
            var optsAsObj = $scope.wines.reduce( function(prev,current) {
            for ( field in current ) {
                prev[field] = prev[field] || [];
                prev[field] = prev[field].concat(current[field])
                // remove duplicates
                prev[field] = prev[field].filter(function(item, pos) {
                    return prev[field].indexOf(item) == pos;
                });
            }
            return prev;
        }, {});

        var result = []
        for (key in optsAsObj) {
            if ( key == '$$hashKey')
               continue;
                result.push ( { name:key, values: optsAsObj[key] })
        }

        return result;

    };

    // for performance, calculate once and set it to a variable
    $scope.filterOptions = $scope.getFilterOptions();

    $scope.filterByCurrentFilter = function (wine) {
        var selectedFilters = {}
        for ( filterName in $scope.filter) {

                // i.e filterName: name, category
                var criteria = $scope.filter[filterName]
            console.log('filterName:' + filterName)
            // i.e criteria: { 'Wine A': true, 'Wine B': null, 'Wine C':false }
            for (criteriaName in criteria) {
                    // i.e. criteriaName: 'Wine A'
                    if (criteria[criteriaName]) {

                        // a filter is true for criteriaName
                        var criteriaVals = selectedFilters[filterName] || []
                    criteriaVals.push(criteriaName);

                    console.log('filterName:' + filterName + ' criteriaName:' + criteriaName + " criteriaVals:" + criteriaVals);

                    if (criteriaVals.length > 0)
                        selectedFilters[filterName] = criteriaVals
                }
            }
        }

        for (key in selectedFilters) {
            console.log(key + ':' + selectedFilters[key])
                var filterVals = selectedFilters[key];
            if (!filterVals || filterVals.length == 0)
                    continue;

            var wineVal =  wine[key];
            wineVal = angular.isArray(wineVal) ? wineVal : [wineVal];

            var matches = wineVal.some ( function(item) {
                    return filterVals.indexOf(item) != -1;
            });

            if (!matches)
                return false;
        }

        return true;
    };

    function noFilter(filterObj) {
        for (var key in filterObj) {
            if (filterObj[key]) {
                return false;
            }
        }
        return true;
    }            
});
Sign up to request clarification or add additional context in comments.

1 Comment

Ok, I see the logic. In addition to the other solution, in this one when you are already filtering by one property (f.ex name), and you filter by a value in another property (f.ex. category), it doesn't add both sets, but it makes the intersection. I think this solves the problem better, so I will switch this to valid. Thanks!
1

This will work:

var app = angular.module('myApp', []);
app.controller('myCtrl', function ($scope) {
    $scope.wines = [
        { name: "Wine A", category: "red" },
        { name: "Wine B", category: "red" },
        { name: "wine C", category: "white" },
        { name: "Wine D", category: "red" },
        { name: "Wine E", category: "red" },
        { name: "wine F", category: ["champagne","white"] },
        { name: "wine G", category: "champagne"},
        { name: "wine H", category: "champagne" }    
    ];
    $scope.filter = {};

    $scope.getOptionsFor = function (propName) {
        return ($scope.wines || []).map(function (w) {
        if(w[propName] instanceof Array)
        {
          for (var key in w[propName]) {
            return w[propName][key];
          }
        }else{
            return w[propName];
            }
        }).filter(function (w, idx, arr) {
            return arr.indexOf(w) === idx;
        });
    };

    $scope.filterByProperties = function (wine) {
        // Use this snippet for matching with AND
        var matchesAND = true;
        for (var prop in $scope.filter) {
            if (noSubFilter($scope.filter[prop])) continue;
            if(wine[prop]  instanceof Array)
            {
                for (var key in wine[prop]) {
                        if (!$scope.filter[prop][wine[prop][key]]){
                  matchesAND = false;
                  }else{
                    matchesAND = true;
                    break;
                  }
                }
            }
            else if (!$scope.filter[prop][wine[prop]]) {
                matchesAND = false;
                break;
            }
        }
        return matchesAND;
    };

    function noSubFilter(subFilterObj) {
        for (var key in subFilterObj) {
            if (subFilterObj[key]) return false;
        }
        return true;
    }
});

http://jsfiddle.net/8d6f5fdf/4/

Comments

0

I would recommend converting all the categories to arrays like this:

$scope.wines = [
    { name: "Wine A", category: ["red"] },
    { name: "Wine B", category: ["red"] },
    { name: "wine C", category: ["white"] },
    { name: "Wine D", category: ["red"] },
    { name: "Wine E", category: ["red"] },
    { name: "wine F", category: ["champagne","white"] },
    { name: "wine G", category: ["champagne"]},
    { name: "wine H", category: ["champagne"] }    
];

Then you can treat this property like an array everywhere. It will be more consistent and readable.

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.