angular grouping filter - angularjs

Following angular.js conditional markup in ng-repeat, I tried to author a custom filter that does grouping. I hit problems regarding object identity and the model being watched for changes, but thought I finally nailed it, as no errors popped in the console anymore.
Turns out I was wrong, because now when I try to combine it with other filters (for pagination) like so
<div ng-repeat="r in blueprints | orderBy:sortPty | startFrom:currentPage*pageSize | limitTo:pageSize | group:3">
<div ng-repeat="b in r">
I get the dreaded "10 $digest() iterations reached. Aborting!" error message again.
Here is my group filter:
filter('group', function() {
return function(input, size) {
if (input.grouped === true) {
return input;
}
var result=[];
var temp = [];
for (var i = 0 ; i < input.length ; i++) {
temp.push(input[i]);
if (i % size === 2) {
result.push(temp);
temp = [];
}
}
if (temp.length > 0) {
result.push(temp);
}
angular.copy(result, input);
input.grouped = true;
return input;
};
}).
Note both the use of angular.copy and the .grouped marker on input, but to no avail :(
I am aware of e.g. "10 $digest() iterations reached. Aborting!" due to filter using angularjs but obviously I did not get it.
Moreover, I guess the grouping logic is a bit naive, but that's another story. Any help would be greatly appreciated, as this is driving me crazy.

It looks like the real problem here is you're altering your input, rather than creating a new variable and outputing that from your filter. This will trigger watches on anything that is watching the variable you've input.
There's really no reason to add a "grouped == true" check in there, because you should have total control over your own filters. But if that's a must for your application, then you'd want to add "grouped == true" to the result of your filter, not the input.
The way filters work is they alter the input and return something different, then the next filter deals with the previous filters result... so your "filtered" check would be mostly irrelavant item in items | filter1 | filter2 | filter3 where filter1 filters items, filter2 filters the result of filter1, and filter3 filters the result of filter 2... if that makes sense.
Here is something I just whipped up. I'm not sure (yet) if it works, but it gives you the basic idea. You'd take an array on one side, and you spit out an array of arrays on the other.
app.filter('group', function(){
return function(items, groupSize) {
var groups = [],
inner;
for(var i = 0; i < items.length; i++) {
if(i % groupSize === 0) {
inner = [];
groups.push(inner);
}
inner.push(items[i]);
}
return groups;
};
});
HTML
<ul ng-repeat="grouping in items | group:3">
<li ng-repeat="item in grouping">{{item}}</li>
</ul>
EDIT
Perhaps it's nicer to see all of those filters in your code, but it looks like it's causing issues because it constantly needs to be re-evaluated on $digest. So I propose you do something like this:
app.controller('MyCtrl', function($scope, $filter) {
$scope.blueprints = [ /* your data */ ];
$scope.currentPage = 0;
$scope.pageSize = 30;
$scope.groupSize = 3;
$scope.sortPty = 'stuff';
//load our filters
var orderBy = $filter('orderBy'),
startFrom = $filter('startFrom'),
limitTo = $filter('limitTo'),
group = $filter('group'); //from the filter above
//a method to apply the filters.
function updateBlueprintDisplay(blueprints) {
var result = orderBy(blueprints, $scope.sortPty);
result = startForm(result, $scope.currentPage * $scope.pageSize);
result = limitTo(result, $scope.pageSize);
result = group(result, 3);
$scope.blueprintDisplay = result;
}
//apply them to the initial value.
updateBlueprintDisplay();
//watch for changes.
$scope.$watch('blueprints', updateBlueprintDisplay);
});
then in your markup:
<ul ng-repeat="grouping in blueprintDisplay">
<li ng-repeat="item in grouping">{{item}}</li>
</ul>
... I'm sure there are typos in there, but that's the basic idea.
EDIT AGAIN: I know you've already accepted this answer, but there is one more way to do this I learned recently that you might like better:
<div ng-repeat="item in groupedItems = (items | group:3 | filter1 | filter2)">
<div ng-repeat="subitem in items.subitems">
{{subitem}}
</div>
</div>
This will create a new property on your $scope called $scope.groupedItems on the fly, which should effectively cache your filtered and grouped results.
Give it a whirl and let me know if it works out for you. If not, I guess the other answer might be better.

Regardless, I'm still seeing the $digest error, which is puzzling: plnkr.co/edit/tHm8uYfjn8EJk3cG31DP – blesh Jan 22 at 17:21
Here is the plunker forked with the fix to the $digest error, using underscore's memoize function: http://underscorejs.org/#memoize.
The issue was that Angular tries to process the filtered collection as a different collection during each iteration. To make sure the return of the filter always returns the same objects, use memoize.
http://en.wikipedia.org/wiki/Memoization
Another example of grouping with underscore: Angular filter works but causes "10 $digest iterations reached"

You can use groupBy filter of angular.filter module,
and do something like this:
usage: (key, value) in collection | groupBy: 'property'or 'propperty.nested'
JS:
$scope.players = [
{name: 'Gene', team: 'alpha'},
{name: 'George', team: 'beta'},
{name: 'Steve', team: 'gamma'},
{name: 'Paula', team: 'beta'},
{name: 'Scruath', team: 'gamma'}
];
HTML:
<ul ng-repeat="(key, value) in players | groupBy: 'team'" >
Group name: {{ key }}
<li ng-repeat="player in value">
player: {{ player.name }}
</li>
</ul>
<!-- result:
Group name: alpha
* player: Gene
Group name: beta
* player: George
* player: Paula
Group name: gamma
* player: Steve
* player: Scruath

Related

OrderBy array in AngularJS according to search

I'm still stuck with a OrderBy issue. Data comes from $http, and looks like this:
[
{
"number":1,
"timetable": [
{
"name":"Station1",
"time":"2016-05-18T18:14:00.000Z"
},
{
"name":"Station2",
"time":"2016-05-18T18:18:00.000Z"
}
]
},
{
"number":2,
"timetable": [
{
"name":"Station1",
"time":"2016-05-18T18:24:00.000Z"
},
{
"name":"Station2",
"time":"2016-05-18T18:28:00.000Z"
}
]
}
]
So what I need is to view rows where name is for example Station2, and I need them in order by time. The rows aren't ordered by time, nor by number. Amount of timetable rows varies, so row numbers don't help either. Is it possible to order them inside ng-repeat, in style of "OrderBy time where name='Station2' "?
EDIT:
At the moment I'm showing the results without any ordering, only with filtering. Current PHP:
<tr ng-repeat="x in rows | limitTo:5">
<td>
<a href="aikataulu.php?n={{x.number}}">
<span class="label label-primary line-{{x.lineID}}">{{x.lineID}}</span> {{x.number}}
</a>
</td>
<td>
<span ng-repeat="y in x.timetable | limitTo:-1">{{y.name}}</span> //This is for showing the destination
</td>
<td>
<span ng-repeat="y in x.timetable | filter:{'name':'<?php echo $as;?>'}: true | limitTo:1">{{y.time | date:'HH:mm'}}</span>
</td>
</tr>
$as is the station to be shown. So, now the order of the list comes straight from the JSON order, so it varies a lot.
You can use the comparator as an additional argument for the filter.
So you would expand your code to:
<span ng-repeat="y in x.timetable | filter:{'name':'<?php echo $as;?>'}: true : myComparator | limitTo:1">{{y.time | date:'HH:mm'}}</span>
You need a regexp to see if the compared string values are dates and you can do something similar to:
$scope.myComparator= function (a, b) {
// regex to see if the compared values are dates
var isDate = /(-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-)/g;
// sorting dates
if (isDate.test(a.value)) {
var aDate = new Date(a.value), bDate = new Date(b.value);
return aDate.getTime() < bDate.getTime() ? -1 : 1
}
// default sorting
return a.index < b.index ? -1 : 1
}
A thing worth noting is you would need a different regular expression to find your date format. Something along the lines of the following might be sufficient:
/(\d{4})-(\d{2})-/g
NOTE: This is untested code and serves only as a guide to the right approach. Make sure your version of AngularJS supports the comparator as a filter argument.
You can order an array of objects by one of their properties with a filter function like this one:
angular.module('yourApp').filter('orderObjectBy', function() {
return function(items, field, reverse) {
var filtered = [];
angular.forEach(items, function(item) {
filtered.push(item);
});
filtered.sort(function (a, b) {
return (a[field] > b[field] ? 1 : -1);
});
if(reverse) filtered.reverse();
return filtered;
};
}
);
Use it like this
<div ng-repeat="elem in data | orderObjectBy:'number'">
// ...
</div>

Edit an object inside several ng-repeat

I got two ng-repeat who display objects call 'post', and I have a button for edit the text and update it.
Everything works fine but I still got a little problem here's the html code :
<li ng-repeat="post in posts | filter: { etat: 'aTraiter' } ">
<p ng-show="!editing[$index]" ng-model href="#/{{post._id}}">{{ post.corps }}</p>
<input ng-show="editing[$index]" type="text" ng-model="post.corps">
<a ng-show="!editing[$index" ng-click="edit(post)">Editer</a>
<a ng-show="editing[$index]" ng-click="update(post)">Confirmer</a>
<a ng-show="editing[$index]" ng-click="cancel(post)">Annuler</a>
</li>
<li ng-repeat="post in posts | filter: { etat: 'enCours' } ">
<p ng-show="!editing[$index]" ng-model href="#/{{post._id}}">{{ post.corps }}</p>
<input ng-show="editing[$index]" type="text" ng-model="post.corps">
<a ng-show="!editing[$index]" ng-click="edit(post)">Editer</a>
<a ng-show="editing[$index]" ng-click="update(post)">Confirmer</a>
<a ng-show="editing[$index]" ng-click="cancel(post)">Annuler</a>
</li>
the controller :
$scope.editing = [];
$scope.posts= Posts.query();
$scope.edit = function(post){
var idx = $scope.posts.indexOf(post);
$scope.editing[idx] = angular.copy($scope.posts[idx]);
}
$scope.update = function(post){
var idx = $scope.posts.indexOf(post);
Posts.update({id: post._id}, post);
$scope.editing[idx] = false;
}
$scope.cancel = function(post){
var idx = $scope.posts.indexOf(post);
post = angular.copy(post);
$scope.editing[idx] = false;
}
If I got just one post I can edit it and all is ok.
But when I got one post in the both ng-repeat I got some bugs, if I click on edit, buttons change in the both ng-repeat and the both post can be edit.
I'm not really sure but I thinks it's a problem with my :
ng-show="editing[$index]"
I try to put the index of the post like this
ng-show="editing[posts.indexOf(post)]"
But this is not working, can somebody help me ?
(The jsfiddle link)
EDIT the post query :
Posts.query();: Array[0]
0: d
__v: 0
_id: "569563a96a81e64409623179"
corps: "asdad"
etat: "enCours"
nomReseau: "Google+"
section: "evolution"
__proto__: d
1: d
__v: 0
_id: "56954e676a81e6440962316b"
corps: "sdfsdfsf"
etat: "enCours"
nomReseau: "Google+"
section: "evolution"
__proto__: d
Using $index in a filtered repeat to access information in your array.
The problem with using $index, passing it to your controller and then trying to use that index to search for a 'post' in your array of posts, is that $index references your view index and not the true index of the item in the array.
This is traditionally not a problem unless you are filtering your array with ng-repeat. Why? Because $index does not reflex the index of the item, but the index of how the item is appearing in the DOM. So although the first rendered post could have index 5 in your posts array, it will still have $index of 0 because it is the first rendered item in the ng-repeat.
Solution: Separate your data first into two separate arrays and then repeat through them individually.
Controller:
$scope.posts = Posts.query();
$scope.postsATraiter = $scope.posts.filter(function(item, index) {
return item.etat === 'aTraiter';
});
$scope.postsEnCours = $scope.posts.filter(function(item, index) {
return item.etat === 'enCours';
})
$scope.edit = function(post, postType){
var idx = getPostsByType(postType).indexOf(post);
$scope.editing[idx] = angular.copy($scope.posts[idx]);
}
$scope.update = function(post, postType){
var idx = getPostsByType(postType).indexOf(post);
Posts.update({id: post._id}, post);
$scope.editing[idx] = false;
}
$scope.cancel = function(post, postType){
var idx = getPostsByType(postType).indexOf(post);
post = angular.copy(post);
$scope.editing[idx] = false;
}
function getPostsByType(postTypeString) {
if (postTypeString === 'aTraiter') {
return $scope.postsATraiter;
} else {
return $scope.postsEnCours;
}
}
Now that the data is separate you are free to use $index because we know that the $index will respect the true index of the item in the array because it is not being filtered.
<li ng-repeat="post in postsATraiter">
<p ng-show="!editing[$index]" ng-model href="#/{{post._id}}">{{ post.corps }}</p>
<input ng-show="editing[$index]" type="text" ng-model="post.corps">
<a ng-show="!editing[$index" ng-click="edit(post, 'aTraiter')">Editer</a>
<a ng-show="editing[$index]" ng-click="update(post, 'aTraiter')">Confirmer</a>
<a ng-show="editing[$index]" ng-click="cancel(post,'aTraiter')">Annuler</a>
</li>
<li ng-repeat="post in postsEnCours">
<p ng-show="!editing[$index]" ng-model href="#/{{post._id}}">{{ post.corps }}</p>
<input ng-show="editing[$index]" type="text" ng-model="post.corps">
<a ng-show="!editing[$index]" ng-click="edit(post, 'enCours')">Editer</a>
<a ng-show="editing[$index]" ng-click="update(post, 'enCours')">Confirmer</a>
<a ng-show="editing[$index]" ng-click="cancel(post, 'enCours')">Annuler</a>
</li>
You may have to fiddle around with your implementation more, but it appears that $index and the way it is being used may be the root of the problems you are having.
You're using $index, which is just ... well... an index. Both collections can have a value at index==1, right? So, $index isn't unique across the entire set of posts.
Luckily, it would appear that you have an ID for each post that seems to be unique: post._id. How about using that instead?
One little side-note - I'm using jquery's grep method below to find a post by Id. It's fine, but I like underscore.js better. Take a look at both ...
So, here's your controller code:
//This is me willfully and wantonly changing your variables ... sorry.
$scope.selectedPost = undefined;
$scope.selectedPost_unchanged = undefined;
//This is the same, though, so you should feel good ;-)
$scope.posts= Posts.query();
$scope.edit = function(postId){
var result = $.grep($scope.posts, function(p){ return p._id == postId; });
if(result.length==0) { return; }
//just store the one we are editing. that should be cool, right?
$scope.selectedPost = results[0];
//ok... im changing this too... see if you like it better?
//we're gonna use it in the CANCEL method (below).
$scope.selectedPost_unchanged = angular.copy(results[0]);
}
$scope.update = function(post){
Posts.update({id: post._id}, post);
$scope.selectedPost = undefined;
$scope.selectedPost_unchanged = undefined;
}
$scope.cancel = function(post){
post = angular.extend({}, $scope.selectedPost_unchanged);
$scope.selectedPost = undefined;
$scope.selectedPost_unchanged = undefined;
}
//This is new too ... just adding it so that the html is clearer.
$scope.isEditing = function(post) {
if($scope.selectedPost==undefined) { return false; }
return post._id == $scope.selectedPost._id;
}
The HTML changes a bit too
All of your editing[$index] code becomes just isEditing(post)
Voilà! Except maybe use css+ng-class...
Not for nothing, but I would add the while editing/not-editing show/hide using css. Then, add an ng-class to the li element instead (eg - ng-class="{editing: isEditing(post)}"). Then, take care of all your show/hides with css. This way, you only have to put isEditing(post) in ONE location in your html (instead of adding it to every element). ng tags are not expensive, but they REALLY add up inside of ng-repeat tags.

Use ng-repeat for only some items of a collection [duplicate]

I want to use parameter in filter, when I iterate some arrays with ng-repeat
Example:
HTML-Part:
<tr ng-repeat="user in users | filter:isActive">
JavaScript-part:
$scope.isActive = function(user) {
return user.active === "1";
};
But I want to be able to use filter like
<tr ng-repeat="user in users | filter:isStatus('4')">
But its not working. How can I do something like that?
UPDATE: I guess I didn't really look at the documentation well enough but you can definitely use the filter filter with this syntax (see this fiddle) to filter by a property on the objects:
<tr ng-repeat="user in users | filter:{status:4}">
Here's my original answer in case it helps someone:
Using the filter filter you won't be able to pass in a parameter but there are at least two things you can do.
1) Set the data you want to filter by in a scope variable and reference that in your filter function like this fiddle.
JavaScript:
$scope.status = 1;
$scope.users = [{name: 'first user', status: 1},
{name: 'second user', status: 2},
{name: 'third user', status: 3}];
$scope.isStatus = function(user){
return (user.status == $scope.status);
};
Html:
<li ng-repeat="user in users | filter:isStatus">
OR
2) Create a new filter that takes in a parameter like this fiddle.
JavaScript:
var myApp = angular.module('myApp', []);
myApp.filter('isStatus', function() {
return function(input, status) {
var out = [];
for (var i = 0; i < input.length; i++){
if(input[i].status == status)
out.push(input[i]);
}
return out;
};
});
Html:
<li ng-repeat="user in users | isStatus:3">
Note this filter assumes there is a status property in the objects in the array which might make it less reusable but this is just an example. You can read this for more info on creating filters.
This question is almost identical to Passing arguments to angularjs filters, to which I already gave an answer. But I'm gonna post one more answer here just so that people see it.
Actually there is another (maybe better solution) where you can use the angular's native 'filter' filter and still pass arguments to your custom filter.
Consider the following code:
<li ng-repeat="user in users | filter:byStatusId(3)">
<span>{{user.name}}</span>
<li>
To make this work you just define your filter as the following:
$scope.byStatusId = function(statusId) {
return function(user) {
return user.status.id == statusId;
}
}
This approach is more versatile because you can do comparisons on values that are nested deep inside the object.
Checkout Reverse polarity of an angular.js filter to see how you can use this for other useful operations with filter.
If you have created an AngularJs custom filter, you can send multiple params to your filter.Here is usage in template
{{ variable | myFilter:arg1:arg2... }}
and if you use filter inside your controller here is how you can do that
angular.module('MyModule').controller('MyCtrl',function($scope, $filter){
$filter('MyFilter')(arg1, arg2, ...);
})
if you need more with examples and online demo, you can use this
AngularJs filters examples and demo
This may be slightly irrelevant, but if you're trying to apply multiple filters with custom functions, you should look into:
https://github.com/tak215/angular-filter-manager
Example I have a students list as below :
$scope.students = [
{ name: 'Hai', age: 25, gender: 'boy' },
{ name: 'Hai', age: 30, gender: 'girl' },
{ name: 'Ho', age: 25, gender: 'boy' },
{ name: 'Hoan', age: 40, gender: 'girl' },
{ name: 'Hieu', age: 25, gender: 'boy' }
];
I want to filter students via gender to be boy and filter by name of them.
The first I create a function named "filterbyboy" as following:
$scope.filterbyboy = function (genderstr) {
if ((typeof $scope.search === 'undefined')||($scope.search === ''))
return (genderstr = "")
else
return (genderstr = "boy");
};
Explaination: if not filter name then display all students else filter by input name and gender as 'boy'
Here is full HTMLcode and demo How to use and operator in AngularJs example

