Angularjs orderBy: how to call default comparator from custom sort function - angularjs

I have a custom sort function to convert strings to numbers for one column in a grid. The other columns are comprised of strings or date values. I have a custom sort function which checks for the number column and performs the conversion, but for the other values I want the default comparator's behavior. How do I achieve this?
$scope.sort = function (keyname) {
$scope.sortKey = keyname;
$scope.reverse = !$scope.reverse;
};
$scope.sortSubGrid = function (a, b) {
if ($scope.sortKey === 'caseNumber') {
return = parseInt(b.value) -parseInt(a.value);
}
else {
return = a.value > b.value;
}
};
<tr dir-paginate="agreement in agreements|orderBy:sortKey:reverse:sortSubGrid" ...>
The second half of the sortSubGrid function is intended to sort the non-numeric columns, but isn't achieving the results I expect, which is simply the results the default comparator provides. How do I get the default behavior for this second clause?

According to the code in orderBy.js you can use either the default or the custom but no both as you describe, it would be a nice functionality though.
// Define the `compare()` function. Use a default comparator if none is specified.
var compare = isFunction(compareFn) ? compareFn : defaultCompare;
So you can always get the original defaultCompare code and modify it.
// source orderBy.js
function defaultCompare(v1, v2) {
var result = 0;
var type1 = v1.type;
var type2 = v2.type;
if (type1 === type2) {
var value1 = v1.value;
var value2 = v2.value;
if (type1 === 'string') {
// Compare strings case-insensitively
value1 = value1.toLowerCase();
value2 = value2.toLowerCase();
} else if (type1 === 'object') {
// For basic objects, use the position of the object
// in the collection instead of the value
if (isObject(value1)) value1 = v1.index;
if (isObject(value2)) value2 = v2.index;
}
if (value1 !== value2) {
result = value1 < value2 ? -1 : 1;
}
} else {
result = type1 < type2 ? -1 : 1;
}
return result;
}

Related

angular1.x currency filter w/ no zero

I have a requirement to not render a currency value if it is zero.
i.e.:
v=1 render $1
v=0 render nothing
[ EDIT ]
:egg on face:
Once I looked deeper into the core code, I found there is a custom filter.
angular
.module('com.td.tdct.bbpcCore')
.filter('currencyFilter', ['$rootScope', 'tdBbpcUserService', '$filter', function ($rootScope, tdBbpcUserService, $filter) {
return function (input, decimal, symbol) {
var languageCd;
if (input) {
if (!$rootScope.languageChange) {
languageCd = tdBbpcUserService.getLanguageCd();
} else {
languageCd = $rootScope.languageChange;
}
var value = input.toString().replace(/[^\d|\-+|\.+]/g, '');
value = (angular.isNumber(decimal)) ? $filter('number')(value, decimal) : $filter('number')(value);
if (angular.isString(languageCd) && (languageCd.toUpperCase() === "FR" || languageCd.toUpperCase() === "FR_CA")) {
value = (angular.isDefined(symbol) && symbol === "N") ? value.toString() : value.toString() + " $";
} else {
value = (angular.isDefined(symbol) && symbol === "N") ? value.toString() : "$ " + value.toString();
}
return value;
}
};
}]);
It's implemented thus:
{{asset.prevMarginalRate | currencyFilter:0}}
I'd like to either
add an optional flag to this existing filter - to return empty if set (it's got to be optional, since existing methods that use this shouldn't be affected)
or
layer another filter on top.
whichever is best practice.
In the first case, I'm trying to figure out how to pass that flag in, sort of thus:
{{asset.prevMarginalRate | currencyFilter:0, true}}
You need to create your own filter for achieve this.
I would do something like this:
app.filter('currencyWithoutZero', function() {
return function(x) {
if(x !== 0) return '$' + x;
else return '';
};
});
Working demo
0 is falsy. So you can do an OR operation to remove if like any false / null / empty values.
function foo(x) {
return x || "nothing";
}
v = 1;
console.log(foo(v));
v = 0;
console.log(foo(v));

Angular table sorting

