Angular4 dynamic table reset values on previous created row - javascript

I am creating a dynamic table which adds a row on click(). However, every add of the row resets the value of the previous row.
<tfoot>
<tr>
<td><button type="button" (click)="addRow()">Add row </td>
</tr>
</tfoot>
// html
<tbody>
<tr *ngFor="let row of rows">
<td><input name="something" type="text" [ngModel]="row.item.name </td>
</tr>
</tbody>
// component
...
this.item = {name: 'Superman'};
this.rows = [{
item: this.item
}];
....
this.addRow() {
this.rows.push({
item: this.item
});
}

[SOLVED] We just need to make the name of the input unique!
<tfoot>
<tr>
<td><button type="button" (click)="addRow()">Add row </td>
</tr>
</tfoot>
// html
<tbody>
<tr *ngFor="let row of rows; let i = index">
<td><input name="something_{{i}}" type="text" [ngModel]="row.item.name </td>
</tr>
</tbody>
// component
...
this.item = {name: 'Superman'};
this.rows = [{
item: this.item
}];
....
this.addRow() {
this.rows.push({
item: this.item
});
}

Because you're pushing same object reference in an array multiple times. So if you change this.item will effectively change the value in all newly added array element(since they carry the same object reference).
The solution would be, push clone object to an items array.
this.rows.push({
item: Object.assign({}, this.item); //created clone
});

Related

How to find the value from a row of table depending on the row of a button I clicked

No jQuery involve pls. I am just started learning javascript.
I want to find the class='id' of the table when I clicked on the class='detail' button.
I manage to point to class='id' but I can't get the value out of it, why?
var button = document.getElementsByClassName("detail");
for (var i in button) {
button[i].onclick = function() {
var row = this.closest("tr");
var id = row.getElementsByClassName("id");
var value = id.innerText;
console.log(id);
console.log(value); //show undefined here
}
}
<table>
<tbody>
<tr>
<td class="id">123</td>
<td class="name">abc</td>
<td><button class="detail">detail</button></td>
</tr>
<tr>
<td class="id">456</td>
<td class="name">def</td>
<td><button class="detail">detail</button></td>
</tr>
</tbody>
</table>
where would need to change? I must use class here, as the table generated through javascript. thanks.
getElementsByClassName returns HTMLCollection containing multiple matching elements. Like an array, you can access the first element in the collection with [0]
var button = document.getElementsByClassName("detail");
for (var i in button) {
button[i].onclick = function () {
var row = this.closest("tr");
var id = row.getElementsByClassName("id");
var value = id[ 0 ].innerText;
console.log(id);
console.log(value);
}
}
<table>
<tbody>
<tr>
<td class="id">123</td>
<td class="name">abc</td>
<td><button class="detail">detail</button></td>
</tr>
<tr>
<td class="id">456</td>
<td class="name">def</td>
<td><button class="detail">detail</button></td>
</tr>
</tbody>
</table>

How to dynamically add a row in a table when user input (postman-style-reactive-table)

