I have one object like this
$scope.listvalues = [{ name:A, id:101 },
{ name:B, id:102 },
{ name:A, id:103 },
{ name:C, id:101 },
{ name:A, id:102 },
{ name:B, id:103 }];
I need to print this object in following structure
name |101 | 102 |103 |
-----|----|-----|---------
A |YES | YES |YES |
-----|----|-----|------------
B |No | YES |YES |
-----|----|-----|-----------
C |YES | NO |NO |
Here i need to print the Name "A" in unique and also need to indicate the A is available for which Id. Is it possible to do with angularjs ng-repeat?. Any one please suggest...
You can, but you would have to write a filter that changes the structure of your data to the following:
$scope.data = [
{A: {'101': true, '102': true, '103': true}},
{B: {'101': false, ...}},
{C: ...}
]
And then you can write your table like this:
<table>
<tr>
<th>name</th>
<th ng-repeat="(column, value) in data[0]">{{column}}</th>
</tr>
<tr ng-repeat="row in data">
<td ng-repeat="(column, value) in data[0]">{{row[column] ? 'Yes' : 'No'}}</td>
</tr>
</table>
Example filter:
yourModule.filter('makeNgRepeatable', function(){
return function(oldStructure) {
// Add code here to convert oldStructure to newStructure.
return newStructure;
}
});
In your controller, inject makeNgRepeatableFilter and do
$scope.data = makeNgRepeatableFilter([{ name:A, id:101 }, ...]);
You can pack this into a table and then resolve with multiple ng-repeats which cell is YES or NO.
Take a look at this plnkr, it demonstrates how this could be achieved.
http://plnkr.co/edit/QI8ZrsbwYuJUeV4DNWGl?p=preview
First you collect all distinct ids and names.
$scope.names = $scope.listvalues.map(function(d){return d.name}).unique();
$scope.ids = $scope.listvalues.map(function(d){return d.id}).unique();
Note: In the plnkr I defined the functions unique and contains. If you use some other libraries like underscore those functions may already be present.
Then you define a function to determine if a specific cell should be true or false.
$scope.hasValue = function(name, id) {
for(var i = 0; i < $scope.listvalues.length; ++i)
if($scope.listvalues[i].name === name && $scope.listvalues[i].id === id)
return true;
return false;
}
However, it would be simpler if you can convert your listvalues into a reasonable structure. This would prevent some of the overhead.
With your array structure you might need some additional helper array/objects. In your case it could look like this:
<table class="table table-bordered">
<thead>
<tr>
<th>Name</th>
<th ng-repeat="th in values">{{th}}</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="obj in names">
<td>
{{obj.name}}
<div><small>{{obj}}</small></div>
</td>
<td>{{obj.ids.indexOf(values[0]) > -1 ? 'YES' : 'NO'}}</td>
<td>{{obj.ids.indexOf(values[1]) > -1 ? 'YES' : 'NO'}}</td>
<td>{{obj.ids.indexOf(values[2]) > -1 ? 'YES' : 'NO'}}</td>
</tr>
</tbody>
</table>
where helper objects are constructed like:
function initData() {
var map = {values: {}, names: {}},
values = [],
names = [];
$scope.listvalues.forEach(function(obj) {
if (!map.values[obj.id]) {
values.push(obj.id);
map.values[obj.id] = true;
}
if (!map.names[obj.name]) {
names.push(obj);
obj.ids = [obj.id];
map.names[obj.name] = obj;
}
else {
map.names[obj.name].ids.push(obj.id);
}
});
$scope.values = values;
$scope.names = names;
}
initData();
Demo: http://plnkr.co/edit/wWJOjtzstUDKjl9V6hCy?p=preview
Related
I have a requirement where I need to add index values for child rows. I have Group rows under which there will be child rows. I am ng-repeat and I am using $index for child's as shown:
HTML code:
<table ng-repeat="node in data">
<tr> <td> {{node.groupName}} </td> </tr>
<tbody ng-model="node.nodes">
<tr ng-repeat="node in node.nodes"> <td> {{$index}} </td> </tr>
</table>
But it is displaying as shown:
But I want it to display as shown:
I am new to Angular JS and not getting how to display it like this. How am I supposed to do that. Please help.
As far as I understood your question, you'd like to have something like that:
<table ng-repeat="group in data">
<thead>
<th> {{group.name}} </th>
</thead>
<tbody ng-repeat="item in group.items">
<tr>
<td>{{getIndex($parent.$index - 1, $index)}} | {{item}} </td>
</tr>
</tbody>
</table>
$scope.data = [
{name: 'Group1', items: ['a','b']},
{name: 'Group2', items: [1,2,3]},
{name: 'Group3', items: ['x', 'xx', 'xxx', 'xxxx']}
];
$scope.getIndex = function(previousGroupIndex, currentItemIndex){
if(previousGroupIndex >= 0){
var previousGroupLength = getPreviousItemsLength(previousGroupIndex);
return previousGroupLength + currentItemIndex;
}
return currentItemIndex;
};
function getPreviousItemsLength(currentIndex){
var length = 0;
for (var i = 0; i <= currentIndex; i++){
length += $scope.data[i].items.length;
}
return length;
// or even better to use Array.prototype.reduce() for that purpose
// it would be shorter and beautiful
// return $scope.data.reduce(function(previousValue, currentGroup, index){
// return index <= previousGroupIndex ? previousValue + currentGroup.items.length : previousValue;
// }, 0);
}
You need to play with $parent.$index property and use some math :) in order to achieve that.
It would look like the following:
Check out this JSFiddle to see live example.
I've got json where each parameter of the object has over 40 key, val pairs. All data is put into a html table on a page
I want user to be able to change a number of rows on a html page.
Firstly, to reduce number of rows into the table I wrote ng-if statement. I added ng-if because LimitTo doesn't work with key, val pairs. And actually I'm stucking here. The main problem in key, val pairs for me.
How can I add more/less buttons that helps user hide or show more rows when a json file contains key,val pairs.
My html
<table class="table ng-cloak" ng-if='$index<10' ng-repeat="shop in shops | filter:isActive">
<thead>
<tr>
<th>#</th>
<th>Месяц</th>
<th>Число кликов</th>
</tr>
</thead>
<tbody>
<tr ng-repeat='(key, val) in shop.products' ng-if="$index < 10">
<td>{{ $index + 1 }}</td>
<td>{{ key }}</td>
<td>{{ val }}</td>
</tr>
</tbody>
</table>
my reduce json
[
{
"products": {
"1359568800": 74,
"1361988000": 71,
"1364666400": 83,
"1367258400": 72,
"1369936800": 78
},
"name": "moskva",
"avg_check": {
"1359568800": 6479,
"1361988000": 7375,
"1364666400": 8477,
"1367258400": 9292,
"1369936800": 8357
},
"income": {
"1359568800": 479515,
"1361988000": 523662,
"1364666400": 703601,
"1367258400": 669072,
"1369936800": 651921
}
}
]
We cannot use default filter for objects. So use custom filter
app.filter('myLimitTo', [function(){
return function(obj, limit){
var keys = Object.keys(obj);
if(keys.length < 1){
return [];
}
var ret = new Object,
count = 0;
angular.forEach(keys, function(key, arrayIndex){
if(count >= limit){
return false;
}
ret[key] = obj[key];
count++;
});
return ret;
};
Checkout the Fiddle https://jsfiddle.net/ebinmanuval/uLo0bo0u/
Hi I have these 2 different things:
columns = {
name:"Name",
ref:"Reference"
}
items:[
{ id:1, name:"Dan", ref:"01" },
{ id:2, name:"Dan2", ref:"02" }]
And I need to put them in a table like:
| Name | Reference |
| Dan | 01 |
| Dan2 | 02 |
normally this would be:
{{for columns}}
{{:name}} {{:ref}}<br>
{{/for}}
{{for items}}
{{:name}} {{:ref}}<br>
{{/for}}
However, my keys in the 2 objects are dynamic, so I want to so something that in javascript would be:
var header = "";
for(key in Object.keys(columns){
header+=" "+columns[key));
}
console.log(header);
for(var i=0;i<items.length;i++){
var item = items[i];
var line ="";
for(key in Object.keys(columns){
line+=" "+items[key);
}
console.log(line);
}
Can anyone suggest anything?
You can do it like this:
<script id="tmpl" type="text/x-jsrender">
<table><thead><tr>
{{props columns}}
<th>{{:prop}}</th>
{{/props}}
</tr></thead>
<tbody>
{{for items}}
<tr>
{{props ~root.columns ~item=#data}}
<td>{{:~item[key]}}</td>
{{/props}}
</tr>
{{/for}}
</tbody></table>
</script>
{{props}} iterates over the properties of an object - and within the {{props}} block, key is the property (field) name and prop is the value. (See http://www.jsviews.com/#propstag).
~item=#data defines a variable (a 'helper property') you can access from within the nested blocks, to easily get to 'parent data' from there. (See http://www.jsviews.com/#samples/jsr/paths).
The above assumes using the following data:
var data = {
columns: {
name:"Name",
ref:"Reference"
},
items:[
{ id:1, name:"Dan", ref:"01" },
{ id:2, name:"Dan2", ref:"02" }
]
}
$("#result").html(tmpl.render(data));
I am attempting to use a custom orderBy function. Initially, I want the data to appear in the order it was added to $scope.rows, and only after clicking on a column heading should it order by a specific property. Here's my fiddle:
http://jsfiddle.net/S8M4c/
Here's my view:
<table ng-app ng-controller="ctrl">
<tr>
<th><a ng-click="orderBy = 'id'">ID</a></th>
<th><a ng-click="orderBy = 'name'">Name</a></th>
</tr>
<tr ng-repeat="row in rows | orderBy:mySort">
<td>{{row.object.id}}</td>
<td>{{row.object.name}}</td>
</tr>
</table>
Here's my controller:
function ctrl($scope)
{
// Initially, we don't sort by anything
$scope.orderBy = "";
$scope.rows = [];
// Add some rows
for(var i = 10;i < 30;i++)
{
$scope.rows.push({settings: {foo: true}, object: {id: i, name: "Name " + i}})
};
$scope.mySort = function(row)
{
if($scope.orderBy != "")
{
return row.object[$scope.orderBy];
}
// What do I return here??
return "";
}
}
In the case that $scope.orderBy isn't set and I want to return $scope.rows in it's original order, what do I return in $scope.mySort? I cannot return row.object.id because the rows are not guaranteed to be added in order of their ID. Running my code as is on Chrome 32, the first row that appears has an ID of 20, which is the halfway row.
return $scope.rows.indexOf(row);
(Fiddle.)
You can also do this with out-of-the-box orderBy by providing a function returning that as the default predicate:
Controller:
$scope.mySort = $scope.unsorted = function(row)
{
return $scope.rows.indexOf(row);
}
View:
<div ng-app ng-controller="ctrl">
<table>
<tr>
<th><a ng-click="mySort = 'object.id'">ID</a></th>
<th><a ng-click="mySort = 'object.name'">Name</a></th>
</tr>
<tr ng-repeat="row in rows | orderBy:mySort">
<td>{{row.object.id}}</td>
<td>{{row.object.name}}</td>
</tr>
</table>
<button ng-click="mySort = unsorted;">Original Sort</button>
</div>
Fiddle here. (I've changed the numbers used in the objects so that sort by id, sort by name, and the original sort aren't all the same.)
I think you have to write your own sortby function. The original angulars orderBy is a regular filter that returns the sorted array. Your filter may look something like this:
.filter('mySort', function(){
return function(values, param){
if(param===''){
return values;
}else{
// very important! create a copy of the array - otherwise
// the $wtachCollection function will fire to often!
var arrayCopy = [];
for ( var i = 0; i < values.length; i++) { arrayCopy.push(values[i]); }
return arrayCopy.sort(function(a,b){
var v1 = a.object[param];
var v2 = b.object[param];
// you know best how to sort it!
if (v1 === v2) return 0;
return v1 < v2 ? -1 : 1;
});
}
}
})
You can use this filter in this way:
<table ng-app="myApp" ng-controller="ctrl">
<tr>
<th><a ng-click="orderBy = 'id'">ID</a></th>
<th><a ng-click="orderBy = 'name'">Name</a></th>
</tr>
<tr ng-repeat="row in rows | mySort:orderBy">
<td>{{row.object.id}}</td>
<td>{{row.object.name}}</td>
</tr>
</table>
here is your modified fiddle: http://jsfiddle.net/spRf6/ I have changed the names a little bit so you may see that the sorting works.
Create a copy of the objects array and the ordering then becomes trivial:
Controller:
$scope.objects = [];
angular.forEach($scope.rows, function(row){
$scope.objects.push(row.object);
});
View:
<tr ng-repeat="object in objects | orderBy:orderBy">
<td>{{object.id}}</td>
<td>{{object.name}}</td>
</tr>
No need for the mySort function.
First of all, i'm new to Knockout.js and underscore.js, and this is my first day of learning those libraries. The task is to sort table by clicking column header in ascending order on first click, and in descending order on second click.
I have this kind of HTML markup:
<table>
<thead>
<tr data-bind="foreach: columnNames">
<td data-bind="text: $data, click: $root.sortColumn, css: { 'active': $root.currentItem() == $data }"></td>
</tr>
</thead>
<tbody data-bind="foreach: persons">
<tr>
<td data-bind="text: name"></td>
<td data-bind="text: formattedAge"></td>
<td data-bind="text: sex"></td>
<td data-bind="text: married"></td>
</tr>
</tbody>
</table>
And this js code for knockout.js:
function personViewModel()
{
var self = this;
self.currentItem = ko.observable('');
self.columnNames = ko.observableArray([
'Name',
'Age',
'Sex',
'Married'
]);
self.persons = ko.observableArray([...]);
self.sortColumn = function(item)
{
self.currentItem(item);
var sorted = _(self.persons()).sortBy(item.toLowerCase());
self.persons(sorted);
};
};
ko.applyBindings(new personViewModel());
Now the question is:
Is it possible to get element descriptor, while one of td's clicked, so i could use something like '$(this)' in self.sortColumn?
Because now i can sort a table by clicking appropriate column header, but i don't know how to mark a column, that it already was clicked (and check it), to use _(self.persons()).sortBy(item.toLowerCase()).reverse(), to sort it in descending order of columns.
Thanks:)
Answer is simple. Right now you saving only column name, you need also have variable for sort direction, and your logic in sortColumn would be:
function personViewModel()
{
var self = this;
self.currentItem = ko.observable('');
self.sortDirection = ko.observable(true);
self.columnNames = ko.observableArray([
'Name','Age','Sex','Married'
]);
self.persons = ko.observableArray([
{ name : "John", formattedAge:27, sex:"Male", married:"No"},
{ name : "Bob", formattedAge:30, sex:"Male", married:"Yes"}
]);
self.sortColumn = function(item)
{
if (item == self.currentItem()) {
self.sortDirection(!self.sortDirection())
} else{
self.currentItem(item);
self.sortDirection(true)
}
if( self.sortDirection() ) {
var sorted = _(self.persons()).sortBy(item.toLowerCase());
self.persons(sorted);
} else {
var sorted = _(self.persons()).sortBy(item.toLowerCase()).reverse();
self.persons(sorted);
}
};
};
ko.applyBindings(new personViewModel());
See jsfiddle with working example.
Also notice that you don't really need underscore.js here, as ko.js provides you with
myObservableArray.reverse()
myObservableArray.sort()
Everything you need.
var sortDirection = true
self.sortColumn = function(item) {
if (item == self.currentItem()) {
sortDirection = !sortDirection
self.persons.reverse()
} else {
sortDirection = true
var field = self.currentItem()
self.persons.sort(function(left, right) {
return left[field] == right[field] ? 0
: ( left[field]< right[field] ? -1 : 1 )
})
}
}