12

I have the following code where i'm trying to filter on the players in the array by checking a checkbox for the pantsize of a player.

I know i have the data array in a repeater, and then the filtering inputs in an element outside of the data array element (two different divs), could this be what's causing the disconnect? Because i notice when i add the checkbox to the repeater element i do get some form of feedback array when i click the checkbox.

Binding a search input box was so easy to implement, however i'm spending way too much time getting this simple checkbox to filter the data.

So im now reaching out to the Angular community for a little help on filtering with checkboxes as the documentation does not cover this topic very well.

Here is the fiddle: http://jsfiddle.net/rzgWr/1/

<div ng-controller="MyCtrl">
<div>
<div ng-repeat="pants in players | groupBy:'pants'">
    <b><input type="checkbox" ng-model="query"/>{{pants}}</b>
    <span>({{(players | filter:pants).length}})</span>
</div>

<div>
    <ul>
    <li ng-repeat="player in players | filter:query">
        <p><b>{{player.name}}</b></p>
        <p>{{player.shirt}} {{player.pants}}, {{player.shoes}}</p>
    </li>
    </ul>    
</div>
</div>

function MyCtrl($scope, filterFilter) {
$scope.players = [
    {name: 'Bruce Wayne', shirt: 'XXL', pants: '42', shoes: '12'},
    {name: 'Wayne Gretzky', shirt: 'XL', pants: '38', shoes: '10'},
    {name: 'Michael Jordan', shirt: 'M', pants: '32', shoes: '9'},
    {name: 'Player Two', shirt: 'XXL', pants: '42', shoes: '12'}
]; 

$scope.$watch('filtered', function (newValue) {
    if (angular.isArray(newValue)) {
        console.log(newValue.length);
    }
}, true);    

}

Any and all help/advice is sincerely appreciated.

Thanks.

Tony H.
  • 1,975
  • 4
  • 14
  • 20

2 Answers2

27

EDIT 2

Per all the requests of the OP, here is the final state.

http://jsfiddle.net/rzgWr/19/


EDIT (OP added a bounty)

Per your bounty, is this what you wanted?

http://jsfiddle.net/rzgWr/17/


Is this what you wanted?

http://jsfiddle.net/rzgWr/12/

Template

<div ng-controller="MyCtrl">
    <div>
      <div>
          Search: <input name="company" type="text" class="search-input" ng-model="query"/>
       </div>
    <div ng-init="pantsGroup = (players | groupBy:'pants')">
        <div ng-repeat="pants in pantsGroup" >
            <b><input type="checkbox" ng-model="usePants[$index]"/>{{pants}}</b>
            <span>({{(players | filter:pants).length}})</span>
        </div>
    </div>

    <div>
        <ul>
        <li ng-repeat="player in players | filter:query | filter:filterPants()">
            <p><b>{{player.name}}</b></p>
            <p>{{player.shirt}} {{player.pants}}, {{player.shoes}}</p>
        </li>
        </ul>    
    </div>
    </div>
</div>

Script

var myApp = angular.module('myApp',[]);

function MyCtrl($scope, filterFilter) {
    $scope.usePants = [];

    $scope.filterPants = function () {
        return function (p) {
            for (var i in $scope.usePants) {
                return (p.pants == $scope.pantsGroup[i] && $scope.usePants[i]);
            }
        };
    };

    $scope.players = [
        {name: 'Bruce Wayne', shirt: 'XXL', pants: '42', shoes: '12'},
        {name: 'Wayne Gretzky', shirt: 'XL', pants: '38', shoes: '10'},
        {name: 'Michael Jordan', shirt: 'M', pants: '32', shoes: '9'},
        {name: 'Rodman', shirt: 'XXL', pants: '42', shoes: '11'},
        {name: 'Jake Smitz', shirt: 'XXL', pants: '42', shoes: '12'},
        {name: 'Will Will', shirt: 'XXL', pants: '42', shoes: '12'},
        {name: 'Youasdf Oukls', shirt: 'XL', pants: '38', shoes: '10'},
        {name: 'Sam Sneed', shirt: 'XL', pants: '38', shoes: '10'},
        {name: 'Bill Waxy', shirt: 'M', pants: '32', shoes: '9'},
        {name: 'Javier Xavior', shirt: 'M', pants: '32', shoes: '9'},
        {name: 'Bill Knight', shirt: 'M', pants: '32', shoes: '9'},        
        {name: 'One More', shirt: 'M', pants: '32', shoes: '9'},        
        {name: 'Player One', shirt: 'XXL', pants: '42', shoes: '11'},
        {name: 'Space Cadet', shirt: 'XXL', pants: '42', shoes: '12'},
        {name: 'Player Two', shirt: 'XXL', pants: '42', shoes: '12'}
    ]; 

    $scope.$watch('filtered', function (newValue) {
        if (angular.isArray(newValue)) {
            console.log(newValue.length);
        }
    }, true);    
}