How to dynamically add a row when atleast one character in input (like Postman behaves) in header tab.
1) When user enters any single character in td of first row then one row
should add below. When user deletes text from first row and when first row's
td are empty then delete below row.
2) keep track of text enetered in a model for every row input (ie key and
value);
3) when user enters any single character in a row in any td then checkbox
should be checked otherwise not.
.ts
model = new Model ('','','',..............);
tableRow = {
"key":[{
'name':'key1',
'keyInput':""
}],
"value":[{
'name':'value1',
'keyInput':""
}]
}
addRow(event){
if(event.target.length < 2){
let data = {
'name':'key' + (this.tableRow.key.length + 1),
'keyInput':""
}
this.tableRow.key.push(data )
}else{
this.tableRow.key.pop();
}
}
.html
<div class="col-12 table-responsive">
<table class="table">
<thead>
<th>
<td></td>
<td>Key</td>
<td>Value</td>
</th>
</thead>
<tbody>
<tr *ngFor="let row of tableRow.key; let i = index;">
<td><input type="checkbox"></td>
<td><input (keyup)="addRow($event)" [(ngModel)]="model.key" type="text"></td>
<td><input [(ngModel)]="model.value" type="text"></td>
</tr>
</tbody>
</table>
</div>
Here's a very rudimentary implementation of what you might want.
component:
export class AppComponent {
title = 'stack-solve';
//defining row of objects and initializing first row with empty values
//All values entered on the screen are bound to this array
rows: any[] = [{
checked:false,
key:'',
value:''
}];
//This function is called on keyup and checks the checkbox on that row and adds new row if the action was on the last row
checkAndAddRow(i){
this.rows[i].checked = true;
if(this.rows.length - 1 == i){//insert new empty row, incase this keyup event was on the last row, you might want to enhance this logic...
this.rows.push({
checked:false,
key:'',
value:''
})
}
}
}
template:
<div class="col-12 table-responsive">
<table class="table">
<thead>
<td></td>
<td>Key</td>
<td>Value</td>
</thead>
<tbody>
<tr *ngFor="let row of rows; let i = index">
<td><input [checked]="row.checked" type="checkbox"></td>
<td><input [(ngModel)]="row.key" type="text" name="key" (keyup)="checkAndAddRow(i)"></td>
<td><input [(ngModel)]="row.value" type="text" name="value" (keyup)="checkAndAddRow(i)"></td>
</tr>
</tbody>
</table>
</div>
app.module:
imports: [
BrowserModule,
FormsModule // need to import FormsModule if you don't have already
],
Your solution is close. The main things that I would modify is to keep track of the key/value/checked state for each row on the row itself, not on some external model. That way each row is self-maintaining and does not have outside side effects that modify other rows (other than the add/remove row, of course).
The other thing I changed is to capture the (input) event, rather than the (keyup) event. This is to allow for better mobile cut/paste and right click cut/paste support. (keyup) will not capture either of those events.
Working Example Here
import {Component} from '#angular/core';
#Component({
selector: "reactive-table",
template: `
<table>
<thead>
<tr>
<th>Selected</th>
<th>Key</th>
<th>value</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of data.rows; let $index = index;">
<td><input type="checkbox" [(ngModel)]="row.selected"/></td>
<td><input [value]="row.key" (input)="handleKeyChange($event, row, $index)" /></td>
<td><input [(ngModel)]="row.value" /></td>
</tr>
</tbody>
</table>
`,
styles: [`
`]
})
export class ReactiveTableComponent {
public data;
constructor() {
}
ngOnInit() {
this.data = {
rows: [
{
selected: false,
key: "",
value: "",
oldKey: ""
}
]
}
}
handleKeyChange(event, row, rowIndex) {
var newKey = event.target.value;
if (newKey !== row.oldKey) {
row.selected = true;
}
if (newKey === "" && rowIndex === this.data.rows.length -2) {
this.data.rows.pop();
row.selected = false;
}
else if (newKey !== "" && row.oldKey === "" && rowIndex === this.data.rows.length -1) {
this.data.rows.push({
selected: false,
key: "",
value: "",
oldKey: ""
});
}
row.oldKey = row.key = newKey;
}
}

Get ID of Table that contains a checkbox

