Blazor button click handler not retaining parameter value - onclick

I am creating a table in a for-loop and adding a button in each cell with a click handler method. I am passing the cell value as a parameter to the click handler method. The cell value is also generated using the same for loop. When the button is clicked, it updates the contents of a div using the passed down parameter value. I was expecting it to display the value for which the button was clicked. But it always shows the value on which the for-loop ended. It seems, the click handler method is not holding on to the value that was passed to it during the button creation. Following is the code from the razor page and the code behind: The project can be downloaded from this location
#page "/"
<PageTitle>Index</PageTitle>
<div>Number Clicked is #NumberClicked</div>
<table>
#for(int i=0; i< 6; i++){
<tr><td><button type="button" class="btn btn-link" #onclick="()=> {ButtonClicked(i);}">#i</button></td></tr>
}
</table>
using Microsoft.AspNetCore.Components;
namespace GridClick.Pages
{
public partial class Index : ComponentBase
{
public string NumberClicked { get; set; }
public void ButtonClicked(int i)
{
NumberClicked = i.ToString();
}
}
}

You are incrementing the i after every iteration. This means that the i you are using is shared between all iterations and will have the latest updated value when you click on the button.
What you can do is create a temporary variable and copy the value to this variable and use it in your for loop.
#for(int i=0; i< 6; i++)
{
int index = i;
<tr>
<td><button type="button" class="btn btn-link"
#onclick="()=> {ButtonClicked(index);}">#index</button></td>
</tr>
}

Another way to solve your issue is to use foreach with Enumerable.Range because using for loop retains the last iteration value when rendered.
<div>Number Clicked is #NumberClicked</div>
<table>
#foreach (var index in Enumerable.Range(1, 6))
{
<tr>
<td>
<button type="button" class="btn btn-link"
#onclick="() => { ButtonClicked(index); }">
#index
</button>
</td>
</tr>
}
</table>

Related

How to get Iteration Number inside #foreach loop by using ASP.NET Core inside the view

I am new to ASP.NET Core development. I am looking for something like a built-in way to use loop iteration numbers inside the view of ASP.NET Core.
I did some research and found solutions like creating int variable outside the loop and then increment inside the loop.
I want to index each user.
Here is my code:
#foreach (var item in l_IEnumerableModUserQuery)
{
<tr>
<td>
<!-- Here I want to add Iteration No. here-->
</td>
<td>
<a href="#">
#item.Pr_FullName
</a>
</td>
<td>#item.Pr_Email</td>
<td>#item.Pr_ContactNo</td>
</tr>
}
You could use a simple for loop to get the index:
//use .Count if it is a List or .Count() with Linq to get the boundary.
#for(var i = 0; i < l_IEnumerableModUserQuery.Count; i++)
{
<tr>
<td>
#i.ToString();
</td>
<td>
<a href="#">
#l_IEnumerableModUserQuery[i].Pr_FullName
</a>
</td>
<td>#l_IEnumerableModUserQuery[i].Pr_Email</td>
<td>#l_IEnumerableModUserQuery[i].Pr_ContactNo</td>
</tr>
}
Thomas Levesque has a neat approach on his blog, using an extension method:
public static IEnumerable<(T item, int index)> WithIndex<T>(this IEnumerable<T> source)
{
return source.Select((item, index) => (item, index));
}
Which would result in:
#foreach (var (item, idx) in l_IEnumerableModUserQuery.WithIndex())
{
<tr>
<td>
#idx
</td>
<td>
<a href="#">
#item.Pr_FullName
</a>
</td>
<td>#item.Pr_Email</td>
<td>#item.Pr_ContactNo</td>
</tr>
}
With an eye on the extension methods approach, you could as well amend your views model and include the index as a property in your model inside your controller / handler or whereever your model is created:
var l_IEnumerableModUserQuery =
someSource.Where(x => ...)
.Select((x, index) => new MyModel {
Index = index,
Pr_Email = xxx,
Pr_Contact = xxy,
/* ... rest of model */
});
return l_IEnumerableModUserQuery;
After this you could access the index like any other property in your view:
<a href="#">
#item.Index
</a>
you can findout the index of the item
#{
int indx=0;}
#foreach (var item in l_IEnumerableModUserQuery)
{
<tr>
<td>
#l_IEnumerableModUserQuery.IndexOf(item)
</td>
<td>
<a href="#">
#item.Pr_FullName
</a>
</td>
<td>#item.Pr_Email</td>
<td>#item.Pr_ContactNo</td>
</tr>
}

