There is a markup:
<div class="scroll">
<table class="table table-striped">
<thead>
<tr>
<th>Image</th>
<th>Name</th>
<th>Author</th>
<th>Year</th>
<th>ISBN</th>
<th>Price</th>
<th>Count</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let book of bookService.bookList">
<td><img src="../../{{book.ImagePath}}" width="100" height="150"></td>
<td>{{book.Name}}</td>
<td>{{book.Author}}</td>
<td>{{book.Year}}</td>
<td>{{book.ISBN}}</td>
<td>{{book.Price}}</td>
<td>{{book.Count}}</td>
<td>
<input type="text" name="Count" [(ngModel)]="Count">
<button class="btn btn-block btn-outline-success" (click)="onAdd(book, Count)"><i class="fa fa-plus"></i></button>
</td>
</tr>
</tbody>
The last column looks like this:
The problem is the following: when filling one TextBox, all the TextBoxes in the column are filled.
How can I solve this problem? Tried to give unique names to text fields and to thrust this cell in form, but all the same did not work.
You need to give unique name to the input field using angular template
<input [name]="'count' + i" >
Where i is the index from the *ngFor
But I think the major issue you have is that you need to bind book.Count instead of just Count
In latter case you'll have one variable called Count and you bind the same variable to all of the input fields. You need to attach the variable to the book itself so it is unique.
All your inputs have the same [(ngModel)]="Count" and the same name so if you update one, all of them will be updated
You can fix that if you have an array of count instead. So it will be something like
<tr *ngFor="let book of bookService.bookList; let i = index">
...
<input type="text" [name]="'count' + i" [(ngModel)]="count[i]">
People are giving you the HTML way, I'm giving you the Angular way : trackBy functions.
*ngFor="let book of bookService.bookList; trackBy: book.Name"
This one should work but I've never tested it.
The one should work in any case :
*ngFor="let book of bookService.bookList; trackBy: customTB"
customTB(item, index) {
return `${index}-${item.Name}`;
}
You can't use the same name for your inputs.
To fix this, you can add an id populate by the index of the loop *ngFor
Try this :
<tr *ngFor="let book of bookService.bookList; let i = index">
<input type="text" name="Count_${i}" [(ngModel)]="count">
Related
I ran into an almost identical issue before and posted a question about it here: Posting data of a single table row when table is in one form We were able to solve it but it felt pretty hacky. To summarize that post, I was attempting to use AJAX and JS to POST values from a table row to a controller. The problem was that every row would be serialized within the form. To solve this, I serialized only the row I needed.
This time I am running into the same issue, the only difference being that I'm not using AJAX and am submitting the model data from the table row directly to the controller action.
I tried to use both the foreach and for loops to generate table rows.
The foreach loop always POSTs the first row in the table, even if I click submit button on the second row.
The for loop doesn't POST anything, or at least I get all null values in my controller action's "shipment" parameter.
I also tried to encase the row in a form, but HTML does not allow to have a <form> in <tbody>.
What I want is to be be able to POST a single row (generated by looping through a list for models) directly to the "UpdateShipment" controller action. All of this without using AJAX.
HTML code:
#model OrderTrackingContract.SalesOrder
#foreach (var lineItem in Model.LineItems)
{
<table class="lineItemTables">
//line items table
</table>
#if (lineItem.Shipments.Count > 0)
{
<form method="post">
<table class="table shipmentTable">
<thead>
<tr>
<th>
ShipmentID
</th>
<th>
Qty Shipped
</th>
<th>
Actions
</th>
</tr>
</thead>
<tbody>
#for (int i = 0; i < lineItem.Shipments.Count; i++)
{
<tr>
<td>
<input class="shipmentID" asp-for="#lineItem.Shipments[i].ShipmentID" />
</td>
<td>
<input class="qtyShipped" asp-for="#lineItem.Shipments[i].QtyShipped" value="#lineItem.Shipments[i].QtyShipped" min="1" max="999" />
</td>
<td>
<input class="updateButton" type="submit" value="Update" asp-action="UpdateShipment" asp-controller="Shipments" />
<input class="deleteButton" type="submit" value="Delete" asp-route-shipmentID="#lineItem.Shipments[i].ShipmentID" asp-action="DeleteShipment" asp-controller="Shipments" />
</td>
</tr>
}
</tbody>
</table>
</form>
}
}
Controller action:
[HttpPost]
public IActionResult UpdateShipment(Shipment shipment)
{
_orderTrackingService.UpdateShipmentByID(shipment.ShipmentID, shipment.QtyShipped);
return NoContent();
}
EDIT: I used fiddler and I'm starting to understand what's going on. Both for and foreach loop submit the whole form because the whole table is in it.
Using a for loop I am forced to write asp-for="#lineItem.Shipments[i].ShipmentID", which means the controller must accept a parameter of LineItem lineItem.
Using foreach loop I am able to write asp-for="#shipment.ShipmentID, but because the generated attributes of the <input> are identical, the 'Shipment shipment' parameter binds to the first values POSTed.
What I want is to be be able to POST a single row (generated by looping through a list for models) directly to the "UpdateShipment" controller action. All of this without using AJAX.
To achieve above requirement, you can try to generate <form> and <table> for each Shipment item, like below.
#foreach (var lineItem in Model.LineItems)
{
<table class="table shipmentTable">
<thead>
<tr>
<th>
ShipmentID
</th>
<th>
Qty Shipped
</th>
<th>
Actions
</th>
</tr>
</thead>
</table>
#if (lineItem.Shipments.Count > 0)
{
foreach (var Shipment in lineItem.Shipments)
{
<form method="post">
<table class="table shipmentTable">
<tbody>
<tr>
<td>
<input class="shipmentID" asp-for="#Shipment.ShipmentID" />
</td>
<td>
<input class="qtyShipped" asp-for="#Shipment.QtyShipped" value="#Shipment.QtyShipped" min="1" max="999" />
</td>
<td>
<input class="updateButton" type="submit" value="Update" asp-action="UpdateShipment" asp-controller="Shipments" />
<input class="deleteButton" type="submit" value="Delete" asp-route-shipmentID="#Shipment.ShipmentID" asp-action="DeleteShipment" asp-controller="Shipments" />
</td>
</tr>
</tbody>
</table>
</form>
}
}
}
Test Result
What would be the best way to dynamically add an HTML element, such as another column onto a basic HTML table?
I want to have a button below the table, and if the user were to click the button, the event would add the same amount of rows already in the table and add another column. I would want to support about 5 extra columns being added to the table.
Here's my HTML table as of right now:
<table>
<thead>
<tr>
<th id="row-tag"></th>
<th id="option-column">Option 1</th>
</tr>
</thead>
<tbody>
<tr>
<td id="row-tag">P</td>
<td id="option-column">{{ p }}</td>
</tr>
<tr id="row-tag">
<td>
<app-a-p (saveButtonClick)="toggleAP($event)"
[modModalValues]="modModalValues">
</app-a-p>
</td>
<td id="option-column">
<div class="input-group input-group-sm">
$<input
*ngIf="toggleAP==true"
type="text"
name="aP"
size="10"
disabled
value=""
/>
<input
*ngIf="toggleAP==false"
type="text"
name="aP"
size="10"
[ngModel]="con.pA"
(ngModelChange)="con.pA = $event" appFormatP
(blur)="checkAP($event.target.value);
inputTracker.addModifiedValue('Option 1', $event.target.value)"
/>
</div>
</td>
</tr>
<tr>
<td id="row-tag">L</td>
<td id="option-column">${{l}}</td>
</tr>
<tr>
<td id="row-tag">R</td>
<td id="option-column">${{r}}</td>
</tr>
</tbody>
</table>
I think in Angular, you define table based on your data. for example, you have fields array defining columns, and data array defines the what's actually in the table.
<table >
<thead>
<tr>
<th *ngFor='let key of this.fields'>{{key}}</th>
</tr>
</thead>
<tbody>
<tr *ngFor='let row of this.data ' >
<td scope="row" *ngFor='let key of this.fields'> {{row[key]}} </td>
</tr>
</tbody>
</table>
when you need a new column, just push a new field into fields. like
fields.push('newcolumn');
when you need a new row, just do:
data.push({col1: '', col2:'', newcolumn: ''});
Look into the insertRow() and insertCell() functions in JavaScript. This alongside an onClick function that you write will let you edit the table.
A good way of generating a table on UI when using angular would be to use 2D-array (row*column) use can have a button using which you can dynamically add values to this array and have another row/column to the table.
I'm working on a shift arrangement app. In it I'm trying to create two tables that show which possible shifts each user has selected.
Both tables display the same data, but arrange it differently. Each table cell has a number of check-boxes that display the possible shifts for each person (in table 1) or the possible people for a shift (in table 2). A checkbox from table 1 that displays shift A option for person X will have the same data-bind as its equivalent checkbox in table 2, which displays person X option for shift A.
The purpose of this is to update the equivalent data in both tables simultaneously when the user couples a person with a shift. The problem: when a checkbox in table 1 is checked/unchecked, all of the check-boxes in table 2 gets checked/unchecked, as shown below:
Here is my template:
<div class="table-container" dir="ltr">
<h3>People</h3>
<table>
<thead>
<th>Name</th>
<th>Options</th>
</thead>
<tbody>
<tr *ngFor="let user of userPreferences">
<td>{{user.name}}</td>
<td>
<div *ngFor="let selection of userYesses[user.name]">
<mat-checkbox class="option-checkbox" dir="ltr" [(ngModel)]="selection.isSelected" name="usc">{{selection.option}}</mat-checkbox>
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="table-container" dir="ltr">
<h3>Shifts</h3>
<table>
<thead>
<th>Time</th>
<th>Options</th>
</thead>
<tbody>
<tr *ngFor="let shift of totalShifts">
<td dir="ltr">{{shift.time}}</td>
<td>
<div *ngFor="let selection of shiftYesses[shift.time]">
<mat-checkbox class="option-checkbox" [(ngModel)]="selection.isSelected" name="syc">{{selection.name}}</mat-checkbox>
</div>
</td>
</tr>
</tbody>
</table>
</div>
And here is relevant component code:
this.userPreferences.forEach(u => {
this.userYesses[u.name] = [];
u.preferences.shifts.forEach(week => {
week.forEach(day => {
if (!day.shifts) return;
day.shifts.forEach(shift => {
if (!this.shiftYesses[`${day.date} ${shift.time}`]) this.shiftYesses[`${day.date} ${shift.time}`] = [];
if (shift.isSelected) {
let selection = new Selection(`${day.date} ${shift.time}`, u.name);
this.userYesses[u.name].push(selection);
this.shiftYesses[`${day.date} ${shift.time}`].push(selection);
}
});
});
});
});
The code seems alright to me, am I missing anything? Maybe it's a bug in Angular?
Thanks in advance!
In case anyone else experiences this issue -
After a few days of struggling with this, I stumbled upon this issue from Angular's git - https://github.com/angular/angular/issues/9230
I've read the following in kara's answer:
In the case that you don't want to register a form control, you currently have a few options:
1 - Use ngModel outside the context of a form tag. This will never throw.
<input [(ngModel)]="person.food">
After reading this, I switched the <form> tag into a <div> and everything works as expected now.
I am using an ng-repeat to generate some data (username, login, role and actions). Each dropdown (role) has the same list of options.
Expected output :
Each row of data can have a different selected option (like the image) and a service call is made whenever a change is made to save the change.
My problem is that I can't choose different options for each row. Because they are all bound to the same ng-model, they all detect the change and all change to the new option.
<table>
<thead>
<tr>
<th>Name</th>
<th>Username</th>
<th>Role</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr data-ng-repeat="participant in model.participantData">
<td>{{participant.DisplayName}}</td>
<td>{{participant.UserLogin}}</td>
<td>
<label for="{{participant.ParticipantID}}" class="sr-only">
Choose a role
</label>
<select id="{{participant.ParticipantID}}" data-ng-options="role.RoleID as role.RoleName for role in model.rolesData" data-ng-change="roleChanged(participant)" data-ng-model="model.chosenRole">
</select>
</td>
<td>
<span aria-hidden="true" class="fa fa-minus-circle clickable"</span>
<span class="sr-only">Delete group member</span>
</td>
</tr>
</tbody>
</table>
Instead of giving same model , Give them model by ng-repeat instance
Like this
data-ng-model="participant.chosenRole"
Select will be
<select
id="{{participant.ParticipantID}}"
data-ng-options="role.RoleID as role.RoleName for role in model.rolesData"
data-ng-change="roleChanged(participant)"
data-ng-model="participant.chosenRole">
</select>
You can get selected value of each row from model.participantData
Is it possible to apply ng-click in AngularJS to a table column? I tried the following, which doesn't seem to do the trick.
<table>
<colgroup>
<col ng-repeat="item in items" ng-click="myFunction(item)"> </col>
</colgroup>
<thead>
<tr>
<th ng-repeat="item in items"> {{item.title}} </th>
</tr>
</thead>
<tbody>
<tr>
<td ng-repeat="item in items"> {{item.info}} </td>
</tr>
</tbody>
</table>
It is not possible do it in the way you have tried. You may register the ng-click on every td element. Another possibility may be registering the ng-click at the table element and use the original dom event to determin wich column was clicked. You can access the event in this way: ng-click="myFunction($event)" then in the controler:
$scope.myFunction = function(e){
console.log(e);
}
see this post How to find row and col number of a cell in table if you would like to go this way...