Filtering complex object inside nested ng-repeat
Asked Answered
S

4

11

I want to filter object inside nested ng-repeat.

HTML:

<div ng-controller="MyController">
<input type="text" ng-model="selectedCityId" />
<ul>
    <li ng-repeat="shop in shops">
      <p ng-repeat = "locations in shop.locations | filter:search" style="display: block">
          City id: {{ locations.city_id }}
          <span style="padding-left: 20px; display: block;" ng-repeat="detail in locations.details | filter:item">Pin code: {{detail.pin}}</span>
      </p>    
    </li>
</ul>

Controller:

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

myApp.controller('MyController', function ($scope) {

    $scope.search = function (location) {

        if ($scope.selectedCityId === undefined || $scope.selectedCityId.length === 0) {
            return true;
        }

           if (location.city_id === parseInt($scope.selectedCityId)) {
               return true;
            }
    };

    $scope.item = function (detail) {

        if ($scope.selectedCityId === undefined || $scope.selectedCityId.length === 0) {
            return true;
        }
        if (detail.pin == parseInt($scope.selectedCityId)) {
            return true;
        }
    };

    $scope.shops =
    [
       {
          "category_id":2,
          "locations":[
             {
                "city_id":368,
                "details": [{
                    "pin": 627718,
                  "state": 'MH'
                }]
             }
          ]
       },
       {
          "name":"xxx",
          "category_id":1,
          "locations":[
             {
                "city_id":400,
                "region_id":4,
                "details": [{
                    "pin": 627009,
                  "state": 'MH'
                },{
                    "pin": 129818,
                    "state": 'QA'
                }]
             },
          ]
       },
    ];

});

Here's the fiddle:

http://jsfiddle.net/suCWn/210/

I want to use multiple filter inside ng-repeat.

Example: Whenever user enters the ID in the input box. The list should filter based on cityID or PinCode. if user enter '129818' it should show pin code result of 129818 along with its parent cityID Similarly, if a user enter 400, the list should filter and show cityID result with 400 along with its child pin code.

EDIT:

Update Code http://codepen.io/chiragshah_mb/pen/aZorMe?editors=1010]

Simplex answered 24/5, 2016 at 13:54 Comment(0)
H
6

First, you must not filter locations with matching details. Use something like this in the search filter:

$scope.search = function (location) {
    var id = parseInt($scope.selectedCityId);
    return isNaN(id) || location.city_id === id || 
           location.details.some(function(d) { return d.pin === id });
};

To show details if filtered by cityID, you have to find the parent location and check if it was filtered.

$scope.item = function (detail) {
    var id = parseInt($scope.selectedCityId);
    return isNaN(id) || detail.pin === id || locationMatches(detail, id);
};

function locationMatches(detail, id) {
    var location = locationByDetail(detail);
    return location && location.city_id === id;
}

function locationByDetail(detail) {
    var shops = $scope.shops;
    for(var iS = 0, eS = shops.length; iS != eS; iS++) {
      for(var iL = 0, eL = shops[iS].locations.length; iL != eL; iL++) {
        if (shops[iS].locations[iL].details.indexOf(detail) >= 0) {
          return shops[iS].locations[iL];
        }
      }
    }
}

EDIT Another, more flexible solution would be to remove all the filters from ngRepeats and do the filtering in a method that you call on ngChange of the search text. Here is the basic structure for this approach.

myApp.controller('MyController', function($scope, $http) { 
  var defaultMenu = [];
  $scope.currentMenu = [];
  $scope.searchText = '';

  $http.get(/*...*/).then(function (menu) { defaultMenu = menu; } );

  $scope.onSearch = function() {
    if (!$scope.searchText) {
      $scope.currentMenu = defaultMenu  ;
    }
    else {
      // do your special filter logic here...
    }
  };
});

And the template:

<input type="text" ng-model="searchText" ng-change="onSearch()" />
<ul>
    <li ng-repeat="category in currentMenu">
      ...   
    </li>
