I have two tables:
A jobs table with 3 fields: id, client_id, name.
A clients table with 2 fields: id, name.
Using Angular 1.5, I'm iterating over the jobs:
controller('JobsController', ['$scope', 'Job', 'Client', function($scope, Job, Client) {
$scope.jobs = Job.query();
$scope.clients = Client.query();
}]);
HTML:
<tr ng-repeat="job in jobs">
<td>
{{clients[job.client_id].name}}
</td>
<td>
{{job.name}}
</td>
</tr>
In the HTML the first column should be the client name. As it is, this isn't working, because $scope.clients is an array of objects that look a bit like this:
[{'id':4, 'name':'test_name'}, {'id':7, 'name':'another client'}]
Is there a way to pick from this clients array by id, in my ng-repeat loop?
$scope.jobs looks like:
[{'id':100, 'client_id': 4, 'name': 'a job'}, ...]
To begin with, it may be easier to do the join on the server side, to where your $scope.jobs would look more like:
[
{
'id': 100,
'name': 'a job',
'client': {
'id': 4
'name': 'test_name'
}
}
...
]
If you need to do it on the front end, what I would do is add a method to your controller to get the client for a specified job. Something like this:
$scope.getClientName = function(job) {
//to prevent errors if $scope.clients is not loaded yet
if (!$scope.clients) {
return;
}
for (var c = 0; c < $scope.clients.length; c++) {
var client = $scope.clients[c];
if (client.id = job.client_id) {
return client.name;
}
}
}
Then instead of {{clients[job.client_id].name}} call your function and pass in the job:
{{getClientName(job)}}
You can do it in front end as below
$scope.clients = [{'id':1, 'name':'test_name'}, {'id':4, 'name':'another client'}];
$scope.jobs = [{'id':100, 'client_id': 4, 'name': 'a job4'}, {'id':100, 'client_id': 1, 'name': 'a job1'}, {'id':100, 'client_id': 24, 'name': 'a job24'}];
function fillJobsByClient(jobs, client){
for(var i=0; i<jobs.length; i++){
if(jobs[i].client_id == client.id){
jobs[i].client = client;
delete jobs[i].client_id;
}
}
}
$scope.clients.forEach(function(client){
fillJobsByClient($scope.jobs, client);
});
console.log($scope.jobs);
I know this is late, but I figured it might help point someone else on the right direction. This is how you could do it on the front end, you should load your array OnInit, then use an if statement to select the correct detail field.
<tr *ngFor='let j of jobs'>
<td>
<div *ngFor='let c of clients'>
<div *ngIf='c.id== j.client_id'>{{j.name}}</div>
</div>
</td>
</tr>
Related
I have a problem to sort my data with smart table, specifically when including a turkish character. Wrong order is generated.
In my controller:
$scope.rowCollection = [{
a: 'Çanakkale',
b: '3'
}, {
a: 'Ceyhan',
b: '2'
}, {
a: 'ĞĞĞĞĞ',
b: '4'
}, {
a: 'Ankara',
b: '1'
}, {
a: 'Zonguldak',
b: '5'
}];
$scope.displayedCollection = [].concat($scope.rowCollection);
and my html:
<tr ng-repeat="row in displayedCollection">
<td ng-repeat="col in columns">{{row[col]}}</td>
</tr>
Here's the plunk:
http://plnkr.co/edit/JW4G1n2QszIqYjcAmlNz
How can i fix it ?
Thanks for your help
This is what I've found for you:
The smart-table version in your plunk is missing some parts (line 164) which doesn't allow you to do what you want. I've changed it to version 2.1.8 in my plunk
Use st-set-sort="yourFilterName" on your table, where your st-table attribute is:
<table st-table="displayedCollection" st-set-sort="turkishFilter" st-safe-src="rowCollection" class="table table-striped">
Write a custom filter function:
angular.module('myApp', ['smart-table'])
.filter('turkishFilter', function(){
return function(items, field, isDescending){
//If you don't create a copy of the array,
//smart-table won't be able to restore the natural order state
var result = items.slice();
//Working only for string properties ATM!
result.sort(function(first, second){
//return first.a.localeCompare(second.a, 'tr');
//OR
return first[field].localeCompare(second[field], 'tr');
//localCompare() is supported only in IE11 and upwards
});
if (isDescending){
result.reverse();
}
return result;
};
})
Working plunk HERE
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 am building my first angular-meteor app.
Find below the app.js file:
libri = new Mongo.Collection("libri");
if (Meteor.isClient) {
angular.module('bookshelf',['angular-meteor']);
angular.module('bookshelf').controller('BookListCtrl', ['$scope','$meteor',
function($scope,$meteor){
$scope.libri = $meteor.collection(libri)
$scope.counter = 0;
for(i = 0; i < libri.find().count(); i++){
$scope.counter += 1;
}
}]);
}
if (Meteor.isServer) {
Meteor.startup(function () {
});
}
and the index.ng.html file:
<div class="container">
<table class="table" ng-controller="BookListCtrl">
<caption>Optional table caption.</caption>
<thead>
<tr>
<th>Contatore</th>
<th>Titolo</th>
<th>Autore</th>
<th>Casa Editrice</th>
</tr>
</thead>
<tbody ng-repeat="libro in libri">
<tr>
<td>{{counter}}</td>
<td>{{libro.titolo}}</td>
<td>{{libro.autore}}</td>
<td>{{libro.casaEditrice}}</td>
</tr>
</tbody>
</table>
</div>
And I get the following table:
As you can see the auto increment field {counter} is always 3. I would like to be 1,2,3 for each line of the table without creating another document-field in the collection ('libri'). That cell of the table is not part of the mongo collection.
Is there any suggestion? Why I cannot print the loop in the html table? Thanks
The reason why you are always getting only number 3 is because that is what is being prepared for the view.
When the view gets the data front the controller, the counter is already at the value 3.
What you should do is use $index instead of {{counter}} and it will automatically get the index from the ng-repeat iterating over the array, so you will get 1, 2, 3....
If you wanted to send it with your data from the controller, then you should imbue the index with your array.
Something like this:
var tempArr = [
{name: 'a'},
{name: 'b'},
{name: 'c'}
]
var i = 0;
for (; i < tempArr.length; i++) {
tempArr[i].index = i;
}
console.log('updated array', tempArr);
Just replace tempArr with your data, and it should run smoothly.
You can test this here, just check your console.
Hope this helps :)
Use {{$index}} instead of {{counter}}.
Say I have an object with keys corresponding to products and values corresponding to objects which in turn have keys corresponding to price points at which those products have sold, and values corresponding to amount sold.
For example, if I sold 10 widgets at $1 and 5 widgets at $2, I'd have the data structure:
{ 'widget': {'1': 10, '2': 5} }
I'd like to loop over this structure and generate rows in a table such as this one:
thing price amount
---------------------
widget $1 10
widget $2 5
In Python it's possible to nest list comprehensions to traverse lists data structures like this. Would such a thing be possible using ng-repeat?
How about this?
http://plnkr.co/edit/ZFgu8Q?p=preview
Controller:
$scope.data = {
'widget1': {
'1': 10,
'2': 5
},
'widget2': {
'4': 7,
'6': 6
}
};
View:
<div ng-controller="MyCtrl">
<table>
<thead>
<tr>
<td>thing</td>
<td>price</td>
<td>amount</td>
</tr>
</thead>
<tbody ng-repeat="(productName, productData) in data">
<tr ng-repeat="(price, count) in productData">
<td>{{productName}}</td>
<td>{{price|currency}}</td>
<td>{{count}}</td>
</tr>
</tbody>
</table>
</div>
Output:
thing price amount
----------------------
widget1 $1.00 10
widget1 $2.00 5
widget2 $4.00 7
widget2 $6.00 6
This would output a tbody per product (thanks to Sebastien C for the great idea).
If needed, you can differentiate between the first, middle and last tbody (using ng-repeat's $first, $middle and $last) and style them with ng-class (or even native CSS selectors such as :last-child -- I would recommend ng-class though)
ng-repeat does not currently have a possible way to complex iterate inside objects (the way it's possible in python). Check out the ng-repeat source code and note that the regex expression matched is:
(key, value) in collection - and that they push into the key array and assign to the value list, and so you cannot possibly have a complex ng-repeat sadly...
There are basically 2 types of solutions which were already answered here:
Nested ng-repeat like the first answer suggested.
Rebuilding your data object to fit 1 ng-repeat like the second answer suggested.
I think solution 2 is better as I like to keep my sorting & coding logic inside the controller, and not deal with it in the HTML document. This will also allow for more complex sorting (i.e based on price, amount, widgetName or some other logic).
Another thing - the second solution will iterate over possible methods of a dataset (as hasOwnProperty wasn't used there).
I've improved the solution in this Plunker (based on the finishingmove Plunker) in order to use angular.forEach and to show that the solution is rather simple but allows for complex sorting logic.
$scope.buildData = function() {
var returnArr = [];
angular.forEach($scope.data, function(productData, widget) {
angular.forEach(productData, function( amount, price) {
returnArr.push( {widgetName: widget, price:price, amount:amount});
});
});
//apply sorting logic here
return returnArr;
};
$scope.sortedData = $scope.buildData();
and then in your controller:
<div ng-controller="MyCtrl">
<table>
<thead>
<tr>
<td>thing</td>
<td>price</td>
<td>amount</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in sortedData">
<td>{{ item.widgetName }}</td>
<td>{{ item.price|currency }}</td>
<td>{{ item.amount }} </td>
</tr>
</tbody>
</table>
</div>
Just transform your object to an array... it's pretty easy in JS.
Something like:
$scope.data = { 'widget': { '1': 10, '2': 5 } };
var tableData = [];
for (item in $scope.data) {
var thing = item;
for (subitem in $scope.data[thing]) {
tableData.push({
thing: thing,
price: subitem,
amount: $scope.data[thing][subitem]
});
}
}
I've created a jsfiddle with this example: http://jsfiddle.net/b7TYf/
I used a simple directive which has a recursive function to loop over my nested object and create nested elements. This way you can keep your nested object structure.
Code:
angular.module('nerd').directive('nestedItems', ['$rootScope', '$compile', function($rootScope, $compile) {
return {
restrict: 'E',
scope: false,
link: function(scope, element, attrs, fn) {
scope.addElement = function(elem, objAr) {
var ulElement = angular.element("<ul></ul>");
if (objAr == undefined || objAr.length == 0) {
return [];
}
objAr.forEach(function(arrayItem) {
var newElement = angular.element("<li>"+arrayItem.val+"</li>");
ulElement.append(newElement);
scope.addElement(newElement,arrayItem.sub);
});
elem.append(ulElement);
};
scope.addElement(element,scope.elements);
}
};
}]);
My Object :
$scope.elements = [
{
id: 1,
val: "First Level",
sub: [
{
id: 2,
val: "Second Level - Item 1"
},
{
id: 3,
val: "Second Level - Item 2",
sub: [{
id: 4,
val: "Third Level - Item 1"
}]
}
]
}
];
My HTML
<nested-items></nested-items>
I'm trying to create a dynamic table that could hold search results with different amount of columns.
I created a table that should have a row for every entry and a column for every datafield both populated with ng-repeat -functions, but for some reason it doesn't show any information in the columns at all, although it does create correct amount of them.
If I try to show e in {{}} it shows the correct key that exists. If I try with i in {{}} it shows the following in each column (the information is same for all columns, but different for every row):
{"etunimi":"firstname","sukunimi":"lastname","optunnus":"010101010101011001"}
Here is the html:
<table id="raporttiTulos" class="resultTable">
<tr ng-repeat="i in raportointiLista">
<td ng-repeat=" e in raportointiAvaimet">{{i.e}}</td>
</tr>
</table>
Here is the function responsible for the incoming data:
$scope.haeMaksut = function(){
$scope.raportointiAvaimet = {};
$http.post('/maksuhaku')
.then(function(res){
x = 0;
$scope.raportointiLista = res.data.message;
for(i in $scope.raportointiLista[0]){
$scope.raportointiAvaimet[x] = i;
x+=1
}
console.log($scope.raportointiAvaimet);
$scope.maksamattomat = $scope.raportointiLista.length;
$scope.lataus = true;
}, function(error){
console.log(error);
});
}
This is how the key list looks like:
Object [ "etunimi", "sukunimi", "optunnus" ]
Here are some rows from the data list:
[…]
[0…99]
0: Object { etunimi: "firstname", sukunimi: "lastname", optunnus: "101010101010101010", … }
instead of doing that you can directly access object keys.
HTML
<tr ng-repeat="i in raportointiLista">
<td ng-repeat="key in raportointiAvaimet">{{i[key]}}</td>
</tr>
Controller
$scope.haeMaksut = function () {
$scope.raportointiAvaimet = {};
$http.post('/maksuhaku')
.then(function (res) {
$scope.raportointiLista = res.data.message;
$scope.raportointiAvaimet = Object.keys($scope.raportointiLista[0]);
}, function (error) {
console.log(error);
});
}