myApp.filter('count', function() {
    return function(collection, key) {
      var out = "test";
      for (var i = 0; i < collection.length; i++) {
          //console.log(collection[i].pants);
          //var out = myApp.filter('filter')(collection[i].pants, "42", true);
      }
      return out;
    }
});

var uniqueItems = function (data, key) {
    var result = new Array();
    for (var i = 0; i < data.length; i++) {
        var value = data[i][key];

        if (result.indexOf(value) == -1) {
            result.push(value);
        }

    }
    return result;
};

myApp.filter('groupBy',
            function () {
                return function (collection, key) {
                    if (collection === null) return;
                    return uniqueItems(collection, key);
        };
    });
Mantisse
  • 309
  • 4
  • 15
Jesus is Lord
  • 14,971
  • 11
  • 66
  • 97
  • is there an easy way to show all data, and then filter on that data once the checkboxes are clicked? What you have in the fiddle you provided is damn near spot-on to what i want, however i would just ask that all the data is visible to start. – Tony H. Jan 12 '14 at 17:46
  • 1
    Words: When i try this functionality now with data from the server, the filtering no longer works because the ng-init is running not able to run the expression on dynamic data. Any thoughts on what i could use instead of ng-init? Thanks. – Tony H. Jan 15 '14 at 16:31
  • 1
    Yeah I used that so I wouldn't have to inject the `$filter` service into the controller. Here's all you need to do: http://jsfiddle.net/rzgWr/16/ – Jesus is Lord Jan 15 '14 at 16:49
  • Gotta leave for lunch... be back in an hour or two. Hopefully the bounty is still here ;) – Jesus is Lord Jan 15 '14 at 16:51
  • Question: Do you want the cascade to work so that each filter builds on the previous or do you want it so that it uses the most recently changed filters to cascade to the remaining filters (that's really hard to systemize)? – Jesus is Lord Jan 15 '14 at 19:09
  • 1
    I suppose either solution would work, as long as cascade filtering is narrowing the array data. Ideally i'd like the filters update according to the remaining data. So if for example Shoes: M is filtered and only pants sizes of Small and Medium remain in the filtered data, then only the Pants filters for Small and Medium are visible and available for further filtering. Does that help at all? – Tony H. Jan 15 '14 at 19:29
  • I think so. Check out the latest. Is that what you wanted? – Jesus is Lord Jan 15 '14 at 20:02
  • This is almost there, however in some instances rather than disappearing, the filters simply zero out. This is not always the case though. Any thoughts on why this may be? I'm looking at this fiddle: http://jsfiddle.net/rzgWr/17/ – Tony H. Jan 15 '14 at 21:08
  • Can you tell me an exact configuration. Cascading filtering all done on the same page is difficult to systemize. The reason 0's are appearing is because there are none in that category. I leave them there so that the user can select more than one of a single category. From top to bottom they cascade nicely, though. I think the issue comes from jumping around. If you could give a set of rules or something that govern the exact behavior you'd like that'd help. You probably need to edit your question or bounty to go into enough detail. Or iteratively go like we've been doing. Whichever. – Jesus is Lord Jan 15 '14 at 21:12
  • Is it possible to keep them all on the page with a zero out where the filter is no longer relevant to the filtered data, just as it does in most instances? I think this would help keep it consistent and should be a more natural feel for filtering the data. – Tony H. Jan 16 '14 at 03:09
  • I think this does what you want. Now it may make sense to take out the parenthesis, though: http://jsfiddle.net/rzgWr/18/ – Jesus is Lord Jan 16 '14 at 03:46
  • Hello Words, i feel like we're 95% there. When i click on the 'M' checkbox, i get the desired functionality where everything filter that does not provide filtering value on already filtered data is zeroed out. When i click on any other filter, there are counts that are over zero when they don't infact provide any filtering value on the balance of data. Example: When i click on 'XL', i get a new count on 'M' of (1), when there is no 'M' data to filter down. Any thoughts? – Tony H. Jan 16 '14 at 19:16
  • 1
    Yeah I just rewrote that section of code. Seems to work. http://jsfiddle.net/rzgWr/19/ – Jesus is Lord Jan 16 '14 at 21:34
  • 2
    What you have done is simply magnificent! You have added tremendous value to this forum for the topic of Angular and i am more than happy to award you the bounty! :) – Tony H. Jan 16 '14 at 21:46
  • @WordsLikeJared I need to implement exactly that but getting the data from an external JSON, would you help me please? – ManelPNavarro May 28 '15 at 15:07
  • @ManelMusti Post a question. I can try. – Jesus is Lord May 29 '15 at 19:16
  • @WordsLikeJared I really appreciate it, but I finally found it out. – ManelPNavarro Jun 02 '15 at 10:59
  • I'm using this and it works fine. The only thing I can't make it work is to show the amount of results including the search filter. I did this change in your main fiddle: http://jsfiddle.net/rzgWr/487/ Do you know how to make the 'Results' show the results amount even when searching with the searchFilter? – celsomtrindade Sep 09 '15 at 14:16
  • 1
    @CelsomTrindade If I understand correctly `{{ (filteredPlayers | filter:query).length }}`. If that doesn't do it - make a question so others can more easily benefit, too. – Jesus is Lord Sep 09 '15 at 15:06
  • @WordsLikeJared That's it! I was trying to change inside the controller. Forgot to do that. Thanks again! This is an awesome code you made! – celsomtrindade Sep 09 '15 at 15:52
  • @WordsLikeJared I have a complementary question about this filtering system here: http://stackoverflow.com/questions/33237748/angularjs-checkbox-filter-with-array-inside-array Would you mind taking a look? – celsomtrindade Oct 20 '15 at 18:24
  • Hi ! I have too a complementary question about this filtering system here: http://stackoverflow.com/questions/36865385/angularjs-filter-on-faceted-search-is-very-slowly Would you mind taking a look? – Akawan May 02 '16 at 14:45
  • 1
    When you write $scope.filterPants = function (player), the variable player is not in use, it is confusing for another developper to understand correctly that in fact you are using the scope and not the player var that can't be individually passed to the filter as far as I know. – Mantisse Jun 22 '16 at 15:50
  • @Mantisse: I think it's normally for production code and even StackOverflow code to have "bugs" in it. Developers should get used to critically looking at code, asking ourselves if it's an oversight on their part or our part. In this case I was just trying to help OP with a highly customized request that I had no idea would get a lot of views. Most of my answers don't. At the time I didn't notice the unused variable (it might have been needed at some point). – Jesus is Lord Jun 22 '16 at 17:21
  • @Mantisse: I would recommend you editing the answer (I welcome all improvements!) - StackOverflow needs constant improvement/refactoring to keep its quality, much like a codebase. It's good for people to feel confident (over time) to step into code someone else wrote and improve it! – Jesus is Lord Jun 22 '16 at 17:23
  • 1
    Your remarks are welcome and I will contribute as the study of your code helped me a lot. By the way your edit 2 fiddle is really impressive. – Mantisse Jun 22 '16 at 18:51
  • @Mantisse: Thanks! I'm glad it helped!! Just keep practicing with Angular for fun always trying to solve things better/more efficient and you'll pick it up. :) – Jesus is Lord Jun 23 '16 at 19:41