I have many tables each one with an ID, (table1,2,3,...), and in each one I have many TD's <td><a href
example :
<table id="myTable1" class="someclass">
<tbody>
<tr>
<td>blablabla</td>
<td>random text</td>
<td>randomtext</td>
</tr>
</tbody>
</table>
</td>
<table id="myTable2" class="someclasse">
<tbody>
<tr>
<td>blablabla</td>
<td>random text</td>
<td>randomtext</td>
</tr>
</tbody>
</table>
</td>
(don't look at the HTML code it's not important for now )
My goal is to open all hrefs within the table "table X" then open them in new tab. I do that with
var els = document.getElementById("myTable1").querySelectorAll("a[href^='https://domaine.']");
for (var i = 0, l = els.length; i < l; i++) {
var el = els[i];
alert(el)
window.open (el,"_blank");
}
It works like a charm. Now I want to add a checkbox to each table, and if checked to open the href on "the" table I checked (I did some innerHTML to "insert" checkbox). Now my question, how can I get the table ID when I'll check the checkbox?
For example I check the table that have "table6" and then every link in that table gets opened.
table id=1 (checkbox)
table id=2 (checkbox)
etc
if i check the checkbox it will get the table with id 2
You can use closest to get the closest table, then you can get the id from that.
// List of checkboxes
let inputs = Array.from(document.querySelectorAll('input[type=checkbox]'))
// Add a click event to each
inputs.forEach(input => {
input.addEventListener('click', e => {
let target = e.currentTarget
// If the checkbox isn't checked end the event
if (!target.checked) return
// Get the table and id
let table = target.closest('table')
let id = table.id
console.log(id)
})
})
<table id="abc">
<tr>
<td><input type="checkbox"></td>
</tr>
</table>
<table id="def">
<tr>
<td><input type="checkbox"></td>
</tr>
</table>
<table id="ghi">
<tr>
<td><input type="checkbox"></td>
</tr>
</table>
<table id="jkl">
<tr>
<td><input type="checkbox"></td>
</tr>
</table>
You say that you are adding the checkbox dynamically, so you won't want to do a querySelectorAll like I did above. You will want to add it when it is created like this:
// List of tables
let tables = Array.from(document.querySelectorAll('table'))
// insert the checkbox dynamically
tables.forEach(table => {
table.innerHTML = '<tr><td><input type="checkbox"></td></tr>'
// Get the checkbox
let checkbox = table.querySelector('input[type=checkbox]')
// Add an eventlistener to the checkbox
checkbox.addEventListener('click', click)
})
function click(e) {
let target = e.currentTarget
// If the checkbox isn't checked end the event
if (!target.checked) return
// Get the table and id
let table = target.closest('table')
let id = table.id
console.log(id)
}
<table id="abc">
</table>
<table id="def">
</table>
<table id="ghi">
</table>
<table id="jkl">
</table>
…I want to add a checkbox to each table, and if [it's] checked…open the href [in] "the" table I checked…how can I get the table ID when I'll check the checkbox?
Given that you want to find the id of the <table> within which the check-box <input> is contained in order to select the <table> via its id property you don't need the id; you simply need to find the correct <table>.
To that end I'd suggest placing an event-listener on each of those <table> elements, and opening the relevant links found within. For example (bearing in mind that there are restrictions on opening new windows/tabs on Stack Overflow, I'll simply style the relevant <a> elements rather than opening them):
function highlight(e) {
// here we find the Static NodeList of <a> elements
// contained within the <table> element (the 'this'
// passed from EventTarget.addEventListener()) and
// convert that Array-like collection to an Array
// with Array.from():
Array.from(this.querySelectorAll('a'))
// iterating over the Array of <a> elements using
// Array.prototype.forEach() along with an Arrow
// function:
.forEach(
// here we toggle the 'ifCheckboxChecked' class-name
// via the Element.classList API, adding the class-name
// if the Event.target (the changed check-box, derived
// from the event Object passed to the function from the
// EventTarget.addEventListener function) is checked:
link => link.classList.toggle('ifCheckboxChecked', e.target.checked)
);
}
// converting the Array-like Static NodeList returned
// from document.querySelectorAll() into an Array:
Array.from(document.querySelectorAll('table'))
// iterating over the Array of <table> elements:
.forEach(
// using an Arrow function to pass a reference to the
// current <table> element (from the Array of <table>
// elements to the anonymous function, in which we
// add an event-listener for the 'change' event and
// bind the named highlight() function as the event-
// handler for that event:
table => table.addEventListener('change', highlight)
);
function highlight(e) {
Array.from(this.querySelectorAll('a'))
.forEach(
link => link.classList.toggle('ifCheckboxChecked', e.target.checked)
);
}
Array.from(document.querySelectorAll('table')).forEach(
table => table.addEventListener('change', highlight)
);
body {
counter-reset: tableCount;
}
table {
width: 80%;
margin: 0 auto 1em auto;
border: 1px solid limegreen;
}
table::before {
counter-increment: tableCount;
content: 'table' counter(tableCount);
}
a.ifCheckboxChecked {
background-color: #f90;
}
<table>
<tbody>
<tr>
<td><input type="checkbox"></td>
<td>cell 1</td>
<td>cell 2</td>
<td>cell 3</td>
</tr>
</tbody>
</table>
<table>
<tbody>
<tr>
<td><input type="checkbox"></td>
<td>cell 1</td>
<td>cell 2</td>
<td>cell 3</td>
</tr>
</tbody>
</table>
<table>
<tbody>
<tr>
<td><input type="checkbox"></td>
<td>cell 1</td>
<td>cell 2</td>
<td>cell 3</td>
</tr>
</tbody>
</table>
<table>
<tbody>
<tr>
<td><input type="checkbox"></td>
<td>cell 1</td>
<td>cell 2</td>
<td>cell 3</td>
</tr>
</tbody>
</table>
JS Fiddle demo.
References:
CSS:
::before pseudo-element
Using CSS Counters.
JavaScript:
Array.from().
Array.prototype.forEach().
Arrow Functions.
Element.querySelectorAll().
Event.
EventTarget.addEventListener().

