OrderBy array in AngularJS according to search - angularjs

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>

Related

Angularjs filter complex JSON by array

I'm having a dropdown list with multiselect option. I want to filter out data from a complex JSON based on that array.
Selected options forms a array of data like:
$scope.myval=["Adyar","Paris","central"];
My JSON :
$scope.myTest={
"buslist":
{
"code":"1",
"message":"Success",
"fromStationCode":"71",
"searchResult":[ {
"arrivalTime":"17:00:00",
"availableSeats":"42",
"boardingPointDetails":[{
"code":"1631",
"name":"Koyambedu",
"time":"09:30:00"
},
{
"code":"961296",
"name":"Paris",
"time":"09:45:00"
}
]
]
},
{
"arrivalTime":"18:00:00",
"availableSeats":"32",
"boardingPointDetails":[{
"code":"2084",
"name":"Adyar",
"time":"09:30:00"
},
{
"code":"961296",
"name":"Madurai",
"time":"09:45:00"
}
]
]
}
}
...
};
My HTML templating is:
<tbody ng-repeat=" i in myTest.buslist.searchResult" >
<tr>
<td>{{i.arrivalTime}}</td>
<td>{{i.availableSeats}}</td>
<td>
<p ng-repeat="m in i.boardingPointDetails">{{m.name}}</p>
</td>
</tr>
</tbody>
I want to filter my data based on selected values. I had tried something like this :
$scope.matched = $scope.myTest.buslist.searchResult.boardingPointDetails.name.indexOf(data);
i.e:selected options must match "name" field in "boardingPointDetails" but it fails. Thanks in advance.
Since $scope.myTest.buslist.searchResult.boardingPointDetails is an array
$scope.myTest.buslist.searchResult.boardingPointDetails.name is not valid.
You need to use an Array function to get the correct result:
$scope.matched = $scope.myTest.buslist.searchResult.boardingPointDetails.filter(function(el) {
return el.name === data;
}).length > 0;
EDIT:
Due to your comments I understand you want to get the boardPointDetails that has the same name property as one of the data options. Where data is an Array of strings.
This will do the job:
$scope.matched = $scope.myTest.buslist.searchResult.boardingPointDetails.filter(function(el) {
return data.indexOf(el.name) === 1;
});

AngularJS : How to filter one object attribute with passed argument

I have meet a problem, I try to filter the query result, I want to filter the item.type. The filter value is bounded on the input field.
I try the following code, but it doesn't work. How can I make it correct? Thanks.
Search: <input ng-model="query">
Current filter: {{query}}
<table>
<tr ng-repeat="item in items | filter : {type : 'query'}">
<td>{{item.type}}</td>
<td>{{item.host}}</td>
</tr>
</table>
Use a custom filter
$scope.filterByType = function(item) {
return item.type == "query";
}
And the use:
<tr ng-repeat="item in items | filter : filterByType">
Just remove the quotes:
filter : {type : query}
Demo
I try the following code, it will return all when no input in the query filter.
$scope.filterByType = function(item) {
if ($scope.query == undefined || $scope.query.length == 0) {
return item;
}
return item.type.toLowerCase().indexOf($scope.query.toString().toLowerCase()) >=0 ;
};

Angular - Filter an array

I was wondering how I can filter an array to show only results if a property in the array is empty. I have tried the following but to no avail:
<tr ng-repeat="performanceOrder in performanceOrders | filter: salesId.length === 0">
I only want to show results if salesId is empty, is this possible?
Edit
salesId is a property of performanceOrder
performanceOrder: {
salesId: "S273626",
status: "Open",
...
}
With ngShow:
<tr ng-show="!salesId.length" ng-repeat="performanceOrder in performanceOrders">
With ngIf:
<tr ng-if="!salesId.length" ng-repeat="performanceOrder in performanceOrders">
You can use a function inside the filter like this:
<tr ng-repeat="performanceOrder in performanceOrders | filter: myFilter()>
Then, in your corresponding controller, you can do something like:
$scope.myFilter = function (val) {
return val.salesId.length === 0;
}

angularjs - filter by multiple models

This seems like it must be simple, I just cannot find the answer.
Let's say I have an array of data, set out like the following:
friends = [{name:'John', age:60, location:'Brighton', street:'Middle Street'},
{name:'Bob', age:5, location:'Brighton', street:'High Street'}];
Now, I want to filter the data based on a text input like so:
<input ng-model="searchText">
<ul>
<li ng-repeat="friend in friends | orderBy:'name' | filter:searchText">
{{friend.name}} - {{friend.location}}</li>
</ul>
This works fine but it filters the input text based on every attribute of the friend object (name, age, location and street). I'd like to be able to filter based on name and location only (ignoring age and street). Is this possible without a custom filter?
Yes, it's possible by simply passing a predicate to the filter instead of a string:
<li ng-repeat="friend in friends | orderBy:'name' | filter:friendContainsSearchText">
$scope.friendContainsSearchText = function(friend) {
return friend.name.indexOf($scope.searchText) >= 0 || friend.location.indexOf($scope.searchText) >= 0
}
Here is how we do it with a custom filter.
DEMO: http://plnkr.co/edit/q7tYjOvFjQHSR0QyGETj?p=preview)
[array] | search:query:columns:operator
> query: this is the term you are looking for
> columns: an array of the names of the properties you want to look for (if empty, will use the angular filter with query)
> operator: a boolean to switch between OR (true) and AND (false, default)
html
<ul>
<li ng-repeat="item in list | search:query:['name','location']:operator">
<pre>{{item | json}}</pre>
</li>
</ul>
js
app.filter('search', function($filter) {
return function(input, term, fields, operator) {
if (!term) {
return input;
}
fields || (fields = []);
if (!fields.length) {
return $filter('filter')(input, term);
}
operator || (operator = false); // true=OR, false=AND
var filtered = [], valid;
angular.forEach(input, function(value, key) {
valid = !operator;
for(var i in fields) {
var index = value[fields[i]].toLowerCase().indexOf(term.toLowerCase());
// OR : found any? valid
if (operator && index >= 0) {
valid = true; break;
}
// AND: not found once? invalid
else if (!operator && index < 0) {
valid = false; break;
}
}
if (valid) {
this.push(value);
}
}, filtered);
return filtered;
};
});
Alternatively you can use:
<li ng-repeat="friend in friends | orderBy:'name' | filter:{ name :searchText}">
You can put several filters just like ....
<div>
<input ng-model="Ctrl.firstName" />
<input ng-model="Ctrl.age" />
<li ng-repeat = "employee in Ctrl.employees | filter:{name:Ctrl.firstName} | filter:{age:Ctrl.age}">{{employee.firstName}}</li>
</div>

angular grouping filter

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

Resources