ASP.NET Editable Grid: Update register

I've been struggling with this for a while now. I'm constructing this view:
But when I hit the 'Update' button after do some changes the web refreshes to show the original values.
About the view: I get this view using an IEnumerable and looping thru each item in the model inside a form. Then, inside the form, there is a table that contains only 1 row. I do this in order to wrap all the items of the record in one form. This is part of code:
#foreach (var item in Model)
{
<form asp-action="Test" asp-route-id="#item.Id">
<table class="table">
<tbody>
<tr>
<td>
<input type="hidden" asp-for="#item.Id" />
<div class="form-group">
<div class="col-md-10">
<input asp-for="#item.MchName" readonly class="form-control" />
<span asp-validation-for="#item.MchName" class="text-danger"></span>
</div>
</div>
</td>
//more fields
<td>
<input type="submit" value="Update" class="btn btn-default" />
</td>
</tr>
</tbody>
</table>
</form>}
I declare an asp-action and a asp-route-id:
<form asp-action="Test" asp-route-id="#item.Id">
Question: Is this good enough? Is there something missing?
This is the Get Method:
public async Task<IActionResult> Test()
{
PopulateMachineTypeDropDownListStore();
return View(await _context.Machines.AsNoTracking().ToListAsync());
}
Question: I'm not passing any argument to the controller, yet the view will list the items following the given structure using an IEnumerable. Should I pass anything to the Get Method or is it fine as it is?
This is the Post Method:
#model IEnumerable<Application.Models.Machine>
[HttpPost, ActionName("Test")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> TestPost(int? id)
{
if (id == null)
{
return NotFound();
}
var machinetoUpdate = await _context.Machines
.SingleOrDefaultAsync(s => s.Id == id);
if (await TryUpdateModelAsync(
machinetoUpdate,
"",
s => s.MchName, s => s.StoreID, s => s.PUnit, s => s.Status))
{
try
{
await _context.SaveChangesAsync();
}
catch (DbUpdateException)
{
ModelState.AddModelError("", "Unable to save changes. " +
"Try again, and if the problem persists, " +
"see your system administrator.");
}
return RedirectToAction("Test");
}
PopulateMachineTypeDropDownListStore();
return View(await _context.Machines.AsNoTracking().ToListAsync());
}
Question: I don't know if because the entity I retrieve the id from (and that I use to update the model thru TryUpdateModelAsync()) is also being used to compare to the model that thru the view this might not been working properly.
Thanks in advance for any help.

ASP.NET Razor: Send variable as parameter

My view contains the following table definition:
<table>
<thead>
<th>Title</th>
<th>Artist</th>
<th>Genre</th>
<th>Price</th>
</thead>
#foreach (var album in Model)
{
<tr>
<td>#album.Title</td>
<td>
#{
foreach (var artist in ViewBag.Artists)
{
if (artist.ArtistID == album.ArtistID)
{ #artist.Name }
}
}
</td>
<td>
#{
foreach (var genre in ViewBag.Genres)
{
if (genre.GenreID == album.GenreID)
{ #genre.Name }
}
}
</td>
<td>#album.Price</td>
<td>
<a asp-action="Details" asp-controller="Albums">Details </a>
<a asp-action="Edit" asp-controller="Albums">Edit </a>
<a asp-action="Delete" asp-controller="Albums">Delete</a>
</td>
</tr>
}
</table>
The Details, Edit, and Delete options in the final column need to be keyed to the AlbumID property of the album variable, in order to ensure that the redirect operates on the proper album. I had thought the parameter for this might be asp-parameter, much like the action is set by asp-action, but this does not appear to exist.
What is the proper method to pass a variable as a URL parameter (for instance, /Albums/Details/var), and what name is used to retrieve this on the other side?
Shyju's answer deals with passing the parameter; my attempts at retrieving the parameter thus far have been as follows:
<%=Url.RequestContext.RouteData.Values["id"]%> - Fails to load, invalid character <.
ViewContext.RouteData.Values["id"] - Fails to load, cannot compare int and object using ==.
RouteData.Values["id"] - Compilation error, name does not exist in current context.
When using link tag helper, You can pass additional information to the action method using the asp-route-[paramName] attribute. For example, if you want to pass the album id to the id parameter of your details action method, you can do this
<a asp-action="Details" asp-controller="Albums" asp-route-id="#album.ID">Details </a>
This will generate the markup like below
<a href='/Albums/Details/101'>Details</a>
Where 101 will be replaced by an actual Album Id value.
Assuming your Details action method has a parameter named id
public ActionResult Details(int id)
{
// to do : Get the album using the id and send something to the view
}

Spring 3 MVC Validation: Specifying path for validation error in a list

I am attempting to validate my object that encapsulates a list of other objects, as follows (shortened for brevity):
public class FormDTO {
private List<AttributeDTO> ruleAttributes;
// More attributes here
}
public class AttributeDTO {
private String value;
// More attributes here
}
A snippet of my validator is as follows:
for(AttributeDTO attributeDTO : attributes)
{
if(attributeDTO.getValue() == null || attributeDTO.getValue().length() == 0)
{
errors.reject("value", "value.isEmpty");
}
}
My jsp contains the following:
<c:forEach items="${form.ruleAttributes}" var="ruleAttribute" varStatus="counter">
<tr>
<td>
<c:choose>
<c:when test="${ruleAttribute.isEditable}">
<form:input path="ruleAttributes[${counter.index}].value" value="${ruleAttribute.value}"/>
</c:when>
<c:otherwise>
<span class="derived">NotEditable</span>
</c:otherwise>
</c:choose>
</td>
<td>
<form:errors path="ruleAttributes[${counter.index}].value"/>
</td>
</tr>
</c:forEach>
How do I get the corresponding error message to appear for the relevant list item? In summary, I want the "value.isEmpty" message to appear in the table cell for the relevant row that has an empty value.
Thanks
After reading the Spring reference guide again, I can answer this question myself.
To get the appropriate error to appear for this snippet...
<form:errors path="ruleAttributes[${counter.index}].value"/>
...I need to modify my validation code as follows:
for(int i = 0; i < ruleAttributes.size(); i++)
{
AttributeDTO attributeDTO = ruleAttributes.get(i);
if(attributeDTO.getValue() == null || attributeDTO.getValue().length() == 0)
{
errors.rejectValue("ruleAttributes[" + i + "].value", "value.isEmpty", "Value should not be empty");
}
}

Spring mvc, how to bind a domain object that has a collection as its property

I have a domain object called Order, and it has a collection attribute called serviceOrders where a collection of service --- order m:m association relationships are hold.
public class Order implements Serializable {
private Long id = null;
private BigDecimal amountPaid;
private BigDecimal accountReceivable;
private User user;
private Set serviceOrders = new HashSet();
private Date closed;
private Date created = new Date();
private String status;
also there is a method for adding the association called addServiceOrder
public void addServiceOrder(ServiceOrder serviceOrder) {
if (serviceOrder == null)
throw new IllegalArgumentException("Can't add a null serviceOrder.");
this.getServiceOrders().add(serviceOrder);
}
how should I use commandName to set this collection with "path", I think it would only call its get set method of the Command Object. how should I add serviceOrder to this command Object. I have no idea about this problem. any help would be highly appreciated
Assuming your ServiceOrder instances have unique ids your service method should be #add(Long id).
Ok bear with me on this one but the solution is simple an annoying at the same time. I ran into this a couple of months ago. I am going to show you my solution using the jstl libraries in my view for handling the collections.
<c:forEach items="${Questions}" var="quest" varStatus="itemsIndex">
<fieldset>
<legend>${quest.section}</legend>
<form:form id="group${itemsIndex.index}" modelAttribute="ChoiceList" action="" method="POST" onsubmit="javascript:ajaxSave($(this).serialize()); return false;">
<a id="Group${quest.id}"></a>
<c:forEach items="${quest.qisQuestionsCollection}" var="quest2" varStatus="itemsRow">
<div style="font-weight: bold; margin: 10px 0px">${quest2.shortText}</div>
( ${quest2.qisQuestionTypes.description} )<br/>
( ${quest2.helpText} )<br/>
<a id="Question${quest2.id}"></a>
<c:choose>
<c:when test="${quest2.qisQuestionTypes.questionType == 'CHOOSEANY'}">
<c:forEach items="${quest2.qisChoicesCollection}" var="quest3" varStatus="indexStatus">
<c:forEach items="${ChoiceFields}" var="CField">
<c:set scope="request" value="${quest3}" var="ChoiceData"/>
<c:set scope="request" value="${CField}" var="ChoiceProperty"/>
<%
answerMap = (HashMap<QisChoice, Answer>) request.getAttribute("AnswerList");
choice = (QisChoice) request.getAttribute("ChoiceData");
if (answerMap.containsKey(choice.getChoiceID())) {
Answer theAnswer = (Answer) answerMap.get(choice.getChoiceID());
if (theAnswer != null) {
if (theAnswer.getChoiceValue() != null) {
request.setAttribute("itemValue", theAnswer.getChoiceValue());
request.setAttribute("itemSelected", true);
} else {
request.setAttribute("itemSelected", false);
request.setAttribute("itemValue", getReflectedValue(
(QisChoice) request.getAttribute("ChoiceData"),
(AccessorStruct) request.getAttribute("ChoiceProperty")));
}
}
} else {
request.setAttribute("itemSelected", false);
request.setAttribute("itemValue", getReflectedValue(
(QisChoice) request.getAttribute("ChoiceData"),
(AccessorStruct) request.getAttribute("ChoiceProperty")));
}
request.setAttribute("itemValue2", getReflectedValue(
(QisChoice) request.getAttribute("ChoiceData"),
(AccessorStruct) request.getAttribute("ChoiceProperty")));
%>
<c:choose>
<c:when test="${CField.visible == 'HIDDEN'}">
<form:hidden value="${itemValue2}" path="question[${itemsRow.index}].choice[${indexStatus.index}].${CField.beanName}" />
</c:when>
<c:otherwise>
<c:choose>
<c:when test="${itemSelected}">
<form:checkbox value="${itemValue}" label="${quest3.description}" path="question[${itemsRow.index}].choice[${indexStatus.index}].${CField.beanName}" checked="true" /><br/>
</c:when>
<c:otherwise>
<form:checkbox value="${itemValue}" label="${quest3.description}" path="question[${itemsRow.index}].choice[${indexStatus.index}].${CField.beanName}" /><br/>
</c:otherwise>
</c:choose>
</c:otherwise>
</c:choose>
</c:forEach>
</c:forEach>
</c:when>
<input type="submit" value="Save Section"
class="button-main" />
</fieldset>
</form:form>
</c:forEach>`
The Key bit is in this line
<form:checkbox value="${itemValue}" label="${quest3.description}" path="question[${itemsRow.index}].choice[${indexStatus.index}].${CField.beanName}" checked="true" /><br/>
To link up the command object with its collection for the postback you have to show the indice of the element as part of the spring path. In my case I have two levels of collections to track
<c:forEach items="${quest.qisQuestionsCollection}" var="quest2" varStatus="itemsRow">
varStatus gives you access to a bean object with the index property you can use to your advantage.
In your case you can do just use the index property of the foreach jstl function in the jsp to generate the indice like I did and append it to the array index notation of your command object. The command object must of course follow the same flow as the path collection names. This works for an infinite number of levels but gets more annoying as we go.
This is a large live example so if you need something smaller show me your markup and I will walk you throgh it.

Resources