AngularJS orderby refining - angularjs

I have a table of names starting with a title (Mr, Mrs, etc) and dates stored as strings plus some other data.
I am currently sorting it using
<tr dir-paginate="booking in bookingResults | orderBy:sortType:sortReverse | filter:searchPassenger | itemsPerPage: 15">
How could I refine my orderBy to sort names excluding the title (Mr, Mrs, etc) and dates as parsed dates not strings.
What would be best practice here?
EDIT :
I don't want to change the names in the model by the way - I want the format to remain "Mr Foo" and "Mr Bar" but when I sort them I want them to act as if they were just "Foo" and "Bar".
EDIT EDIT :
AngularJS 1.5.6

getting the right data in the right format
title & name
I'd use a regexp to pull the title from the name:
var regex = /((Dr\.|Mr\.|Ms\.|Miss|Mrs\.)\s*)/gmi
objName.replace(regex, '')
date
I'm assuming you're getting either a date object or a standard date string. If it's the latter, just create a Date object via new Date(incomingDateString). Then you can call:
objDate.getTime() //returns epoch in milliseconds
sorting
Some people might dislike this but I hate dirtying up view controllers with methods that NG directives need to use for things like ordering. Instead, I added some ng-flagged properties using ng-init on each row item. Then I can sort based off that. I didn't do it for the date in the example but you could extrapolate and apply.
ng-init w. ng-flagged properties
<tr ng-repeat="row in vc.listData | orderBy:vc.sortKey track by $index"
ng-init="row.$name = row.name.replace(vc.regexp, '')">
So in other words your objects go from this:
{
name:'Mr. Fred Rogers',
date:<date-object>
}
to this thanks to ng-init:
{
name:'Mr. Fred Rogers',
date:<date-object>,
$name:'Fred Rogers',
$date:1466192224091
}
And then via your sorting UI, you can set your $scope.sortKey to either $name or $date.
code pen
I made a sample in code pen but I did it with my template which is coffeescript and jade. You can probably figure out what I'm doing.
pen - http://codepen.io/jusopi/pen/aZZjgG?editors=1010

Ok, after some research, I found that the easiest solution is upgrading to AngularJS version 1.5.7 which introduces the comparator into the orderBy filter.
So I've changed my repeater to use an order by comparator
<tr dir-paginate="booking in Results | orderBy:Variable:TrueOrFalse:bookingComparator">
Variable is a string which I bound to the table headings so you can change the order by key, TrueOrFalse is a boolean which alternates between ascending and descending if you click the table heading and bookingComparator is my actual comparator.
My booking comparator looks like this
$scope.bookingComparator = function (a, b) {
var getTitle = /((Mrs|Mr|Mstr|Miss|Dr)\s*)/g;
var isDate = /(-(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)-)/g
if (getTitle.test(a.value)) {
var aName = a.value, bName = b.value;
return aName.replace(getTitle, '') < bName.replace(getTitle, '') ? -1 : 1
}
if (isDate.test(a.value)) {
var aDate = new Date(a.value), bDate = new Date(b.value);
return aDate.getTime() < bDate.getTime() ? -1 : 1
}
return a.index < b.index ? -1 : 1
}
The comparator is basically a function acting like the javascript .sort() method.
If the value contains a title (Mr, Mrs, etc) it is a name so I strip the titles and compare the actual names regardless of title.
If the variable matches a -Month- pattern, it's a date string and I compare the parsed date objects.
Hope this is helpful to someone, took me a while to figure out. I'm open to suggestions if you think there's a better way of doing this, and feel free to post an answer for people who want to use AngularJS =< 1.5.6

Related

AngularJS how to have multiple $filter in a controller?