I need to sort table items by clicking table header. My column values number as string. So sorting is not applying as I wish. I know I should convert my string into float. I need comma separator and double point precision as well. I tried to do parseFloat but that's not working.
Example. I want to sort "123,456.00", "345", "-34.00", "7.78" kind of strings. I shared jsfiddle link to explain my scenario.
http://jsfiddle.net/k4seL1yx/1/
function SortableTableCtrl() {
var scope = this;
scope.head = {
a: "Poured",
b: "Sold",
c: "Loss"
};
scope.body = [{"ID":"September-2016", "Product":"September-2016", "Poured":"111759.07", "Sold":"107660.97", "Loss":"-4098.10", "Variance":"-3.67", "startDate":"2016-09-01", "endDate":"2016-09-22"}, {"ID":"November-2015", "Product":"November-2015", "Poured":"53690.25", "Sold":"52953.60", "Loss":"-736.65", "Variance":"-1.37", "startDate":"2015-11-20", "endDate":"2015-11-30"}, {"ID":"May-2016", "Product":"May-2016", "Poured":"156401.65", "Sold":"151192.51", "Loss":"-5209.14", "Variance":"-3.33", "startDate":"2016-05-03", "endDate":"2016-05-31"}, {"ID":"March-2016", "Product":"March-2016", "Poured":"49260.22", "Sold":"49399.14", "Loss":"138.92", "Variance":"0.28", "startDate":"2016-03-01", "endDate":"2016-03-09"}, {"ID":"June-2016", "Product":"June-2016", "Poured":"162126.88", "Sold":"161718.62", "Loss":"-408.26", "Variance":"-0.25", "startDate":"2016-06-01", "endDate":"2016-06-30"}, {"ID":"July-2016", "Product":"July-2016", "Poured":"160185.68", "Sold":"154882.40", "Loss":"-5303.28", "Variance":"-3.31", "startDate":"2016-07-01", "endDate":"2016-07-31"}, {"ID":"January-2016", "Product":"January-2016", "Poured":"355509.26", "Sold":"179696.72", "Loss":"-175812.54", "Variance":"-49.45", "startDate":"2016-01-01", "endDate":"2016-01-31"}, {"ID":"February-2016", "Product":"February-2016", "Poured":"150980.73", "Sold":"146248.72", "Loss":"-4732.01", "Variance":"-3.13", "startDate":"2016-02-01", "endDate":"2016-02-29"}, {"ID":"December-2015", "Product":"December-2015", "Poured":"167843.42", "Sold":"163732.95", "Loss":"-4110.47", "Variance":"-2.45", "startDate":"2015-12-01", "endDate":"2015-12-31"}, {"ID":"August-2016", "Product":"August-2016", "Poured":"168853.51", "Sold":"160024.84", "Loss":"-8828.67", "Variance":"-5.23", "startDate":"2016-08-01", "endDate":"2016-08-31"}]
scope.sort = {
column: 'b',
descending: false
};
scope.selectedCls = function(column) {
return column == scope.sort.column && 'sort-' + scope.sort.descending;
};
scope.changeSorting = function(column) {
var sort = scope.sort;
if (sort.column == column) {
sort.descending = !sort.descending;
} else {
sort.column = column;
sort.descending = false;
}
};
}
I found a solution for my question. Actually I am very focused on convert "poured, sold, loss" from string to float. But why I should not create a new column as clone of this and the format of that is float. parseFloat done the magic. So I can use Poured value for table show and use pouredClone value for sorting on the behind.
//Initially I got the above scope.body only. To perform sorting I modified scope.body as like below
scope.body.forEach(function(value) {
value.pouredClone = parseFloat(value.Poured)
value.soldClone = parseFloat(value.Sold)
value.lossClone = parseFloat(value.Loss)
value.varianceClone = parseFloat(value.Variance)
});
Here is the link of fiddle with solution.
http://jsfiddle.net/q7mcu6fx/1/

Issue using filter AngularJS

I'm using $filter to iterate through an array and fetch a specific value
Below is my code:
var selected = $filter('filter')($scope.folders, {url: el.selected[0] });
This code is working, but I got a problem when the url contain an accent and space like so :
/Users/Me/project/products/Poste à souder
In that case the string comparaison isn't working anymore.
What is the cleaner way to solve this situation ?
That true. As a francophone, I've often encounter encoding/decoding issues with angularjs.
The source code of the default filter is as follow
function filterFilter()
{
return function(array, expression, comparator)
{
if (!isArrayLike(array))
{
if (array == null)
{
return array;
}
else
{
throw minErr('filter')('notarray', 'Expected array but received: {0}', array);
}
}
var expressionType = getTypeForFilter(expression);
var predicateFn;
var matchAgainstAnyProp;
switch (expressionType)
{
case 'function':
predicateFn = expression;
break;
case 'boolean':
case 'null':
case 'number':
case 'string':
matchAgainstAnyProp = true;
//jshint -W086
case 'object':
//jshint +W086
predicateFn = createPredicateFn(expression, comparator, matchAgainstAnyProp);
break;
default:
return array;
}
return Array.prototype.filter.call(array, predicateFn);
};
}
and the predicate generator stand as follow: it generate the default comparator if the provided one is not a function
function createPredicateFn(expression, comparator, matchAgainstAnyProp)
{
var shouldMatchPrimitives = isObject(expression) && ('$' in expression);
var predicateFn;
if (comparator === true)
{
comparator = equals;
}
else if (!isFunction(comparator))
{
comparator = function(actual, expected)
{
if (isUndefined(actual))
{
// No substring matching against `undefined`
return false;
}
if ((actual === null) || (expected === null))
{
// No substring matching against `null`; only match against `null`
return actual === expected;
}
if (isObject(expected) || (isObject(actual) && !hasCustomToString(actual)))
{
// Should not compare primitives against objects, unless they have custom `toString` method
return false;
}
actual = lowercase('' + actual);
expected = lowercase('' + expected);
return actual.indexOf(expected) !== -1;
};
}
predicateFn = function(item)
{
if (shouldMatchPrimitives && !isObject(item))
{
return deepCompare(item, expression.$, comparator, false);
}
return deepCompare(item, expression, comparator, matchAgainstAnyProp);
};
return predicateFn;
}
Too much speech. You have the choice:
Provide a comparator to your filter see the doc
but remember that you can't define inline function in angular template
you can define a function in that scope, but it will only be available in that scope
You can write your own filter
.filter('myCustomFilter', function()
{
return function(input, criteria)
{
... // your logic here
return ...// the filtered values
};
})
Maybe it's best to write your own filter:
app.filter("customFilter", function () {
//the filter will accept an input array, the key you want to look for and the value that the key should have
return function (array, key, value) {
return array.filter(function(x){
return (x.hasOwnProperty(key) && (x[key] === value));
});
};
});
And use it in your controller like:
$scope.filtered = $filter("customFilter")($scope.folders, "url", "/Users/Me/project/products/Poste à souder");
Check out a working demo here.

