I have created a table using ng-table as below.
$scope.tableParams = new NgTableParams({}, {
filterOptions: { filterComparator:angular.equals },
dataset:$scope.usersList
});
This my html.
<table ng-table="tableParams" show-filter="true" class="table table-condensed table-bordered">
<tr ng-repeat="user in $data">
<td data-title="'User ID'" header-class="'text-left'" sortable="'id'" filter="{ 'id': 'text' }">
{{user.id}}
</td>
<td data-title="'Username'" header-class="'text-left'" sortable="'username'" filter="{ 'username': 'text' }">
{{user.username}}
</td>
<td data-title="'Name'" filter="{status: 'select'}" filter-data="status" sortable="'status'">{{ user.status }}</td>
</tr>
</table>
When filter "status", I want to get the output as exact match. But when filter "username" I want it to be as contains. Since I have given "filterComparator" as "angular.equals " ,now the whole table is filtered as exact match. But how can I filter only "status" column as exact match and others as contains?
I don't think it can be done in a clean way.
For the same use case, I wrote a single filterComparator function, and decided the kind of comparison I wanted to do on the basis of the type of the parameters passed to the function.
function filterComparator(actual, expected) {
if (expected instanceof Date) {
// using moment.js for date comparison
return moment(actual).isSame(moment(expected), 'day');
} else {
// default: case insensitive substring match
return actual.toString().search(new RegExp(expected.toString(), 'i')) != -1;
}
}
Have you been able to solve the problem in a different way? I would be interested to hear.
I wrote a filter function to compare equality of two strings
angular.module('myApp').filter('equalComparator', function($filter) {
return function(values, searchTerm , property) {
var filtered = [];
if(typeof values != 'undefined' && typeof searchTerm != 'undefined') {
angular.forEach(values, function(value) {
if(value[property] == searchTerm ) {
filtered.push(value);
}
});
}
return filtered;
}
})
And then used this "equalComparator" in ngtable filter method as below
if(params.filter()[//column name to filter]) {
$scope.filteredData = $filter('equalComparator')($scope.filteredData,params.filter()[//column name to filter] ,//field name to filter);
}
So I try to generate columns in datatables based on response from api request.
$scope.getProductCustomFields = function() {
$scope.custom_fields_loading = true;
$scope.dtCustomFieldsInstance = {};
$scope.dtCustomFieldsOptions = DTOptionsBuilder.newOptions().withOption('order', []);
$scope.dtCustomFieldsOptions.withOption('ajax', {
headers: {'Authorization': 'Basic ' + $rootScope.globals.currentUser.api_token},
dataSrc: 'data',
url: API.returnUrl('/ecommerce/reports/get-product-custom-fields?' + $httpParamSerializer({product: $scope.product})),
type: 'GET'
})
.withOption('processing', true)
.withOption('serverSide', true)
.withPaginationType('full_numbers')
.withOption('createdRow', createdRow);
function createdRow(row, data, dataIndex) {
// Recompiling so we can bind Angular directive to the DT
$compile(angular.element(row).contents())($scope);
}
$scope.dtCustomFieldsColumns = [];
//Here I make another request to php within this function since I cannot actually use dataSrc: 'data' as array
ProductsReportsService.getProductCustomFields($scope.product).then(function (response) {
$scope.data = response.data.data;
angular.forEach($scope.data, function (value, key) {
$scope.dtCustomFieldsColumns.push(DTColumnBuilder.newColumn('value.value').withTitle(key).notSortable());
});
});
$scope.custom_fields_loading = false;
};
As you can see I make two requests, a ajax one whose data is not accessible and another one before which I have commented, that I use for my forEach.
data looks like this:
array:1 [
"test drop down" => array:2 [
0 => array:4 [
"id" => 1
"label" => "test drop down"
"value" => "test1"
"name" => "test drop down"
]
1 => array:4 [
"id" => 1
"label" => "test drop down"
"value" => "test2"
"name" => "test drop down"
]
]
So to put it simple what I try to accomplish is table that basically looks like this:
<table>
<thead>
<tr>
<th>test drop down</th>
</tr>
</thead>
<tbody>
<tr>
<td>test1</td>
</tr>
<tr>
<td>test2</td>
</tr>
</tbody>
</table>
Right now my table only has the headers right but I have no data in table body.
<table>
<thead>
<tr>
<th>test drop down</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
</tr>
<tr>
<td></td>
</tr>
</tbody>
</table>
Thank you for your time and help!
Not sure is this is what you're looking for, but displaying a table like that from given data set (assuming it's saved in $scope.dtCustomFieldsColumns) would be something like this:
<tbody>
<tr ng-repeat="column in dtCustomFieldsColumns.testdropdown">
<td>{{column.value}}</td>
</tr>
</tbody>
You should change test drop down key in your data set to something like testDropDown. That'll help you access columns directly. Good luck.
My goal is to apply a formatting filter that is set as a property of the looped object.
Taking this array of objects:
[
{
"value": "test value with null formatter",
"formatter": null,
},
{
"value": "uppercase text",
"formatter": "uppercase",
},
{
"value": "2014-01-01",
"formatter": "date",
}
]
The template code i'm trying to write is this:
<div ng-repeat="row in list">
{{ row.value | row.formatter }}
</div>
And i'm expecting to see this result:
test value with null formatter
UPPERCASE TEXT
Jan 1, 2014
But maybe obviusly this code throws an error:
Unknown provider: row.formatterFilterProvider <- row.formatterFilter
I can't immagine how to parse the "formatter" parameter inside the {{ }}; can anyone help me?
See the plunkr http://plnkr.co/edit/YnCR123dRQRqm3owQLcs?p=preview
The | is an angular construct that finds a defined filter with that name and applies it to the value on the left. What I think you need to do is create a filter that takes a filter name as an argument, then calls the appropriate filter (fiddle) (adapted from M59's code):
HTML:
<div ng-repeat="row in list">
{{ row.value | picker:row.formatter }}
</div>
Javascript:
app.filter('picker', function($filter) {
return function(value, filterName) {
return $filter(filterName)(value);
};
});
Thanks to #karlgold's comment, here's a version that supports arguments. The first example uses the add filter directly to add numbers to an existing number and the second uses the useFilter filter to select the add filter by string and pass arguments to it (fiddle):
HTML:
<p>2 + 3 + 5 = {{ 2 | add:3:5 }}</p>
<p>7 + 9 + 11 = {{ 7 | useFilter:'add':9:11 }}</p>
Javascript:
app.filter('useFilter', function($filter) {
return function() {
var filterName = [].splice.call(arguments, 1, 1)[0];
return $filter(filterName).apply(null, arguments);
};
});
I like the concept behind these answers, but don't think they provide the most flexible possible solution.
What I really wanted to do and I'm sure some readers will feel the same, is to be able to dynamically pass a filter expression, which would then evaluate and return the appropriate result.
So a single custom filter would be able to process all of the following:
{{ammount | picker:'currency:"$":0'}}
{{date | picker:'date:"yyyy-MM-dd HH:mm:ss Z"'}}
{{name | picker:'salutation:"Hello"'}} //Apply another custom filter
I came up with the following piece of code, which utilizes the $interpolate service into my custom filter. See the jsfiddle:
Javascript
myApp.filter('picker', function($interpolate ){
return function(item,name){
var result = $interpolate('{{value | ' + arguments[1] + '}}');
return result({value:arguments[0]});
};
});
One way to make it work is to use a function for the binding and do the filtering within that function. This may not be the best approach: Live demo (click).
<div ng-repeat="row in list">
{{ foo(row.value, row.filter) }}
</div>
JavaScript:
$scope.list = [
{"value": "uppercase text", "filter": "uppercase"}
];
$scope.foo = function(value, filter) {
return $filter(filter)(value);
};
I had a slightly different need and so modified the above answer a bit (the $interpolate solution hits the same goal but is still limited):
angular.module("myApp").filter("meta", function($filter)
{
return function()
{
var filterName = [].splice.call(arguments, 1, 1)[0] || "filter";
var filter = filterName.split(":");
if (filter.length > 1)
{
filterName = filter[0];
for (var i = 1, k = filter.length; i < k; i++)
{
[].push.call(arguments, filter[i]);
}
}
return $filter(filterName).apply(null, arguments);
};
});
Usage:
<td ng-repeat="column in columns">{{ column.fakeData | meta:column.filter }}</td>
Data:
{
label:"Column head",
description:"The label used for a column",
filter:"percentage:2:true",
fakeData:-4.769796600014472
}
(percentage is a custom filter that builds off number)
Credit in this post to Jason Goemaat.
Here is how I used it.
$scope.table.columns = [{ name: "June 1 2015", filter: "date" },
{ name: "Name", filter: null },
] etc...
<td class="table-row" ng-repeat="column in table.columns">
{{ column.name | applyFilter:column.filter }}
</td>
app.filter('applyFilter', [ '$filter', function( $filter ) {
return function ( value, filterName ) {
if( !filterName ){ return value; } // In case no filter, as in NULL.
return $filter( filterName )( value );
};
}]);
I improved #Jason Goemaat's answer a bit by adding a check if the filter exists, and if not return the first argument by default:
.filter('useFilter', function ($filter, $injector) {
return function () {
var filterName = [].splice.call(arguments, 1, 1)[0];
return $injector.has(filterName + 'Filter') ? $filter(filterName).apply(null, arguments) : arguments[0];
};
});
The newer version of ng-table allows for dynamic table creation (ng-dynamic-table) based on a column configuration. Formatting a date field is as easy as adding the format to your field value in your columns array.
Given
{
"name": "Test code",
"dateInfo": {
"createDate": 1453480399313
"updateDate": 1453480399313
}
}
columns = [
{field: 'object.name', title: 'Name', sortable: 'name', filter: {name: 'text'}, show: true},
{field: "object.dateInfo.createDate | date :'MMM dd yyyy - HH:mm:ss a'", title: 'Create Date', sortable: 'object.dateInfo.createDate', show: true}
]
<table ng-table-dynamic="controller.ngTableObject with controller.columns" show-filter="true" class="table table-condensed table-bordered table-striped">
<tr ng-repeat="row in $data">
<td ng-repeat="column in $columns">{{ $eval(column.field, { object: row }) }}</td>
</tr>
</table>
I ended up doing something a bit more crude, but less involving:
HTML:
Use the ternary operator to check if there is a filter defined for the row:
ng-bind="::data {{row.filter ? '|' + row.filter : ''}}"
JS:
In the data array in Javascript add the filter:
, {
data: 10,
rowName: "Price",
months: [],
tooltip: "Price in DKK",
filter: "currency:undefined:0"
}, {
This is what I use (Angular Version 1.3.0-beta.8 accidental-haiku).
This filter allows you to use filters with or without filter options.
applyFilter will check if the filter exists in Angular, if the filter does not exist, then an error message with the filter name will be in the browser console like so...
The following filter does not exist: greenBananas
When using ng-repeat, some of the values will be undefined. applyFilter will handle these issues with a soft fail.
app.filter( 'applyFilter', ['$filter', '$injector', function($filter, $injector){
var filterError = "The following filter does not exist: ";
return function(value, filterName, options){
if(noFilterProvided(filterName)){ return value; }
if(filterDoesNotExistInAngular(filterName)){ console.error(filterError + "\"" + filterName + "\""); return value; }
return $filter(filterName)(value, applyOptions(options));
};
function noFilterProvided(filterName){
return !filterName || typeof filterName !== "string" || !filterName.trim();
}
function filterDoesNotExistInAngular(filterName){
return !$injector.has(filterName + "Filter");
}
function applyOptions(options){
if(!options){ return undefined; }
return options;
}
}]);
Then you use what ever filter you want, which may or may not have options.
// Where, item => { name: "Jello", filter: {name: "capitalize", options: null }};
<div ng-repeat="item in items">
{{ item.name | applyFilter:item.filter.name:item.filter.options }}
</div>
Or you could use with separate data structures when building a table.
// Where row => { color: "blue" };
// column => { name: "color", filter: { name: "capitalize", options: "whatever filter accepts"}};
<tr ng-repeat="row in rows">
<td ng-repeat="column in columns">
{{ row[column.name] | applyFilter:column.filter.name:column.filter.options }}
</td>
</tr>
If you find that you require to pass in more specific values you can add more arguments like this...
// In applyFilter, replace this line
return function(value, filterName, options){
// with this line
return function(value, filterName, options, newData){
// and also replace this line
return $filter(filterName)(value, applyOptions(options));
// with this line
return $filter(filterName)(value, applyOptions(options), newData);
Then in your HTML perhaps your filter also requires a key from the row object
// Where row => { color: "blue", addThisToo: "My Favorite Color" };
// column => { name: "color", filter: { name: "capitalize", options: "whatever filter accepts"}};
<tr ng-repeat="row in rows">
<td ng-repeat="column in columns">
{{ row[column.name] | applyFilter:column.filter.name:column.filter.options:row.addThisToo }}
</td>
</tr>
I'm trying to generate a table using ng-repeat.
Use case
The data to generate the table from looks as follows:
$scope.data = [
{
name : 'foo1',
group : 1
},
{
name : 'foo2',
group : 1
},
{
name : 'foo3',
group : 1
},
{
name : 'foo4',
group : 1
},
{
name : 'foobar',
group : 2
},
{
name : 'foobarbar',
group : 3
}
];
The html generated should look like this:
<tr>
<th>Group</th>
<th>Name</th>
</tr>
<tr>
<td rowspan="4">1</td>
<td>foo1</td>
</tr>
<tr>
<td>foo2</td>
</tr>
<tr>
<td>foo3</td>
</tr>
<tr>
<td>foo4</td>
</tr>
<tr>
<td rowspan="1">2</td>
<td>foobar</td>
</tr>
<tr>
<td rowspan="1">2</td>
<td>foobarbar</td>
</tr>
Implementation
I know the easiest way would probably be to pre-process the data and group the items per group in a new array of arrays. However, I chose a different approach:
<td
ng-if = "isDifferentFromPrev(items, $index, groupingData)"
rowspan = "{{item._groupSize}}"
>
with
$scope.isDifferentFromPrev = function(array, index, groupingData){
if(index === 0){
groupingData.startI = 0;
groupingData.counter = 1;
array[0]._groupSize = 1;
return true;
}
var eq = equalsMethod(array[index], array[index-1]);
if(eq){
groupingData.counter++;
array[groupingData.startI]._groupSize = groupingData.counter;
}
else{
groupingData.startI = index;
groupingData.counter = 1;
array[index]._groupSize = 1;
}
return !eq;
};
Problem
For some reason the rendered value for rowspan is always 1.
The attribute is only set for the first td of the first tr of a group, as intended, but the value for it is 1.
If I put a breakpoint inside isDifferentFromPrev(), the values seem to be updated correctly. This does not reflect in the html though.
Solution?
It occured to me that maybe ng-repeat renders each step sequentially, without returning to it. So maybe the _groupSize values for the first item of each group do get properly updated, but since they are updated after that item has already been rendered by ng-repeat, the update isn't processed anymore.
I have no idea if this reasoning is correct, nor about how to solve it. Any suggestions please?
This solution, even if a bit orthodox, does work:
var app = angular.module("myApp", []);
app.controller("myController", function($scope) {
$scope.data = [{
name: 'foo1',
group: 1
}, {
name: 'foo2',
group: 1
}, {
name: 'foo3',
group: 1
}, {
name: 'foo4',
group: 1
}, {
name: 'foobar',
group: 2
}, {
name: 'foobarbar',
group: 3
}];
$scope.itemHasRowspan = function(item) {
return typeof item === "object" && item.hasOwnProperty("rowspan");
};
var groupData = {},
currentGroup = null,
addGroup = function(firstItem) {
currentGroup = firstItem.group;
groupData[firstItem.group] = {
"firstItem": firstItem,
"count": 1
};
};
angular.forEach($scope.data, function(item, index) {
if (item.group !== currentGroup) {
addGroup(item);
} else {
groupData[item.group].count++;
}
});
angular.forEach(groupData, function(group, index) {
group.firstItem["rowspan"] = group.count;
});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="myController">
<table>
<thead>
<tr>
<th>Group</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in data">
<td ng-if="itemHasRowspan(item)" rowspan="{{ item.rowspan }}" valign="top">
{{ item.group }}
</td>
<td>
{{ item.name }}
</td>
</tr>
</tbody>
</table>
</div>
</div>
Doing a loop within a loop in a view:
<tr ng-repeat="result in results">
<td>
<span ng-repeat="device in helpers.getIosDevices(result.ios_device)">
{{ device.code }}
</span>
</td>
</tr>
The controller:
$scope.helpers = CRM.helpers;
The helper:
var CRM = CRM || {};
CRM.helpers = {
// Handle "111" value format
getIosDevices: function (devices) {
var obj = [];
if (devices !== null && devices !== undefined) {
if (devices.charAt(0) === '1') {
obj.push({
code: 'ipod',
name: 'iPod',
});
}
if (devices.charAt(1) === '1') {
obj.push({
code: 'ipad',
name: 'iPad',
});
}
if (devices.charAt(2) === '1') {
obj.push({
code: 'iphone',
name: 'iPhone',
});
}
}
return obj;
}
};
Got this error: https://docs.angularjs.org/error/$rootScope/infdig?p0=10&p1=%5B%5B%22fn:%E2%80%A620%20%20%7D;%20newVal:%20undefined;%20oldVal:%20undefined%22%5D%5D
as I understand but I don't know how can I solve it in my case. What workaround should I use?
The reason of this error that you try to change source list in ng-repeat directive during digest cycle.
<span ng-repeat="device in helpers.getIosDevices(result.ios_device)">
^^^^^^^^
and obj.push(/* ... */) in getIosDevices
First we need ask our self when digest cycle will stop looping: It will stop when Angular detect that on several iterations the list didn't change. In your case each time when ng-repeat calls getIosDevices method - the list gets different items and therefore it looping again till you get limit and Angular drops this exception.
So what is a solution:
In Angular its not good practice to call method getList() in ngRepeat. Because developpers make bugs.
Its clear that in your case getIosDevices() list depends on results therefore I would create different fixed object with some watcher on results and write HTML part like:
<tr ng-repeat="result in results">
<td>
<span ng-repeat="device in devices[result.ios_device]">
{{ device.code }}
</span>
</td>
</tr>
where devices represents Map.
This is some demo that might help you:
$scope.results = [{
ios_device: "100"
}, {
ios_device: "010"
}, {
ios_device: "001"
}];
$scope.devices = {
"100": [{
code: 'ipod',
name: 'iPod1',
},
{
code: 'ipod',
name: 'iPod2',
}],
"010": [{
code: 'ipod',
name: 'iPod1',
},
{
code: 'ipad',
name: 'iPad2',
}],
"001": [{
code: 'ipod',
name: 'iPod1',
},
{
code: 'iphone',
name: 'iphone2',
}],
}
HTML
<table>
<tr ng-repeat="result in results">
<td><span ng-repeat="device in devices[result.ios_device]">
{{ device.code }}
</span>
</td>
</tr>
</table>
Demo in Fiddle