The leftValue array contains JSON information such as { name: 'John', gender: 'male' }
I am trying to filter information by both name and gender in a search bar. For now, the filter only filtered by name. How can I filter by both name and gender?
scope.leftValue = $filter('filter')( leftValue, {
'name': text
})
What I have tried
scope.leftValue = $filter('filter')( leftValue, {
'name': text,
'gender': text,
})
I think your getting a little mixed up. You can specify an object map like your second example. This is what the docs state about it when you do.
Object: A pattern object can be used to filter specific properties on
objects contained by array. For example {name:"M", phone:"1"}
predicate will return an array of items which have property name
containing "M" and property phone containing "1". A special property
name $ can be used (as in {$:"text"}) to accept a match against any
property of the object or its nested object properties. That's
equivalent to the simple substring match with a string as described
above. The predicate can be negated by prefixing the string with !.
For example {name: "!M"} predicate will return an array of items which
have property name not containing "M".
The important thing to take away here is the second sentence and the and. Meaning in order for a match the string, in your case text, has to match ALL properties specified in your map.
Searching for just male wont match if the name is only John for example. But searching for ma would return the following record:
{
name: 'mark',
gender: 'male'
}
Just for FYI you can also search by object map through the view but it has the same limitations.
That being said it is possible to use the $ wildcard giving it a comma separated list of properties. This will do an or match over any of the properties.
{
$: 'name,gender'
}
The catch here is all properties will have the same value checked against them.
Here's a fiddle showing them in action.
The other answers sum up quite well the alternatives, just felt they were lacking in explaining what was happening and the reasons behind it.
You can use chain of filters.
var result1 = $filter('filter')( leftValue, {'name': text })
var result2 = $filter('filter')( result1 , {'gender': text })
i appreciate you said in the controller, but just to give you options you can have it all done in your HTML.
{{yourJSONLinkedToScope | filter: name | filter: gender}}
where name and gender are ng-models from the input boxes.
I think better we do it in html.
<tr ng-repeat="player in players | filter:{id: player_id, name:player_name} | filter:ageFilter">
$scope.ageFilter = function (player) {
return (player.age > $scope.min_age && player.age < $scope.max_age);
}
You can check the way they do in this link:
AngularJS multiple filter with custom filter function

Filter ng-repeat more times

I'm creating an application to manage restaurant orders.
I create the menu from $http so I've this list:
<div class="row vertical" style="background-image: url(/gest/images/etichette/ANTIPASTI.png);border-color: #0CF">
<div class="card piatti col s2" ng-repeat="anti in antis | filter:{tipo:'ANTIPASTI'}">
<div class="card-content"> <span class="card-title truncate red darken-3">{{anti.piatto}}</span> </div>
<div class="card-action"> {{n}}</div>
</div>
</div>
The div with class "row vertical" contain one time starters, then pasta, then beef ecc.
So I use ng-repeat each time, and filter by tipo.
My question is: is there any way to make ng-repeat only one time to show all menu (orderer before by starters, then pasta, beef ecc)?
I have this data (is a restaurant menu):
piatto: name of the the dish
tipo: category of the dish (like pasta, beef, fish, starters ecc)
I would show with only one repeat all the dishes ordered so:
starters, pasta, beef, fish, dessert etc.
And I would create each time a new row
From what I understand you already have all your date on the antis and you just want to filter it by type or do you want to OrderIt by a certain type?
This fiddle for example would order by name, but you can also provide an array with functions to retrieve each type in the way that you like, you can read about it here.
But basically you'd do
anti in antis | orderBy:'+tipo'
or
anti in antis | orderBy: [ function(){}, function(){} ]
EDIT:
As #yarons mentioned you can also chain strings to filter even further. I've updated the Fiddle so now the filter would be anti in antis | orderBy:['+tipo', '+piato']" which indicates that first the tipo would be alphabetically ordered ascending (+ indication) and after that the piato would also be alphabetically ascending.
If you'd want to define a different order than the alphabetical one I think you can use a sort of ENUM for the tipo as in:
var tipoENUM = {};
tipoENUM['ANIPASTI'] = 0;
tipoENUM['PASTA'] = 1;
tipoENUM['PIZZA'] = 2;
tipoENUM['BEEF'] = 3;
tipoENUM['DESERT'] = 4;
So that way you'd avoid using the string for the order, see following fiddle for the example.
EDIT 2:
Ok, so if you receive the data via the HTTP request it's better if you create a order function to help you, check this updated fiddle, like so:
// The enum would be defined as before but:
$scope.orderTipo = function (dish) {
return tipoENUM[dish.tipo];
}
On the HTMl you'll do:
ng-repeat="anti in antis | orderBy:[orderTipo, '+piato']"
Ok your example is perfect but I would repeat each time the "tipo" and then the relative "piato" in a list....something like this:
ANTIPASTI
- bruschetta
- suppli
- fritto
PRIMI
- caqrbonara
- amatriciana
etc.
Is it possible?

Angular JS Filter On date