</ul>
Habergeon answered 24/5, 2016 at 14:12 Comment(12)
Filtering through city id should also show it's pin code.Simplex
@chiragshah Should it hide the other children, if searched by pin code?Habergeon
Yes. It should hide other childrenSimplex
@chiragshah A little bit ugly, but should work. (for some reason, i cannot update the fiddle anymore...)Habergeon
Seems working well. The only problem is $scope.shops is updated ($http response object) once after initial page load which disables the item filter for some reason. Can you provide a fiddle for the same ?Simplex
@chiragshah Can you provide a fiddle? I can't reproduce such behaviour.Habergeon
Update codepen [codepen.io/chiragshah_mb/pen/aZorMe?editors=1010] This contains the actual complex object. Before the user interaction starts the $scope.defaultmenu is updated ($http request - response updates it). Now I want a three level hierarchy where the user search on the basis on Category, Group & Item (See the fiddle). I haven't change the code. Just changed the HTML & object. Please help me with it.Simplex
@chiragshah if a category matches, you want to see all it's groups and items (event if they don't match)? if a group matches you want to see all it's items? if an item matches, you don't want to see non matching items in this group? Is this the desired behaviour?Habergeon
The desired behaviour : If CATEGORY matches :- All of its groups & items must be seen. If GROUP matches :- All of its item, Only Its category And no other group in that category must be seen. If ITEM matches: Only its category, group (of that item only) & the item itself must be seen.Simplex
@chiragshah I think you should implement the filter logic in a function that updates the whole menu at once. This is the most flexible approach. I edited my answer. I believe that you can add your special logic on your own.Habergeon
Let us continue this discussion in chat.Simplex
As per your comment "The only problem is $scope.shops is updated ($http response object) once after initial page load which disables the item filter for some reason." I think you are trying to use 2nd approch. Then on ajax call response you need to call $scope.onSearch() again to update the filtered list.Prisoner
L
2

I have updated your filters. The problem is in your search filter you are only checking for the city_id, what you should do is:

  1. Check if the typed id is city_id
  2. Check if typed id is a pid of a child detail of given location

Similar thing for the item filter:

  1. Check if the typed id is a pid of the detail being filtered
  2. Check if the typed id is a city_id of the parent location of the detail passed in

Here is a working jsFiddle. I hope this helps.

Longbow answered 27/5, 2016 at 8:51 Comment(0)
C
1

By simply modifying the JSON to include the city_id for children so you don't need to loop through it to get the parent's city_id, the solution is as easy as this:

var myApp = angular.module('myApp', []);
myApp.controller('MyController', function ($scope) {
    $scope.search = function (location) {
        if (!$scope.selectedCityId)
            return true;
        //if user's input is contained within a city's id
        if (location.city_id.toString().indexOf($scope.selectedCityId) > -1)
            return true;
        for (var i = 0; i < location.details.length; i++)
            //if user's input is contained within a city's pin
            if (location.details[i].pin.toString().indexOf($scope.selectedCityId) > -1)
                return true;
    };

    $scope.item = function (detail) {
        if (!$scope.selectedCityId)
            return true;
        //if user's input is contained within a city's id
        if (detail.city_id.toString().indexOf($scope.selectedCityId) > -1)
            return true;
        //if user's input is contained within a city's pin
        if (detail.pin.toString().indexOf($scope.selectedCityId) > -1)
            return true;
    };

Modified JSON

$scope.shops=[{"category_id":2,"locations":[{"city_id":368,"details":[{"city_id":368,"pin":627718,"state":'MH'}]}]},{"name":"xxx","category_id":1,"locations":[{"city_id":400,"region_id":4,"details":[{"city_id":400,"pin":627009,"state":'MH'},{"city_id":400,"pin":129818,"state":'QA'}]},]},];});

If directly modifying the JSON is not possible, you can modify it like this in this controller directly after this $scope.shops = ...json... statement:

for(var i=0; i<$scope.shops.length; i++)
    for(var j=0, cat=$scope.shops[i]; j<cat.locations.length; j++)
        for(var k=0, loc=cat.locations[j]; k<loc.details.length; k++)
            loc.details[k].city_id=loc.city_id;

Working fiddle: http://jsfiddle.net/87e314a0/

Calculate answered 1/6, 2016 at 22:38 Comment(1)
codepen.io/chiragshah_mb/pen/aZorMe?editors=1010] This is the actual use case. Can you provide solution for this ?Simplex
A
1

I tried to make the solution easier to understand :

index.html :

<div ng-controller="MyController">
    <input type="text" ng-model="search.city_id" />
    <ul>
        <li ng-repeat="shop in shops">
          <p ng-repeat = "locations in shop.locations | filter:search.city_id" style="display: block">
              City id: {{ locations.city_id }}
              <span style="padding-left: 20px; display: block;" ng-repeat="detail in locations.details | filter:item">Pin code: {{detail.pin}}</span>
          </p>    
        </li>
    </ul>
</div>

app.js :

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

myApp.controller('MyController', function ($scope) {
    $scope.shops =
[
   {
      "category_id":2,
      "locations":[
         {
            "city_id":368,
            "details": [{
                "pin": 627718,
              "state": 'MH'
            }]
         }
      ]
   },
   {
      "name":"xxx",
      "category_id":1,
      "locations":[
         {
            "city_id":400,
            "region_id":4,
            "details": [{
                "pin": 627009,
              "state": 'MH'
            },{
                "pin": 129818,
                            "state": 'QA'
            }]
         },
      ]
   },
];


});

Here's the fiddle : mySolution

Airdrome answered 2/6, 2016 at 15:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.