0

Full optimzed js Code


var myApp = angular.module('myApp',[]);
var selected;

var uniqueItems = function (data, key) {
    var result = [];

    for (var i = 0; i < data.length; i++) {
        var value = data[i][key];

        if (result.indexOf(value) == -1) {
            result.push(value);
        }

    }
    return result;
};

var fliter = function(totalData,selectedData,equalData){
    var filterAfter = [];   
    selected = false;
    for (var j in totalData) {
        var p = totalData[j];
        for (var i in selectedData) {
            if (selectedData[i]) {
                selected = true;
                if (i == p[equalData]) {
                    filterAfter.push(p);
                    break;
                }
            }
        }        
    }
    if (!selected) {
        filterAfter = totalData;
    }

    return filterAfter;
}

function MyCtrl($scope, filterFilter) {

    $scope.usePants = {};
    $scope.useShirts = {};
    $scope.useShoes = {};

    $scope.players = [
        {name: 'Bruce Wayne', shirt: 'XXL', pants: '42', shoes: '12'},
        {name: 'Wayne Gretzky', shirt: 'XL', pants: '38', shoes: '10'},
        {name: 'Michael Jordan', shirt: 'M', pants: '32', shoes: '9'},
        {name: 'Rodman', shirt: 'XXL', pants: '42', shoes: '11'},
        {name: 'Jake Smitz', shirt: 'XXL', pants: '42', shoes: '12'},
        {name: 'Will Will', shirt: 'XXL', pants: '42', shoes: '12'},
        {name: 'Youasdf Oukls', shirt: 'XL', pants: '38', shoes: '10'},
        {name: 'Sam Sneed', shirt: 'XL', pants: '38', shoes: '10'},
        {name: 'Bill Waxy', shirt: 'M', pants: '32', shoes: '9'},
        {name: 'Javier Xavior', shirt: 'M', pants: '32', shoes: '9'},
        {name: 'Bill Knight', shirt: 'M', pants: '32', shoes: '9'},        
        {name: 'One More', shirt: 'M', pants: '32', shoes: '9'},        
        {name: 'Player One', shirt: 'XXL', pants: '42', shoes: '11'},
        {name: 'Space Cadet', shirt: 'XXL', pants: '42', shoes: '12'},
        {name: 'Player Two', shirt: 'XXL', pants: '42', shoes: '12'}
    ]; 

    // Watch the pants that are selected
    $scope.$watch(function () {
        return {
            players: $scope.players,
            usePants: $scope.usePants,
            useShirts: $scope.useShirts,
            useShoes: $scope.useShoes
        }
    }, function (value) {

            $scope.count = function (prop, value) {
                return function (el) {
                    return el[prop] == value;
                };
            };

            $scope.pantsGroup = uniqueItems($scope.players, 'pants');
            $scope.shirtsGroup = uniqueItems($scope.players, 'shirt');
            $scope.shoesGroup = uniqueItems($scope.players, 'shoes');

            var fliterType = [{selected : $scope.usePants,fliter : 'pants'},{selected : $scope.useShirts,fliter : 'shirt'},{selected : $scope.useShoes,fliter : 'shoes'}];

            var startFliter = $scope.players;

            for(var i in fliterType){
                var startFliter = fliter(startFliter,fliterType[i].selected,fliterType[i].fliter);
            }

            $scope.filteredPlayers = startFliter;

        }, true);}
Balakumaran
  • 63
  • 1
  • 11
  • This is another way to write a above flitering code – Balakumaran Jan 18 '16 at 08:01
  • I am using your code for a similar scenario that I have for my requirement. the only difference is I want to have the unfiltered objects passed to the filterType loop. In your code you passing the startFilter which is the filtered objects but I want to pass the unfiltered objects so whenever user checks multiple options the code shows all the objects for the selected ones. Do you know what change do I need to do to get it done? I have tried different solutions but they breaking the code. – Nisman Sep 27 '16 at 21:05
  • if you debug that selected value you will get answer..for an example put "console.log($scope.usePants);" it return {42:true} for selected; if you want to unselected that value u did need to set like this '{42:false}'; – Balakumaran Sep 28 '16 at 12:36