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.
Related
Here is the code
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body>
<p>Click the table headers to change the sorting order:</p>
<div ng-app="myApp" ng-controller="namesCtrl">
<table border="1" width="100%">
<tr>
<th>Sl no</th>
<th ng-click="orderByMe('name')">Name</th>
<th ng-click="orderByMe('country')">Country</th>
<th>Delete</th>
</tr>
<tr ng-repeat="x in names | orderBy:myOrderBy">
<td>{{$index+1}}</td>
<td>{{x.name}}</td>
<td>{{x.country}}</td>
<td><button type="button" ng-click="Delete($index)">Delete</button></td>
</tr>
</table>
</div>
<script>
angular.module('myApp', []).controller('namesCtrl', function($scope) {
$scope.names = [
{name:'Jani',country:'Norway'},
{name:'Carl',country:'Sweden'},
{name:'Margareth',country:'England'},
{name:'Hege',country:'Norway'},
{name:'Joe',country:'Denmark'},
{name:'Gustav',country:'Sweden'},
{name:'Birgit',country:'Denmark'},
{name:'Mary',country:'England'},
{name:'Kai',country:'Norway'}
];
$scope.orderByMe = function(x) {
$scope.myOrderBy = x;
}
$scope.Delete = function(index){
$scope.names.splice(index, 1);
};
});
</script>
</body>
</html>
Here at first if I want to delete a row it works fine, but if I sort once by name or country, then everytime the the unwanted row get deleted. Can any one help me in this. Here is the plunker link.
Thanks
There are two ways you would want to solve this:
As mentioned in comments by #Tushar, send whole object instead of just the index and in delete function, you could have:
$scope.names.splice($scope.names.indexOf(x), 1);
Demo provided by him
Alternatively, you can have a reference of your filtered array like this:
ng-repeat="x in filtered = (names | orderBy:myOrderBy)"
And, inside delete, you can use that to find the object index in your original array:
$scope.Delete = function(index){
$scope.names.splice($scope.names.indexOf($scope.filtered[index]), 1);
};
Demo for this approach
Use foreach. Try like below..
<html>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body>
<p>Click the table headers to change the sorting order:</p>
<div ng-app="myApp" ng-controller="namesCtrl">
<table border="1" width="100%">
<tr>
<th>Sl no</th>
<th ng-click="orderByMe('name')">Name</th>
<th ng-click="orderByMe('country')">Country</th>
<th>Delete</th>
</tr>
<tr ng-repeat="x in reArrange = (names | orderBy:myOrderBy)">
<td>{{$index+1}}</td>
<td>{{x.name}}</td>
<td>{{x.country}}</td>
<td><button type="button" ng-click="Delete(x)">Delete</button></td>
</tr>
</table>
</div>
<script>
angular.module('myApp', []).controller('namesCtrl', function($scope) {
$scope.names = [
{name:'Jani',country:'Norway'},
{name:'Carl',country:'Sweden'},
{name:'Margareth',country:'England'},
{name:'Hege',country:'Norway'},
{name:'Joe',country:'Denmark'},
{name:'Gustav',country:'Sweden'},
{name:'Birgit',country:'Denmark'},
{name:'Mary',country:'England'},
{name:'Kai',country:'Norway'}
];
$scope.orderByMe = function(x) {
$scope.myOrderBy = x;
}
$scope.Delete = function(x){
//$scope.names.splice($scope.names.indexOf($scope.reArrange[index]), 1); // pass index
angular.forEach($scope.names, function (value, index) {
if (x.name == value.name) {
$scope.names.splice(index,1)
}
})
}
});
</script>
</body>
</html>
Simple way is to pass the object on delete and remove it form the array using underscore.js
Check this plnkr
<td>
<button type="button" ng-click="Delete(x)">Delete</button>
</td>
...
$scope.Delete = function(item){
$scope.names = _.without($scope.names, _.findWhere($scope.names, item));
};
Now the delete function is working exactly as I want.
$scope.Delete = function(name) {
var index = -1;
var comArr = eval($scope.names);
for (var i = 0; i < comArr.length; i++) {
if (comArr[i].name === name) {
index = i;
break;
}
}
if (index === -1) {
alert("Something gone wrong");
}
$scope.names.splice(index, 1);
};
Here is the plunker link : http://plnkr.co/edit/Az0c6IzCIuB87hac9wgN?p=preview
I'm having a bit of a weird problem and there are now 2 people in my office completely stumped by this (so I hope it's not something embarrassingly obvious!).
As an overview, we have a SQLite database that could contain any number of tables, each with any number of rows or columns. We want to display this data in a page in an Electron app.
We have an Angular page in our Electron app (with Express/Node as a backend) which makes some calls to an Express endpoint (which gets data from the SQLite db) and we end up with two 2D arrays of data - one for the table headers ($scope.tableHeaders) and one for the table content ($scope.documentsList).
The code for the calls is below:
$http.get('http://localhost:3000/tableCount').then(function (res) {
if (res.data.numberoftables) {
$scope.count = res.data.numberoftables;
var i;
$scope.documentsList = new Array();
$scope.tableHeaders = new Array();
for (i = 1; i <= $scope.count; i++) {
var tableNo = i;
//loop through tables
***$http.get('http://localhost:3000/doclist/' + tableNo).then(function (resDocs) {
//get table content
if (resDocs.data) {
***$scope.documentsList.push(resDocs.data);
}
});
$http.get('http://localhost:3000/tableColumns/' + tableNo).then(function (resHeads) {
//get table headers
if (resHeads.data) {
$scope.tableHeaders.push(resHeads.data);
}
});
}
}
});
Just for fun, here's our HTML:
<div id="documentTables">
<div ng-repeat="tableID in getNumberArray(count)">
<table st-table="documentsList[tableID]" class="table table-condensed table-hover">
<thead>
<tr st-safe-src="tableHeaders[tableID]">
<th ng-repeat="col in tableHeaders[tableID]">
{{col}}
</th>
</tr>
<tr st-safe-src="tableHeaders[tableID]" colspan="{{tableHeaders[tableID].length}}">
<th colspan="1" ng-repeat="col in tableHeaders[tableID]">
<input id="{{col + 'searchbox' + tableID}}" st-search="col" placeholder="{{col}}" class="input-sm form-control" type="search" />
</th>
</tr>
</thead>
<tbody>
<tr st-safe-src="documentsList[tableID]" ng-repeat="document in documentsList[tableID]">
<td ng-repeat="col in tableHeaders[tableID]">
{{document[col].value}}
</td>
</tr>
</tbody>
<tfoot>
<tr>
<td colspan="{{tableHeaders[tableID].length}}" class="text-center">
<div st-pagination="" st-items-by-page="10" st-displayed-pages="7"></div>
</td>
</tr>
</tfoot>
</table>
</div>
</div>
The problem we're coming across is that no actual data is displayed in the tables. Somewhere between the starred lines (***) in the JS above, an empty array for each table is added. So if we have two tables in our database, we end up with documentsList = Array[4] with documentsList[0] and documentsList[1] being empty arrays. Why/where would this be happening, and how can I fix it?
Note - if you'd like to test this out without our endpoints, try out these variables:
$scope.count = 2;
$scope.tableHeaders = [["TabNo", "Document", "PageNumber", "Date"],["TabNo", "Document", "PageNumber"]];
$scope.documentsList = [];
$scope.documentsList.push(JSON.parse('[{"idANX":1,"pathANX":"Product Overview.pdf","isHeaderANX":1,"isExcludedANX":0,"isNotHyperlinkedANX":0,"isSubHeaderANX":0,"HyperlinkedColumnANX":1,"TabNo":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":false,"value":"1"},"Document":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"Product Overview.pdf","isLastCell":false,"value":"Product Overview"},"PageNumber":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":false,"value":"1 - 2"},"Date":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":true,"value":"18 August 2015"}},{"idANX":2,"pathANX":"Spec.pdf","isHeaderANX":0,"isExcludedANX":0,"isNotHyperlinkedANX":0,"isSubHeaderANX":0,"HyperlinkedColumnANX":1,"TabNo":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":false,"value":"2"},"Document":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"Spec.pdf","isLastCell":false,"value":"Spec"},"PageNumber":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":false,"value":"3 - 4"},"Date":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":true,"value":"1 April 2015"}}]');
$scope.documentsList.push(JSON.parse('[{"idANX":1,"pathANX":"Product Overview.pdf","isHeaderANX":0,"isExcludedANX":0,"isNotHyperlinkedANX":0,"isSubHeaderANX":0,"HyperlinkedColumnANX":1,"TabNo":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":false,"value":"1"},"Document":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"Product Overview.pdf","isLastCell":false,"value":"Product Overview"},"PageNumber":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":false,"value":"1 - 2"}},{"idANX":2,"pathANX":"Spec.pdf","isHeaderANX":0,"isExcludedANX":0,"isNotHyperlinkedANX":0,"isSubHeaderANX":0,"HyperlinkedColumnANX":1,"TabNo":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":false,"value":"2"},"Document":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"Spec.pdf","isLastCell":false,"value":"Spec"},"PageNumber":{"colspan":0,"rowspan":0,"hyperlinkedDoc":"","isLastCell":false,"value":"3 - 4"}}]');
$scope.getNumberArray = function (num) {
var n = new Array(num);
var i;
for (i = 0; i < num; i++) {
n[i] = i;
}
return n;
}
$scope.rowClass = function (row) {
if (row.isSubHeaderANX == 1) {
return 'subheader';
} else if (row.isHeaderANX == 1) {
return 'header';
}
else { return 'doctablerow'; }
}
Also pagination and filtering don't work but that's an issue for when our table actually contains data.
Edit: Probably should have mentioned that this is using angular-smart-table
You should take a look at how asynchronous requests and promises works. To make your code run you could do this: var promises = []; // Creates an array to store the promises
$http.get('http://localhost:3000/tableCount').then(function (res) {
if (res.data.numberoftables) {
$scope.count = res.data.numberoftables;
var i;
$scope.documentsList = new Array();
$scope.tableHeaders = new Array();
var promises = [];
for (i = 1; i <= $scope.count; i++) {
var tableNo = i;
$scope.getHttp(i).then(function(resp){
$scope.documentsList.push(resolutions[0]);
$scope.tableHeaders.push(resolutions[1]);
});
}
}
});
$scope.getHttp = function(tableNo){
var promise;
promise = $http({
url:'http://localhost:3000/doclist/' + tableNo,
method:'get'
});
promises.push(promise);
promise = $http({
url:'http://localhost:3000/tableColumns/' + tableNo,
method:'get'
});
promises.push(promise);
return $q.all(promises);
}
Make the changes accordingly. I did not test it because dont have complete controller.But for sure, if you are looping http, make sure promises are stored.
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 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
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 )
})
}
}