How to remove an item from the array on button click

I have a table with few rows and delete button. I have stored all the list of table in an array 'arr'. How can I remove the selected item from that array on button click.
<table id="sum_table">
<tr class="titlerow">
<th>S.N.</th>
<th>Name</th>
<th>Action</th>
</tr>
<tr>
<td>1</td>
<td>John</td>
<td><button class="dm" data-id="0">Remove</button></td>
</tr>
<tr>
<td>2</td>
<td>Henry</td>
<td><button class="dm" data-id="1">Remove</button></td>
</tr>
</table>
var arr= [
["name", John],
["name", Henry]
];
function clickHandler(clickEvent) {
}
document.addEventListener('DOMContentLoaded', function() {
document.addEventListener('click', clickHandler);
});
Your array need to be an array of object and not an array of array. Also you can give a class to the name column of a table to access its value and then use findIndex to find the index of the name attribute in array and then splice to remove it.
$(function(){
var arr= [
{"name": "John"},
{"name": "Henry"}
];
$('.dm').click(function(){
var val = $(this).closest('tr').find(".name").text();
console.log(val);
var index = arr.findIndex(function(item) {return item.name == val})
console.log(index)
arr.splice(index, 1)
console.log(arr);
})
})
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="sum_table">
<tr class="titlerow">
<th>S.N.</th>
<th>Name</th>
<th>Action</th>
</tr>
<tr>
<td>1</td>
<td class="name">John</td>
<td><button class="dm" data-id="0">Remove</button></td>
</tr>
<tr>
<td>2</td>
<td class="name">Henry</td>
<td><button class="dm" data-id="1">Remove</button></td>
</tr>
</table>
Lets say you send the name of the person on click event then you can use array.splice method as shown below :
for(var i = arr.length - 1; i >= 0; i--) {
if(arr[i] === name) {
arr.splice(i, 1);
}
}
You have to note that it will delete all the values from array which has the same name.
With Index - Just send the data-id on click of button and splice the array on that index
arr.splice(dataid, 1)

how can I prevent Nested tables inside ng-repeat from populating in every row?