I was wondering if anyone could help me with filter on dates within ng-repeat
I have a text field that I enter the search text into for filter the results in my table
take this cut down example`
<tr id="credentialsData" ng-repeat="credential in credentials.data | filter:credentialsSearchText">
<td>{{credential.createdDate | date:'medium'}}</td>
</tr>`
credential.createdDate comes back in the rest call in the format 2015-03-24T21:19:49Z
When I attach the medium date filter - it displays as Mar 24, 2015 9:19:49 PM
However when i search on the String Mar or 9:, I get no results. Angularjs searches on the base object and ignores the filter.
I have read other options online where the person recommends adding different date formats into the json object but unfortunately that is not an option for me
Any help on this would be appreciated
Cheers
Damien
You can use a custom function for the filter.
https://docs.angularjs.org/api/ng/filter/filter
<tr id="credentialsData" ng-repeat="credential in credentials.data | filter:credentialsSearchText:compareCredentialDate">
<td>{{credential.createdDate | date:'medium'}}</td>
</tr>
In your controller, put a
$scope.compareCredentialDate = function(credential, expected) {
// you have to inject '$filter' to use this:
var dateFilter = $filter('date');
// this is the value that "credential.createdDate | date:'medium'"
// evaluates to:
var formattedDateString = dateFilter(credential.createdDate, 'medium');
// hypothetical matching method - you can implement whatever you
// want here:
var isMatch = formattedDateString.indexOf(expected) >= 0;
return isMatch;
}

Order By Date and truncate date strings in Angular

I have a json file with a released field that comes back with such format:
released: "2002-01-28"
I intend to display them sorted by date (earlier first) and only showing the year. I've used the truncate module (in my example, release: 4) and so far its showing only the first 4 characters, but I haven't succeed using orderby to sort it correctly.
Any pointers?
Also, in some items the released field comes back empty, any quick way to display just a "unknown" instead of a blank space?
Thanks!
<li ng-show="versions" ng-repeat="version in versions | filter: '!file' | orderBy: version.released">
{{version.released | release:4}} - {{version.format}} - {{version.label}}
</li>
Here is a date formatting filter I use. It takes a date and converts it into whatever format you wish, in your case, 'yyyy'. Bind the raw date stamp in your template and then 'orderBy' should work fine. This is how I always do it. Oh, you might not want the replace() function... that was specific to my last project.
.filter('DateFormat', function($filter){
return function(text){
if(text !== undefined){
var tempdate = new Date(text.replace(/-/g,"/"));
return $filter('date')(tempdate, "MMM. dd, yyyy");
}
}
})
You can show unknown by doing {{version.released || 'unknown'}}.
If you only want to show the year do this {{ (version.released | date : date : 'YYYY' ) || 'unknown'}}

How to extend or override existing filters in angularjs?

Is it possible to extend existing "standard" filters (date, number, lowercase etc)?
In my case I need to parse date from YYYYMMDDhhmmss format so I'd like to extend (or override) date filter instead of writing my own.
I prefer to implement the decorator pattern, which is very easy in AngularJS.
If we take #pkozlowski.opensource example, we can change it to something like:
myApp.config(['$provide', function($provide) {
$provide.decorator('dateFilter', ['$delegate', function($delegate) {
var srcFilter = $delegate;
var extendsFilter = function() {
var res = srcFilter.apply(this, arguments);
return arguments[2] ? res + arguments[2] : res;
}
return extendsFilter;
}])
}])
And then in your views, you can use both.. the standard output and the extended behavior. with the same filter
<p>Standard output : {{ now | date:'yyyyMMddhhmmss' }}</p>
<p>External behavior : {{ now | date:'yyyyMMddhhmmss': ' My suffix' }}</p>
Here is a working fiddle illustrating both techniques:
http://jsfiddle.net/ar8m/9dg0hLho/
I'm not sure if I understand your question correctly, but if you would like to extend functionality of existing filters you could create a new filter that decorates an existing one. Example:
myApp.filter('customDate', function($filter) {
var standardDateFilterFn = $filter('date');
return function(dateToFormat) {
return 'prefix ' + standardDateFilterFn(dateToFormat, 'yyyyMMddhhmmss');
};
});
and then, in your template:
{{now | customDate}}
Having said the above, if you simply want to format a date according to a given format this can be done with the existing date filter:
{{now | date:'yyyyMMddhhmmss'}}
Here is the working jsFiddle illustrating both techniques: http://jsfiddle.net/pkozlowski_opensource/zVdJd/2/
Please note that if a format is not specified AngularJS will assume that this is 'medium' format (the exact format depends on a locale). Check http://docs.angularjs.org/api/ng.filter:date for more.
The last remark: I'm a bit confused about the 'parse from' part of your question. The thing is that filters are used to parse an object (date in this case) to string and not vice verse. If you are after parsing strings (from an input) representing dates you would have to look into NgModelController#$parsers (check the "Custom Validation" part in http://docs.angularjs.org/guide/forms).

Resources