I am seeing a confusing issue with my viewmodel posting back to my controller and I am confused as of why it is not working. Though I have an idea as of why it may not be working which I explained near the bottom.
Basically I use a for loop to bind my model to HTML in the razor view
#for (int i = 0; i < Model.CheckBoxTag.Count; i++)
{
#if (Model.CheckBoxTag[i].TagTypeName == "test")
{
....
}
}
When I submit the form, the test CheckBoxTag objects are sent to my controller as expected.
However, when I do the same further down the html page only using the Escalation tags:-
#for (int i = 0; i < Model.CheckBoxTag.Count; i++)
{
#if (Model.CheckBoxTag[i].TagTypeName == "test1")
{
...
}
}
The test1 CheckBoxTag objects are not sent back to the controller. (The count is still 3, whereas it should be 6)
The fact it's the same code I am unsure how to tackle it.
My theory: I believe it is not posting back to my controller because the test for loop are the 1st elements in the collection, therefore it always goes into the IF. Whereas the test1 objects are near the bottom of the collection so therefore the IF is skipped quite a few times in the loop.
Is that correct? If not, what could be the issue?
Thanks
As stated in the comments section, the indexers must be consecutive.
Therefore in the for loops I put
#for (int i = 0; i < Model.CheckBoxTag.Count; i++)
{
#if (Model.CheckBoxTag[i].TagTypeName == "test1")
{
// New!
<input type="hidden" name="CheckBoxTag.Index" value="#i" />
...
}
}
So now the indexers "[i]" are now being incremented on every loop
Related
I have the below code but the index parameter that is passed when I click the <tr> element is always 9.
That is becuase I have 9 rows in the table that is passed to the component as data.
So looks like the index is always the value of variable 'i' which was last set... in this case the value of i after the last row in foreach loop is 9 so i am getting the index parameter as 9 on clicking all the rows in the table...
What is the issue in my code which is not setting the i value for each row onclick.
<table border="1">
#for(int i=0;i< ListData.DataView.Table.Rows.Count; i++)
{
<tr #onclick="(() => RowSelect(i))">
#foreach (ModelColumn col in ListData.ListColumns)
{
<td>#ListData.DataView.Table.Rows[i][col.Name]</td>
}
</tr>
}
</table>
#code {
private async Task RowSelect(int rowIndex)
{
await ListRowSelected.InvokeAsync(rowIndex);
}
}
General
Actually your problem is about lambda that captures local variable. See the following simulation with a console application for the sake of simplicity.
class Program
{
static void Main(string[] args)
{
Action[] acts = new Action[3];
for (int i = 0; i < 3; i++)
acts[i] = (() => Job(i));
foreach (var act in acts) act?.Invoke();
}
static void Job(int i) => Console.WriteLine(i);
}
It will output 3, 3, 3 thrice rather than 0, 1, 2.
Blazor
Quoted from the official documentation about EventCallback:
<h2>#message</h2>
#for (var i = 1; i < 4; i++)
{
var buttonNumber = i;
<button class="btn btn-primary"
#onclick="#(e => UpdateHeading(e, buttonNumber))">
Button ##i
</button>
}
#code {
private string message = "Select a button to learn its position.";
private void UpdateHeading(MouseEventArgs e, int buttonNumber)
{
message = $"You selected Button #{buttonNumber} at " +
$"mouse position: {e.ClientX} X {e.ClientY}.";
}
}
Do not use a loop variable directly in a lambda expression, such as i in the preceding for loop example. Otherwise, the same variable is used by all lambda expressions, which results in use of the same value in all lambdas. Always capture the variable's value in a local variable and then use it. In the preceding example, the loop variable i is assigned to buttonNumber.
So you need to make a local copy for i as follows.
#for(int i=0;i< ListData.DataView.Table.Rows.Count; i++)
{
int buffer=i;
<tr #onclick="(() => RowSelect(buffer))">
#foreach (ModelColumn col in ListData.ListColumns)
{
<td>#ListData.DataView.Table.Rows[i][col.Name]</td>
}
</tr>
}
This happens because the value of i isn't rendered to the page (as it would have been in MVC/Razor Pages), it's just evaluated when you trigger the event. You won't trigger the event until the page has rendered, and so by that point the loop will have completed, so the value for i will always be the value at the end of the loop.
There are a couple of ways to deal with this, either use a foreach loop instead if that's suitable (which is what most of the Blazor documentation examples do), or declare a local variable inside the loop:
#for(int i=0;i< ListData.DataView.Table.Rows.Count; i++)
{
int local_i=i;
// Now use local_i instead of i in the code inside the for loop
}
There's a good discussion of this in the Blazor Docs repo here, which explains the problem as follows:
Problem is typically seen in event handlers and binding expressions.
We should explain that in for loop we have only one iteration variable
and in foreach we have a new variable for every iteration. We should
explain that HTML content is rendered when for / foreach loop is
executed, but event handlers are called later. Here is an example code
to demonstrate one wrong and two good solutions.
This is all particularly confusing if you're coming to Blazor from an MVC/Razor Page background, where using for is the normal behaviour. The difference is that in MVC the value of i is actually written to the html on the page, and so would be different for each row of the table in your example.
As per the issue linked above and #Fat Man No Neck's answer, the root cause of this is down to differences in the behaviour of for and foreach loops with lambda expressions. It's not a Blazor bug, it's just how C# works.
I am using a while loop which gives me an indexoutofrange error. I do not understand why this code would give me this.
This is what I have in my view:
#{
int i = 1;
while(i < 6)
{
<li class="item" id="ti+#i"><img src="Content/images/items/#Model[i].image_name" /></li>
i++;
}
}
Also, when I start the website I get a The resource cannot be found error. The website is pointed to start at Home/Index which is this view page.
Controller for that page:
DBController controller = new DBController();
public ActionResult Index()
{
List<items> items= controller.getItems();
return View(items);
}
I have no clue why this all doesnt work.
Loops in C# usually range from 0 to n-1, so be double-sure that starting at 1 is what you want.
Other than that, the error results probably from the fact that Model only contains 5 or less elements, so accessing Model[5] results in an error, as the elements in Model are indexed from 0 to (at most) 4.
I am trying to read the following list:
<ol class="sublist">
<li>
Sort Out Your Values
</li>
<li>
Establish Realistic Goals
</li>
<li>
Determine Your Monthly Net Income
This is the code i wrote for it; but currently, everytime it runs; my string is coming up empty. I want to get the inner text so that in my loop i grab it and click it and return back to previous screen.
IWebElement container = driver.FindElement(By.ClassName("sublist"));
IList<IWebElement> elements = driver.FindElements(By.TagName("a"));
string [] newlink = new string[elements.Count()];
for (int i = 0; i < newlink.Count(); i++)
{
if (newlink[i] != null)
{
driver.FindElement(By.LinkText(newlink[i])).Click();
driver.WaitForElement(By.CssSelector("[id$='hlnkPrint']"));
driver.Navigate().Back();
}
}
The script is able to run but was getting that the links were null, so i added a check to see if any of them were null and it turns out all of them are.
Im sure it has something to do with with the '.text' or 'ToString', but Im not sure where to implement that.
Thanks
There's a few issues with your code.
- You haven't set the value of newlink, just created it.
- Count is a property, but you're using it as a method.
- Link text is the .Text property of an IWebElement, and you would need to access that.
- Your current code will likely click one link, and after going back will throw a StaleElementException.
In the following
- I set newlink to the Text values of the links found for elements
- I then iterate through the array of link text
IWebElement container = driver.FindElement(By.ClassName("sublist"));
IList<IWebElement> elements = container.FindElements(By.TagName("a"));
string[] newlink = new string[elements.Count];
for (int i = 0; i < newlink.Count; i++)
{
newlink[i] = elements[i].Text;
}
for (int i = 0; i < newlink.Count; i++)
{
if (newlink[i] != null)
{
driver.FindElement(By.LinkText(newlink[i])).Click();
driver.WaitForElement(By.CssSelector("[id$='hlnkPrint']"));
driver.Navigate().Back();
}
}
You can use FindElement() on an IWebElement. so in this case, if you want to find elements that are children of container, you would use container.FindElements().
In my view I need to create a drop down list for a property that is not in the immediate view model, rather nested within two more levels of view models. It's arranged as so:
patient -> (list)referrals -> (list)bookings.attendanceId
How would I use the DropDownListFor helper in this case? The problem is not finding the List<SelectListItem> but pointing the helper to the scalar value the drop down selection should fill.
First just an opinion, one of the main reason we use view models if to simplify the domain models to only what you explicitly need in the view. So my question to you is why is your view model so complex?
That being said the only way to accomplish what you want to do is to have your drop down list nested inside 2 for loops like this:
#for (var i = 0; i < Model.Referrals.Count; i++)
{
#for (var j = 0; j < Model.Referrals[i].Bookings.Count; j++)
{
#Html.DropDownListFor(m => m.Referrals[i].Bookings[j].AttendanceId, Model.SomeSelectList)
}
}
Try
#for(int i = 0; i < Model.Referrals .Count; i++)
{
for (int j = 0; j < Model.Referrals[i].Bookings.Count; j++)
{
#Html.DropDownListFor(m => m.Referrals[i].Bookings[j].AttendanceId, Model.YourSelectList)
In my last question I was having problems looping through a list with jQuery. Then we figured this out and it worked perfectly:
public List<Sale> AllSales { get; set; }
for (var i = 0; i < <%= AllSales.Count %>; i++) {
}
I now need to use the values inside the loop so I thought it would be as simple as this :
for (var i = 0; i < <%= AllSales.Count %>; i++) {
var date = <%= AllSales[i].Date %>;
alert(date);
}
When I first tried this, it said "The name 'i' does not exist in the current context
", so I just put 0 instead of i instead of AllSales[0]. Then nothing happens.
What am I missing?
You have javascript loop which you want to iterate on server side list this is not possible. You can use ajax to send data to client side. This is a nice article for using jQuery ajax with csharp.
Assigning the values of your list separated with comma to some hidden field and accessing that hidden field in javascript could be a possible solution. But if you want to use more attributes of your list object then it would be very messy solution. Using ajax is best option.