Popping from an array of objects - javascript

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();
}
});
});
});

Related

How can I remove a JSON inside a JSON by a key using jQuery?

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)

How to splice all the items in AngularJS

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
}

angular.element() cannot find DOM element

I have a table of users that is populated dynamically from a Firebase database:
<table id="users">
<thead>
...
</thead>
<tbody>
<tr ng-repeat="user in users">
<td data-user-id="{{user.$id}}">{{user.$id}}</td>
</tr>
</tbody>
</table>
function loadUsers($scope, DatabaseFactory) {
DatabaseFactory.users.on('value', function(snapshot) {
var users = snapshot.val();
for(var id in users) {
var user = users[id];
// Find cell with user ID
var element = angular.element('#users tr td[data-user-id="' + user.id + '"]'); // 1
console.log(element); // 2
}
});
};
In loadUsers(), I'm trying to find a cell which contains a particular user ID. The log statement above returns an array with length 0. Why does this happen? When I try the statements 1 and 2 in the Chrome console, it works fine.
Put your loop inside viewContentLoaded event.Its look like things are asycnhronous.
$scope.$on('$viewContentLoaded', function () {
for (var id in users) {
var user = users[id];
// Find cell with user ID
var element = angular.element('#users tr td[data-user-id="' + user.id + '"]'); // 1
console.log(element); // 2
}
});
What worked for me was turning loadUsers() into a directive. It is called after generating the DOM elements with ngRepeat.
HTML:
<tr ng-repeat="user in users" load-users>
Angular:
var app = angular
.module('app', ['firebase'])
.directive('loadUsers', loadUsers);
function loadUsers($scope, DatabaseFactory) {
return function(scope, element, attrs) {
if (scope.$last) {
...
}
}
};

Selecting a row in table based on content and clicking link from selected row - Protractor

I have a page object that looks like this:
<table border>
<th>Email</th>
<th>action</th>
<tr current-page="adminUsers.meta.page">
<td>admin#example.com</td>
<td>Delete permanently</td>
</tr>
<tr current-page="adminUsers.meta.page">
<td>matilda#snape.com</td>
<td>Delete permamently</td>
</tr>
</table>
I want to create a method that will enable me to delete a user based on his email address.
This is what I came up with, basing on How to find and click a table element by text using Protractor?:
describe('Admin panel', function() {
it('admin deletes a user', function() {
var re = new RegExp("matilda#snape.com");
var users_list = element.all(by.xpath("//tr[#current-page='adminUsers.meta.page']"));
var delete_btn = element(by.xpath("//a[contains(text(), 'Delete permamently')]"));
users_list.filter(function(user_row, index) {
return user_row.getText().then(function(text) {
return re.test(text);
});
}).then(function(users) {
users[0].delete_btn.click();
});
// some assertion, not relevant right now
});
});
First I'm trying to filter the row in which there's a user I want delete (array with all rows fitting my filter, then selecting the first row - should be one row anyway) and then click corresponding Delete button.
However, from my debugging I know that the method ignores the filtering and clicks the first Delete button available in the table and not the first from filtered elements.
What am I doing wrong?
In this particular case, I would use an XPath and its following-sibling axis:
function deleteUser(email) {
element(by.xpath("//td[. = '" + email + "']/following-sibling::td/a")).click();
}
I agree with #alexce's short & elegant answer but #anks, why don't you delete inside your filter??
describe('Admin panel', function() {
it('admin deletes a user', function() {
var re = new RegExp("matilda#snape.com");
var users_list = element.all(by.xpath("//tr[#current-page='adminUsers.meta.page']"));
var delete_btn = element(by.xpath("//a[contains(text(), 'Delete permamently')]"));
users_list.filter(function(user_row, index) {
return user_row.getText().then(function(text) {
return if(re.test(text)) { //assuming this checks the match with email id
user_row.delete_btn.click();
}
});
})
// some assertion, not relevant right now
});
});

How to populate/present added datatables row with data?

I am performing some ASP.NET gridview conversions using the datatables.net plug-in. The answer to why I am doing this is more involved and can be debated. However, I need some help with one of the issues I am running into.
Using Javascript to convert the gridview on the page was actually quite simple and works well. The major issue is that I want to have a fixed 'total' row (footer) within the body of the datatable so that it remains responsive just like the rest of the table.
I have attempted to add a footer using the code-behind, and I can populate that footer with total data, but it is not responsive with the rest of the table. I am assuming because the <tfoot> is outside of the <tbody>.
Using javascript, I have successfully added a new datatable row and I can output the data to the console, but I am unable to populate the added row with the object data.
Javascript:
var sum;
$(document).ready(function () {
var table = $('#cphPage_gvTaxColl').DataTable();
//convert string to int
var intVal = function (i) {
var j = $("<span/>");
var txt = j.html(i).text();
// alert('txt :' + txt);
var myVal = typeof txt === 'string' ?
parseFloat(txt.replace(/[^\d.-]/g, '')) :
typeof txt === 'number' ?
i : 0;
return myVal || 0;
};
//format integer as currency
var formatSum = function (myVal) {
return accounting.formatMoney(myVal, {
symbol: "$",
precision: 2,
thousand: ",",
decimal: ".",
format: {
pos: "%s %v",
neg: "%s (%v)",
zero: "%s 0.00"
}
});
};
//add total row and determine index
table.row.add(['GRAND TOTAL']).draw();
var total_row = (table.row().count() + 1);
var total_col = (table.row(total_row).column().count + 1);
//alert
console.log('total row: ' + total_row);
//loop columns
table.columns('.sum').every(function () {
sum = this
.data()
.reduce(function (a, b) {
console.log('adding ' + intVal(a) + ' and ' + intVal(b));
return intVal(a) + intVal(b);
});
//alert
console.log('sum: ' + sum);
console.log('column row 2 val: ' + this.data().row([2]));
$(this.cell.node( total_row )).html(
formatSum(sum)
);
});
});
How do I present the object data within the datarow?
I am also receiving the following error message, and I am not certain which parameter is missing ( the 2nd and 3rd columns are null ):
Error message
I have included a screenshot with the console data, and if you need it I can provide the .aspx markup:
Page + cosole log output
I'm still learning the ins-and-outs of this stuff. Any guidance you can provide is greatly appreciated.
Thanks in advance!
Here is my solution:
The html-table for datatables should have <tfoot>
Something like this:
<table id='table'>
<tbody>
<tr><td></td></tr>
</tbody>
<tfoot>
<tr><td></td></tr>
</tfoot>
</table>
Define footerCallback field
Datatables initialization:
$('#table').dataTable({
...
"footerCallback": _footerDrawn
...
});
footerCallback:
I use server-side data, so my source of data for footer is just another field of of ajax-response:
_footerDrawn: function( row, data, start, end, display ) {
var table = $('#table').dataTables();
var json = table.api().ajax.json();
// assign values to each cell of the footer from json.total array
$.each( table.api().columns().indexes(), function( index ) {
$( table.api().column( index ).footer() ).html( json.total[index] );
});
}
}
json.total contains array of string to print in footer row

Categories