I have a table and I'm using AngularJS to display it, there's a clear button where if clicked all the rows will be deleted from the table. I am using splice to do this but when I try to splice it, only the 1st and 3rd row get splice.
How do I splice all of the rows?
self.row.length = 3;
for (var index=0; index < self.row.length; index++) {
if (self.row[index].DDelDetailId != 0) {
self.deletedRow.push(angular.copy(self.row[index]));
}
console.log("index: "+index+" Rows: "+self.row.length)
self.row.splice(index, 1)
}
I already looked at all the similar questions but none of them helped me. It can splice if the self.row.length is 1 but if it is greater than 1 it leaves 1 row.
Below is what was printed in the console log:
Index: 0 Rows: 3
Index: 1 Rows: 2
I push all the deleted row to self.deletedRow then if user clicks save then the deleted rows will be deleted in the database. Each row has a delete button so user can delete all rows or delete 1 specific row.
As you're moving the index forward while deleting rows, you're skipping rows:
iteration 1:
index = 0
arr: [0, 1, 2]
arr.splice(0, 1) => arr: [1, 2] // deletes first item
iteration 2:
index = 1
arr: [1, 2]
arr.splice(1, 1) => arr: [1] // deletes second item
iteration 3:
index = 2
arr: [1]
arr.splice(2, 1) => arr [1] // tries to delete third item
If you delete the first item all the time, you won't skip anything:
arr.splice(0, 1)
It's also more efficient to remove all rows: arr = [] or arr.length = 0,
if clicked all the rows will be deleted from the table
Why you are using splice for every row you can clear that whole array. So use self.row=[] instead of using splice().
As per the comment below: I actually push all the deleted row to self.deletedRow then if user clicks save then the delete rows
assign the row values to self.deletedRow before delete your all rows.
self.deleteAll=function()
{
self.deletedRow = self.row;
self.row = [];
}
this Above way for all rows
and this below way for selected rows
self.deleteSingleRow = function(currentObject)// currentObject is `ng-repeat` directive object and you should be pass to the `deleteSingleRow` in html
{
self.deletedRow.push(currentObject);
//do your delete service call and rebind the `row` array
}
you can achieve this using splice itself
Here is the working example for whole requirement
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<link data-require="bootstrap#3.3.7" data-semver="3.3.7" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.21.0/moment.min.js"></script>
<script data-require="angular.js#1.6.6" data-semver="1.6.6" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.6/angular.min.js"></script>
<script>
(function() {
var app = angular.module("myApp", []);
app.controller('testCtrl', function($scope) {
var self = this;
self.data = [{"Product":"Body Spray","Location":"USA","Dec-2017":"234","Jan-18":"789","Feb-18":"234","Mar-18":"789","Apr-18":"234"},{"Product":"Groceries","Location":"USA","Dec-2017":"234","Jan-18":"789","Feb-18":"234","Mar-18":"789","Apr-18":"234"},{"Product":"Ready Cook","Location":"USA","Dec-2017":"234","Jan-18":"789","Feb-18":"234","Mar-18":"789","Apr-18":"234"},{"Product":"Vegetables","Location":"USA","Dec-2017":"234","Jan-18":"789","Feb-18":"234","Mar-18":"789","Apr-18":"234"}];
self.deletedData = [];
self.duplicateData = angular.copy(self.data);
self.clearData = function(element){
if(element){
var index = self.data.indexOf(element);
if(index > -1){
self.deletedData.push(angular.copy(element));
self.data.splice(index, 1);
}
}
else{
self.deletedData = angular.copy(self.data);
self.data.splice(0, self.data.length);
}
};
self.resetData = function(element){
//The table order wont change
self.data = angular.copy(self.duplicateData);
//The table order will change in this
/*angular.forEach(self.deletedData, function (item, index) {
self.data.push(item);
});*/
self.deletedData = [];
};
});
}());
</script>
</head>
<body ng-controller="testCtrl as ctrl">
<button ng-click="ctrl.clearData()">Delete All</button>
<button ng-click="ctrl.resetData()">Reset All</button>
<table class="table">
<thead>
<tr>
<th data-ng-repeat="(key, val) in ctrl.data[0] as header">
{{ key }}
</th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="row in ctrl.data track by $index">
<td data-ng-repeat="(key, val) in ctrl.data[0]">
{{ row[key] }}
</td>
<td>
<button ng-click="ctrl.clearData(row)">Delete</button>
</td>
</tr>
</tbody>
</table>
</body>
</html>
It is because self.row.length is changing dynamically as you are splicing.
use a temp variable and store length of your array to delete all rows
e.g
var temp = self.row.length;
for(var index=0;index<temp;index++) {
//splice
}
Related
I an trying to determine how to remove one or more specific rows from an array of objects.
In the page, I have a text box for user to enter a user ID; click "Find" button and I do a search in AD and add a row to a table with user's display name and user ID. In addition to this, for later processing, I store in an array of objects some info on user in addition to display name and user ID.
Also, I have a checkbox column in above table that user can select and remove one or more of the added users; I can correctly rebuild the table displaying data but don't know how, at this point, remove one or more rows from the array (i.e. the deleted users).
This is what I have:
HTML:
<table class="adminList" style="width:100%">
<thead>
<tr>
<th></th>
<th>User Name</th>
<th>User ID</th>
</tr>
</thead>
<tbody>
</tbody>
</table>
<button type="button" class="btn-primary btn-sm delete-admin-row">Delete User(s)</button>
JS/jQuery:
var admins = [];
<Somehow, using ajax, get user's data from AD>
var fn = userData.FirstName;
var mi = userData.Initial;
var ln = userData.LastName;
var name = userData.DisplayName;
var email = userData.Email;
var userid = userData.UserID;
//push the object onto the array
admins.push({
"fn" : fn,
"mi" : mi,
"ln": ln,
"email": email,
"userid": userid
});
// Display in admins list
var markup = "<tr><td><input type='checkbox' name='record'></td><td>" + name + "</td><td>" + userid + "</td></tr>";
$(".adminList tbody").append(markup);
FURTHER DOW IN THE CODE:
$(document).ready(function(){
$(".delete-admin-row").click(function(){
$(".adminList tbody").find('input[name="record"]').each(function(){
if($(this).is(":checked")){
$(this).parents("tr").remove();
// HERE I NEED TO POP THE ARRAY
}
});
});
});
Not sure if removing the checkbox column and having instead an 'x' image next to each row, associate some property of user data with this 'x' image, say User ID, as 'data-xxx' and accessing it in delete function would be easier; but I still wouldn't know how to access and delete the row from array based on 'userid'.
Update
Following the suggestions made, I made the changes but noticed that IE does not understand => nor findIndex. So, I made the following changes but still get -1 as returned index.
$(".delete-admin-row").click(function(){
$(".adminList tbody").find('input[name="record"]').each(function(){
if($(this).is(":checked")){debugger
$(this).parents("tr").remove();
// Both of these return the userid
const useridToFind1 = this.dataset.userid;
const useridToFind = $(this).closest("tr").find("td:eq(2)").text();
//const index = admins.findIndex(admin => admin.userid === useridToFind);
const index = admins.indexOf(function (e) { return e.userid === useridToFind });
if (index !== -1) { // Always -1
admins.splice(index, 1);
}
}
});
});
I'd add the UserID to a data attribute of the input in a TR, so that when iterating over checked inputs, you can take that value and use .findIndex to see if a matching UserID object exists in the array. Then you can splice it:
var markup = "<tr><td><input data-userid='" + userid + "' type='checkbox' name='record'></td><td>" + name + "</td><td>" + userid + "</td></tr>";
and
$(".delete-admin-row").click(function(){
$(".adminList tbody").find('input[name="record"]:checked').each(function(){
$(this).parents("tr").remove();
const useridToFind = Number(this.dataset.userid);
const index = admins.findIndex(admin => admin.userid === useridToFind);
if (index !== -1) {
admins.splice(index, 1);
}
});
});
I'm assuming the userid is a number, thus the need for a type cast in the .find (since a dataset element will always be a string).
If you have to use an obsolete 7-year-old browser, dumb down the syntax and use a different iteration method:
"use strict";
$(".delete-admin-row").click(function() {
$(".adminList tbody")
.find('input[name="record"]:checked')
.each(function() {
$(this)
.parents("tr")
.remove();
var useridToFind = Number(this.dataset.userid);
var foundIndex = -1;
admins.forEach(function(admin, index) {
if (admin.userid === useridToFind) foundIndex = index;
});
if (index !== -1) {
admins.splice(index, 1);
}
});
});
Just use splice method, what you need to know - index of array (first element - 0)
var a = [1, 2, 3, 4, 5];
a.splice(1, 2); // delete 2 elements starting from second element
// (second, because first element in array has index - 0,
// so index 1 - it's second element)
// a will contain -> [1, 4, 5]
Get the user ID from the third <td> in the current row. Find the index of the array element with that userid, and remove it with splice().
$(document).ready(function() {
$(".delete-admin-row").click(function() {
$(".adminList tbody").find('input[name="record"]').each(function() {
if ($(this).is(":checked")) {
let userid = $(this).closest("tr").find("td:eq(2)").text();
let index = admins.findIndex(el => el.userid == userid);
if (index >= 0) {
admins.splice(index, 1);
}
$(this).parents("tr").remove();
}
});
});
});
I have the following JSON structure:
[
{"key":1,"idProduct":"Monitor","obsProduct":""},
{"key":2,"idProduct":"Mouse","obsProduct":""},
{"key":3,"idProduct":"Keyboard","obsProduct":""},
{"key":4,"idProduct":"Processor","obsProduct":""}
]
And the following HTML table (representing the JSON):
When user click on "Remove" button, I need to remove the corresponding iten on JSON. When user click, I can capture the key, so I need to remove iten using the key value.
Assuming that the user click on "Remove" button on "Mouse", so, the JSON needs to return that way:
[
{"key":1,"idProduct":"Monitor","obsProduct":""},
{"key":3,"idProduct":"Keyboard","obsProduct":""},
{"key":4,"idProduct":"Processor","obsProduct":""}
]
How can I do this?
HTML of table:
<div class="row">
<div class="col-md-12">
<div class="table-responsive">
<table class="table table-bordered table-hover" id="table-products">
<thead style="background-color: #f2f2f2">
<tr class="text-center">
<th style="width: 40%">Product</th>
<th style="width: 40%">Obs</th>
<th style="width: 20%">Action</th>
</tr>
</thead>
<tbody>
<tr>
<td colspan="3">No iten!</td>
</tr>
<!-- Will be generated! -->
</tbody>
</table>
</div>
</div>
</div>
JS that generates the lines:
var i = 1;
var itensArray = [];
var nameProduct = $("input[name='nameProd']").val();
var obsProduct = $("input[name='obsProd']").val();
if(i <= 5)
{
var newItem = '<tr class="itemRow"><td>' + nameProduct + '</td><td>' + obsProduct + '</td><td><button type="button" name="' + i + '" class="btn btn-outline-danger btn-sm" id="btnRemoveProduct"><i class="far fa-trash-alt"></i> Remove</button></td></tr>';
if(i == 1)
$("#table-products tbody").html(newItem);
else
$("#table-products tbody").append(newItem);
var products = {
key: i,
idProduct: nameProduct,
obsProduct: obsProduct
};
itensArray.push(products)
var aux = JSON.stringify(itensArray);
i++;
console.log(aux);
}
JS that remove lines from table:
$("#table-products").on('click', '#btnRemoveItem', function(){
var idProduct = $(this).attr("name");
$(this).closest('tr').remove();
toastr.success('Iten removed!');
i--;
if(i == 1)
{
var defaultItem = '<tr><td colspan="3">No iten added!</td></tr>';
$("#table-products tbody").html(defaultItem);
}
/* I NEED TO PUT HERE THE CODE TO REMOVE ITEM FROM JSON */
});
Iterating through the array of objects and finding the matching object having key as the key value which is captured when clicking on remove button.
var itensArray = [
{"key":1,"idProduct":"Monitor","obsProduct":""},
{"key":2,"idProduct":"Mouse","obsProduct":""},
{"key":3,"idProduct":"Keyboard","obsProduct":""},
{"key":4,"idProduct":"Processor","obsProduct":""}
];
//key is captured in a variable on click of remove button.
var idProduct = 2;
for (var i = 0; i < itensArray.length; i++) {
var obj = itensArray[i];
if (obj.key === idProduct) {
itensArray.splice(i, 1);
}
}
console.log(itensArray);
The simplest way is to assing some id to button which responds entry id. While delete button is clicked you can extract ID from button and you know which row to delete.
You said you can capture the key, so I'm assuming you've got some kind of click handler function that's receiving that key. From there the best way to do it is probably to create a new array that filters out the element that matches, and replace the old array with the new. (Generally considered a better practice than mutating the array in place with something like .splice)
const capturedKey = event.target.value // or however you're capturing it
yourArray = yourArray.filter(obj => obj.key !== capturedKey)
You can find the index using Array.findIndex (It will return on the first match so it is an optimized way if your 'key' values are unique)
const myArray = [
{"key":1,"idProduct":"Monitor","obsProduct":""},
{"key":2,"idProduct":"Mouse","obsProduct":""},
{"key":3,"idProduct":"Keyboard","obsProduct":""},
{"key":4,"idProduct":"Processor","obsProduct":""}
];
const idToDelete = 2; // retrieved via clicked btn
const idx = myArray.findIndex( item => item.key == idToDelete );
if(idx != -1) {
myArray.splice(idx, 1);
}
console.log(myArray)
I want to make excel like cell system but dynamically, Where i have to select every cell individually.
My desired output:
If i have 2 and 10, the output will be like that(above image). 2 means 2 row and 10 means 10 columns. the 2 and 10 is from database then javascript/angularjs should make the table according to those values. The second thing is that i have to select every individual cell using javascript. For example, i want to select B7 and if i click on that cell, an alert box will be shown with the selected cell number.
In real i want to store some values regarding that cell. How can i make every cell clickable? Any suggestion? I prefer angularjs.
Edit:
Acually i want to make Yard graphical view. Staff will select cell and input goods weight(in bootstrap modal). Then save. Next time if a cell/slot has weight before, it will be in different color(red-means the cell/slot is already filled with goods) and if he click on that cell , all details will be shown regarding to that cell like weight. Database table will store yard_id,cell_id,weiight. How can make query to get details from database to have my cell filled with color and show details if the cell has details before?
Edit 2:
You make an object in factory to set value in cell :
database[createKey({
row: 'A',
column: 1
})] = 12;
Here row A and column 1 is red colored by default. But in real app, i will have data for some cells like:
[{"row":"A","column":1,"weight":100},
{"row":"A","column":2,"weight":200}
].
Then how can i set those value on specific cells and have different bg color?
I want to use this method( loadData() ) to set color(like the one you set-red color) in the cell those have value stored in database when page load for the first time :
function loadData() {
fakeHttp.get('api/weights').then(function (result) {
$scope.weights = result.data;
console.log(result.data)
});
};
I will pass json data in result.data parameter(given above).
...in real app, i will have data for some cells like:... how can i set those value on specific cells and have different bg color?
You can keep loadData the same and change $scope.getWeight to accommodate the format of the data. This takes a dependency on lodash's find, since that makes things more concise. If you don't want to do that you can replace _.find with your own find method that does the same thing - I'll leave that as an exercise for you :)
http://plnkr.co/edit/b0q4qTyNjQp7J2IB7ayf?p=preview
HTML
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.0.1/lodash.js"></script>
JavaScript
$scope.getWeight = function (row, column) {
if ($scope.weights) {
// See if there's a record with the row and column.
var record = _.find($scope.weights, {
row: row,
column: column
});
// Was a record found with the row and column?
if (record) {
// If so return its weight.
return record.weight;
}
}
};
Staff will select cell and input goods weight(in bootstrap modal). Then save. Next time if a cell/slot has weight before, it will be in different color(red-means the cell/slot is already filled with goods) and if he click on that cell , all details will be shown regarding to that cell like weight...How can make query to get details from database to have my cell filled with color and show details if the cell has details before?
I didn't completely understand, but here's roughly what you could do for the parts I did understand. This assumes you have 3 endpoints - GET api/weights to get the weights; GET api/weight to get the weight for a single cell and POST api/weight to update a weight. You'll need to replace fakeHttp with $http and the actual url's. I don't know what Yard or Yard_id is.
http://plnkr.co/edit/aAiYbChTqmAwgky0WOJ3?p=preview
// TODO: Replace fakeHttp with $http
var module = angular.module('myApp', ['ui.bootstrap']);
module.controller('MainCtrl', function ($scope, $uibModal, fakeHttp) {
$scope.rows = [
'A',
'B'
];
$scope.columns = [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10
];
$scope.select = function (row, column) {
var modalInstance = $uibModal.open({
templateUrl: 'myModal.html',
controller: 'ModalInstanceCtrl',
resolve: {
row: function () {
return row;
},
column: function () {
return column;
}
}
});
modalInstance.result.then(loadData);
};
$scope.getWeight = function (row, column) {
if ($scope.weights) {
var key = createKey({
row: row,
column: column
});
return $scope.weights[key];
}
};
loadData();
function loadData() {
fakeHttp.get('api/weights').then(function (result) {
$scope.weights = result.data;
});
};
function createKey(data) {
var key = {
row: data.row,
column: data.column
};
return JSON.stringify(key);
}
});
module.controller('ModalInstanceCtrl', function ($scope, row, column, fakeHttp, $uibModalInstance) {
$scope.row = row;
$scope.column = column;
fakeHttp.get('api/weight', {
row: row,
column: column
}).then(function (result) {
$scope.weight = result.data;
});
$scope.save = function () {
fakeHttp.post('api/weight', {
row: row,
column: column,
weight: $scope.weight
}).then(function () {
$uibModalInstance.close();
});
};
});
module.factory('fakeHttp', function ($q) {
var fakeHttp = {};
var database = {};
database[createKey({
row: 'A',
column: 1
})] = 12;
fakeHttp.get = function (url, data) {
if (url === 'api/weight') {
var key = createKey(data);
return $q.when({ data:
database[key]
});
} else if (url === 'api/weights') {
return $q.when({ data:
database
});
} else {
alert('invalid url: ' + url);
}
};
fakeHttp.post = function (url, data) {
if (url === 'api/weight') {
var key = createKey(data);
database[key] = data.weight;
return $q.when({});
} else {
alert('invalid url: ' + url);
}
};
return fakeHttp;
function createKey(data) {
var key = {
row: data.row,
column: data.column
};
return JSON.stringify(key);
}
});
index.html
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<link data-require="bootstrap#3.3.7" data-semver="3.3.7" rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" />
<script data-require="angular.js#1.6.2" data-semver="1.6.2" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.2/angular.js"></script>
<script data-require="angular.js#1.6.2" data-semver="1.6.2" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.2/angular-route.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/1.3.3/ui-bootstrap-tpls.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-controller="MainCtrl">
<table border="1" cellspacing="0">
<tbody>
<tr ng-repeat="row in rows">
<td ng-repeat="column in columns" style="width: 100px; cursor: pointer"
ng-style="{ background: getWeight(row, column) ? 'red' : '' }"
ng-click="select(row, column)">
{{row}}{{column}}
</td>
</tr>
</tbody>
</table>
</body>
</html>
myModal.html
<div class="modal-body">
Please enter weight for {{ row }}{{ column }}
<input type="text" class="form-control"
ng-model="weight" />
<button class="btn btn-primary"
ng-click="save()">Save</button>
</div>
How can i make every cell clickable?
Here's how to make every cell clickable in AngularJS.
http://plnkr.co/edit/XKa5WwjyYTugDZ744iWB?p=preview
Your question was very unclear. I couldn't tell what you wanted, exactly.
JavaScript:
module.controller('MainCtrl', function($scope) {
$scope.rows = [
'A',
'B'
];
$scope.columns = [
1,
2,
3,
4,
5,
6,
7,
8,
9,
10
];
$scope.select = function(row, column) {
if ($scope.selectedRow === row && $scope.selectedColumn === column) {
$scope.selectedRow = undefined;
$scope.selectedColumn = undefined;
} else {
$scope.selectedRow = row;
$scope.selectedColumn = column;
}
};
});
HTML:
<body ng-controller="MainCtrl">
<table border="1" cellspacing="0">
<tbody>
<tr ng-repeat="row in rows">
<td ng-repeat="column in columns" style="width: 100px; cursor: pointer"
ng-click="select(row, column)"
ng-style="{ background: row == selectedRow && column == selectedColumn ? 'yellow' : 'none' }">
{{row}}{{column}}
</td>
</tr>
</tbody>
</table>
<br>
Selected (row, column):
<br>
({{selectedRow || 'undefined'}}, {{selectedColumn || 'undefined'}})
</body>
I would like to hide all items until filter is selected. I can't figure out from the documentation if there's an option for that and how to configure it.
For example
I have filters:
Shirts | Pants | Shoes (filters)
---------------------- (grid with items that need to be filtered)
Item 1 | Item 2 | Item 3
... | ... | ...
... | ... | ...
... | ... | ...
----------------------
How can I make all items be hidden until first filter is selected?
Thanks!
I'm using jQuery isotope plugin for that.
From the docs:
To show only .metal items, the jQuery script would be:
$('#container').isotope({ filter: '.metal' });
My first idea to hide all items was setting the filter option to an empty string, but it did not work. However, if you set it to some non-existing class, then all items will be hidden:
$('#container').isotope({ filter: '.dummy' });
Here is the jsfiddle.
Comments of #RizkySyaiful:
Also, if you this a code snippet http://codepen.io/desandro/pen/JGKyrY (made by the creator of Isotope), you can just simply change this line
// combine inclusive filters
var filterValue = inclusives.length ? inclusives.join(', ') : '*';
to
// combine inclusive filters
var filterValue = inclusives.length ? inclusives.join(', ') : '.dummy';
Something like this should work
$('.filter').click(function(){
var type = $(this).attr('data-filter');
$('article[data-type="'+type+'"]').show();
});
Fiddle here:
http://jsfiddle.net/z6ngr/
Not very professional, but works.
Javascript only.
<html>
<head>
<script>
var col_1_items = new Array("Alex","Mark","John")
function editTable () {
var rows = col_1_items.length; //Or whatever like 1,2,3 ..
var cols = 1; //Each col should have its array of items !
var myTable = document.getElementById('myTable');
//delete old rows
for(var i = myTable.rows.length-2; i > -1; i--)
{
myTable.deleteRow(i);
}
for (r = 0; r < rows; r++) {
var row = myTable.insertRow(r);
for (c = 0; c < cols; c++) {
var cell = row.insertCell(c);
if (document.getElementById('filter').value == "All") {
cell.innerHTML = col_1_items[r]
}
else {
var indexOfFilterInArray = col_1_items.indexOf(document.getElementById('filter').value)
cell.innerHTML = col_1_items[indexOfFilterInArray];
myTable = null //Break the parent cycle that otherwise would go on adding rows.
break;
}
}
}
}
</script>
</head>
<body>
<table id="myTable" rules="all" border="1">
<tr><td colspan="2"> Filter<select id='filter'>
<option value="All">All</option>
<option value="Alex">Alex</option>
</select>
<button onclick="editTable()">Go</button>
</td></tr>
</table>
</body>
</html>
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.