OK, so I want to create a dynamic nested hierarchy of tables. I get they data no problem, but using ng-repeat at each level causes the parent table to insert child data for a specific row into each row of the parent table. I want to prevent this, I have tried using ng-repeat-start and ng-repeat-end, however, because of the table nesting I cannot add the end tag in an appropriate place to stop the repeat.
UPDATE
Let me clarify a bit here, I have 3 nested tables, the top level table has list of groups, the first child table is a list of all of the items that belong to a specific group in the parent table. When the user clicks the expand button, I populate the child table based on which row in the parent table was clicked, this is OK, however the child table now shows up in each of the parent table rows instead of just the row that was clicked.
Plunker Link
http://plnkr.co/edit/RVOnf9wBF3TzXauzvMfF
HTML:
<table id="sicGroupTable" class="RadGrid_Metro">
<thead>
<tr>
<td>Sic Code Group</td>
</tr>
</thead>
<tbody ng-repeat="group in sicCodeGroup">
<tr class="rgRow">
<td class="rgExpandCol"><img class="rgExpand" ng-click="expandRow(group.GroupId)" ng-model="hideTwoDigitSub" /></td>
<td><input type="checkbox" ng-model="SelectGroup" /></td>
<td>{{group.GroupName}}</td>
</tr>
<tr ng-hide="hideTwoDigitSub">
<td></td>
<td>
<table id="sic2DigitTable" class="RadGrid_Metro">
<thead>
<tr>
<th>2 digit sic Code</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="twoDigitSic in twoDigitSicCodes" class="rgRow">
<td class="rgExpandCol"><img class="rgExpand" ng-click="expandRow(twoDigitSic.SicCode)" /></td>
<td><input type="checkbox" ng-model="Select2DigitSicCode" /></td>
<td>{{twoDigitSic.SICCode2}} - {{twoDigitSic.Title}}</td>
</tr>
<tr>
<td></td>
<td>
<table id="sic4DigitTable" class="RadGrid_Metro">
<thead>
<tr>
<th>4 digit sic code</th>
</tr>
</thead>
<tbody>
<tr class="rgRow">
<td class="rgExpandCol"><img class="rgExpand" ng-click="expandRow(sicCode.SicCode)" /></td>
<td><input type="checkbox" ng-model="Select2DigitSicCode" /></td>
<td>{{sicCode.SicCode}} - {{sicCode.Title}}</td>
</tr>
<tr>
<td></td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
JavaScript:
var app = angular.module("apptSetting", ['ngResource'])
app.factory('dataFactory',
function ($resource) {
return {
getSicGroups: $resource('../Packages/GetJsonSicCodeGroups'),
expandRow: $resource('../Packages/GetExpandedRowData')
}
});
app.controller('aPackageController', ['$scope', 'dataFactory', function ($scope, dataFactory) {
function init() {
$scope.hideTwoDigitSub = true;
$scope.hideFourdigitsub = true;
}
$scope.sicCodeGroup = dataFactory.getSicGroups.query({}, isArray = true);
$scope.twoDigitSicCodes = null;
$scope.expandRow = function (groupId, sicCode) {
if (groupId != undefined)
{
$scope.twoDigitSicCodes = dataFactory.expandRow.query({ GroupId: groupId }, isArray = true);
$scope.hideTwoDigitSub = false;
if (sicCode != null && sicCode != undefined && sicCode != "") {
if (sicCode.length == 2) {
$scope.hideTwoDigitSub = false;
$scope.twoDigitSicCodes = dataFactory.Get2DigitSicCodes.query({ GroupId: groupId }, isArray = true);
}
}
}
}
init();
}])
The issue is that you're using a single boolean hideTwoDigitSub to control all the trs created by your ngRepeat:
<tr ng-hide="hideTwoDigitSub">
So when you set $scope.hideTwoDigitSub = false; every ngHide within your ngRepeat gets that false and thus all the tr elements are shown.
Radio Button Fix
Instead of using a boolean I'd set hideTwoDigitSub to the groupId for the row you want to show (and maybe rename hideTwoDigitSub to showTwoDigitSub since the variable now indicates which row to show).
So inside your expandRow() function I'd set which row to show by changing:
$scope.hideTwoDigitSub = false;
to
$scope.hideTwoDigitSub = groupId;
And change the above tr to:
<tr ng-hide="hideTwoDigitSub != group.GroupId">
So the row will be hidden unless your control variable hideTwoDigitSub is not equal to the current groups GroupId.
Or it might be clearer to use ngShow (note I changed the hideTwoDigitSub to showTwoDigitSub in this example since it's clearer):
<tr ng-show="showTwoDigitSub == group.GroupId">
radio button plunker
Checkbox solution
This approach is easiest done switching hideTwoDigitSub to showTwoDigitSub- so everything below assumes that.
For this approach, inside init() I'd set your control variable to be an array:
$scope.showTwoDigitSub=[];
And then toggle the appropriate control inside expand:
$scope.showTwoDigitSub[groupId] = !$scope.showTwoDigitSub[groupId];
And use the array inside your html:
<tr ng-show="showTwoDigitSub[group.GroupId]">
checkbox plunker

Categories