Stuck on AngularJS Filter

I have the below custom filter that will truncate text after a certain number of characters:
test.app.filter('cut', function () {
return function (value, wordwise, max, tail) {
if (!value) return '';
max = parseInt(max, 10);
if (!max) return value;
if (value.length <= max) return value;
value = value.substr(0, max);
if (wordwise) {
var lastspace = value.lastIndexOf(' ');
if (lastspace != -1) {
value = value.substr(0, lastspace);
}
}
return value + (tail || ' …');
};
});
If I apply it to ng-options, it does not work - the options in the drop down disappear:
<select name="country" class="form-control" ng-options="country.value as country.label for country in countries | cut:true:20:' ...'" ng-model="address.Country" placeholder="{{'Country' | r | xlat}}" ng-required="address.IsCustEditable != false" ng-disabled="address.IsCustEditable == false" />
It works great though in other elements, such as this:
<strong>{{v.ExternalID | cut:true:20:' ...'}}</strong>
What am I doing wrong?
You are passing the an object to the filter when it expects a string.
That's why when you do :
<strong>{{v.ExternalID | cut:true:20:' ...'}}</strong>
It works. If you did this :
<strong>{{v | cut:true:20:' ...'}}</strong>
You would have a similar issue.
You could rewrite the filter to take and object, like this :
test.app.filter('cut', function () {
return function (obj, wordwise, max, tail) {
var value = obj.label || obj ;
if (!value) return '';
max = parseInt(max, 10);
if (!max) return value;
if (value.length <= max) return value;
value = value.substr(0, max);
if (wordwise) {
var lastspace = value.lastIndexOf(' ');
if (lastspace != -1) {
value = value.substr(0, lastspace);
}
}
return value + (tail || ' …');
};
});
But that becomes a bit messy if the filter is expected to handle multiple object structures.
You could also rewrite the ng-options statement. I always find the ng-options dsl a bit awkward. Here are a couple ways I think you could write this, but it might take some tinkering to get it correct. The idea remains the same though, you need to get the string to the filter, not the object :
country.value as country.label for country in countries | cut:country.label:true:20:' ...'
Or like this
country.value as (country.label | cut:true:20:' ...') for country in countries

Sorting an array by its nth-grand-child

I want to extend the functionality of this function AngularJS sorting by property.
It basically sorts an array by its child property. But what I want is to make it more generalized, to make it able to sort by any specified nth-grand-child.
The function is
function orderObjectBy(input, attribute) {
if (!angular.isObject(input)) return input;
var array = [];
for(var objectKey in input) {
array.push(input[objectKey]);
}
array.sort(function(a, b){
a = parseInt(a[attribute]);
b = parseInt(b[attribute]);
return a - b;
});
return array;
}
Here's an array which works with this: orderObjectBy(object, 'stat_a')
array = [
{stat_a:3},
{stat_a:2},
{stat_a:5},
]
Here's what I want to work with it: orderObjectBy(object, 'stats.a')
array = [
{stats:{a: 3}},
{stats:{a: 2}},
{stats:{a: 5}},
]
I have no idea how to do it though. The line where it compares
a = parseInt(a[attribute]);
cannot just take stats.a as attribute.
Neither could I figure out how to make it parseInt(a[stats][a])
Note, stats.a example would be merely .split('.').pop() etc but I want a more general approach, that'll work for stats.some.things.a or stats.some.other.things.a ...)
Any guidance how to proceed?
Split the attribute name by "." and explore the object with the attribute name list.
array.sort(function(a,b) {
a = parseInt(getAttribute(a, attribute);
b = parseInt(getAttribute(b, attribute);
return a-b
})
function getAttribute(object,attribute) {
var o = object;
var attrs = attribute.split(".")
for ( var i=0; i<attrs.length; i++) {
o = o[attrs[i]];
if (o==null) {
return null;
}
}
return o;
}

Resources