Angular ng-repeat, nested repeat for each parent loop

I have some trouble with getting proper data. I have many-to-many relation model, so it's 3 tables, two with data, and third is connection between them (by ID's). For example, first table is stores, second is items, and third is 'have' that connects them by id.
Now i should display available items per store. I'm using ng-repeat="store in stores" to loop through stores, and trying to create function that will return me items available in each store (store.idStore).
I have tried several approaches and none of them seems to be working for me, and since I'm new to angular, Im a little bit lost. I would appreciate any help.
Last function that I used is:
function forEachStore(id) {
angular.forEach($scope.Have, function (value, index) {
if (idStore == id) {
alert(idPlayliste);
this.push(dataFact.catchData(urlItem, idItem));
}
}, $scope.storeHasItem)
}
$scope.Have --> contains json object like({"id":1, "idStore":1, "idItem":1}, {"id":2, "idStore":1, "idItem":2}, ...)
dataFact.catchData--> my factory that gets api url and idItem and returns json object(this is working correctly).
id == store.idStore sent from ng-repeat.
So, I'm sending to this function 'store.idStore', and I want it to return me all items that is available in that store.
No alert for me :)
From your code it looks like $scope.stores is an array of Store objects. I'm assuming you have something similar for items. In your first ng-repeat loop, add something like ng-repeat="item in getItemsForStore(store.idStore)" to start the inner loop, and add the following functions to your scope (not yet tested):
$scope.getItemsForStore = function(storeId) {
var items = [];
for (var have = $scope.have, i = 0, len = have.length; i < len; i++) {
var link = have[i];
if (link.idStore === storeId && !items.contains(link.idItem) {
items.push(link.idItem);
}
}
for (int j = 0, len = items.length; j < len; j++) {
items[j] = getItem(items[j]);
}
return items;
}
function getItem(itemId) {
for (var i = 0, items = $scope.items, len = items.length; i < len; i++) {
var item = items[i];
if (item.idItem === itemId) {
return item;
}
}
throw "unknown item ID: " + itemId;
}
Things could be far more efficient if you used objects instead of arrays, with items/stores keyed by ID.
function Ctrl($scope){
$scope.stores =[
{id:1,name:'store 1'},
{id:2,name:'store 2'},
{id:3,name:'store 3'},
{id:4,name:'store 4'}
];
$scope.items =[
{id:1, name:'item 1'},
{id:2, name:'item 2'},
{id:3, name:'item 3'},
{id:4, name:'item 4'},
{id:5, name:'item 5'},
{id:6, name:'item 6'},
];
var storeItemLinked= [
{sId:1,iId:1},
{sId:1,iId:2},
{sId:1,iId:3},
{sId:2,iId:4},
{sId:2,iId:5},
{sId:2,iId:6},
{sId:3,iId:1},
{sId:3,iId:3},
{sId:3,iId:5},
{sId:4,iId:2},
{sId:4,iId:4},
{sId:4,iId:5}
];
$scope.selectedStoreItems=[];
$scope.showStoreItems=function(store){
$scope.selectedStoreItems=[];
$scope.selectedStore=store;
var itemIds=[];
for(var i=0;i<storeItemLinked.length;i++){
if(storeItemLinked[i].sId==store.id)
itemIds.push(storeItemLinked[i].iId);
}
for(var j=0;j<$scope.items.length;j++){
if(itemIds.indexOf($scope.items[j].id)>=0)
$scope.selectedStoreItems.push($scope.items[j]);
}
}
$scope.showAvailableStores = function(item){
$scope.selectedItem=item;
$scope.selectedItemStores=[];
var storeIds=[];
for(var i=0;i<storeItemLinked.length;i++){
if(storeItemLinked[i].iId==item.id)
storeIds.push(storeItemLinked[i].sId);
}
for(var j=0;j<$scope.stores.length;j++){
if(storeIds.indexOf($scope.stores[j].id)>=0)
$scope.selectedItemStores.push($scope.stores[j]);
}
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.11/angular.min.js"></script>
<div ng-app ng-controller="Ctrl">
<ul>
<li ng-repeat="store in stores" ng-click="showStoreItems(store)">{{store.name}}
<ul ng-show="selectedStore==store">
<li ng-repeat="item in selectedStoreItems" ng-click="showAvailableStores(item)">
{{item.name}}
<ul ng-show="selectedItem==item">
<li ng-repeat="store in selectedItemStores">{{store.name}}</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
click the store will list all available items then click an item will list all stores that sell that item
I just mocked up the database by some js objects and you may want to change those ng-click functions to get remote data and the logic inside will depends on the view model return from server
Edit:
On a comment inspired second read :).
I thinkidStore should be value.idStore(here:
angular.forEach($scope.Have, function (value, index) {
if (idStore == id) { ... })};
)
And also:
angular.forEach(obj, iterator, [context]), where context will provide 'this' for the iterator. You're setting context to $scope.storeHasItem - a function on the $scope, I guess it's not what you're after.
Old answer:
When you're using ng-repeat="store in stores" - you get something like this: store = stores[0] in the scope + a dom template in the view (a copy of the element on which you've put the repeat)...then store = stores[1] and a copy...and so on
So in the function, the parameter you're getting is a whole store object, not just the id. Try a console.log - it should clarify things.

Angular checkbox filtering data list

Seen a few options for filtering data via checkboxes but it all seems fairly overly complicated for something I'd expect Angular to do easily.
Take a nose at http://plnkr.co/edit/Gog4qkLKxeH7x3EnBT0i
So there are a few filters in place here but the ones I'm interested in are the checkboxes. Using a pretty nifty Angular UI module I found called Unique, it lists the different types of providers and rather than repeating them, just lists one of each type. Lovely stuff.
However I can't get that to filter the results set below. However if I take the rendered markup from the generated checkboxes and put that directly into the HTML, it works, even though it is the same. Madness.
I don't understand filtering enough, so what am I doing wrong? I was hoping to use the unique module for a couple of other checkbox filters. Like door numbers, etc.
Here is a solution; showing diffs only:
In index.html modify the relevant lines as follows:
<li data-ng-repeat="result in results | unique: 'provider.name'">
<input type="checkbox"
id="cb_{{ result.provider.providerId }}"
data-ng-model="checkbox[result.provider.providerId]"
/>
<label for="cb_{{ result.provider.providerId }}">{{ result.provider.name }}</label>
</li>
...
<li data-ng-repeat="result in ( filteredItems = (results | filter: searchByCarClass | filter: selectCarClass | filter: searchByCheckbox )) | orderBy:orderByFilter">
...
</li>
In script.js add:
$scope.checkbox = {};
var showAll = true;
$scope.searchByCheckbox = function(result) {
return showAll || $scope.checkbox[result.provider.providerId];
};
$scope.$watch("checkbox", function(newval, oldval) {
showAll = true;
angular.forEach($scope.checkbox, function(val) {
if( val === true ) showAll = false;
});
}, true);
(EDIT) Changed the key to $scope.checkbox to providerId. Filter starts disabled, so all entries are shown.
Good luck!
Just for the fun of it, I implemented a solution that has a much simpler API (wish of Leads in comments). Here it goes:
Add the cbFilter dependency to the controller, remove all the checkbox-related code and replace it as follows; this is the new API (it can't get any simpler :)
app.controller('resultsData', function($scope, $http, cbFilter){
...
$scope.checkbox = cbFilter($scope, "provider.providerId");
...
}
Rreplace the filter in the list (notice searchByCheckbox is replaced by checkbox):
<li data-ng-repeat="result in ( filteredItems = (results | filter: searchByCarClass | filter: selectCarClass | filter: checkbox )) | orderBy:orderByFilter">
And, finally, add the service:
app.factory("cbFilter", ["$parse", function($parse) {
return function($scope, matchExpression) {
var showAll = true,
getter = $parse(matchExpression),
filter = function(data) {
if( showAll ) return true;
return filter[getter(data)] === true;
},
unwatch = $scope.$watch(
function() {
var x, ret = {};
for( x in filter ) {
if( !filter.hasOwnProperty(x) ) continue;
ret[x] = filter[x];
}
return ret;
},
function() {
showAll = true;
angular.forEach(filter, function(val) {
if( val === true ) showAll = false;
});
},
true
);
$scope.$on("$destroy", unwatch);
return filter;
};
}]);
The implementation is much more complex than before, and probably slower. However the API is much simpler (one-liner).

Resources