I have a details-view where a varying number of sites are displayed in textboxes. Next to each site the country should be shown in a disabled dropdown. My problem is that the dropdown does not select the right value. Instead it always selects the first item. Some debugging showed that "SelectedCountries[i]" is holding the correct value for each dropdown but somehow this value does not affect the selection at all.
The dropdown's names are "SelectedCountries[i]" and I've read it is bad to use the same name for the dropdown and for the field in the model. However, changing it didn't solve the problem.
<div id="allSitesAndCountries">
#for (int i = 0; i < Model.SelectedSites.Count; i++)
{
#Html.TextBoxFor(m => m.SelectedSites[i], new { disabled = "disabled", })
#Html.DropDownListFor(m => m.SelectedCountries[i], Model.AllCountriesSelectListItems, new{disabled = "disabled"})
}
</div>
What am I doing wrong here?
Thanks in advance.
EDIT: I got it working by creating the SelectList directly in the view and adding the 4th parameter:
<div id="allSitesAndCountries">
#for (int i = 0; i < Model.SelectedSites.Count; i++)
{
<div class="editor-label">
#Html.TextBoxFor(m => m.SelectedSites[i], new { disabled = "disabled"})
#Html.DropDownListFor(m => m.SelectedCountries[i], new SelectList(Model.AllCountries, "Id", "Name", Model.SelectedCountries[i]), new{disabled = "disabled"})
</div>
}
</div>
Related
this is a tricky one to explain, so I'll try bullet pointing.
Issue:
Dynamic rows (collection) available to user on View (add/delete)
User deletes row and saves (POST)
Collection passed back to controller with non-sequential indices
Stepping through code, everything looks fine, collection items, indices etc.
Once the page is rendered, items are not displaying correctly - They are all out by 1 and therefore duplicating the top item at the new 0 location.
What I've found:
This happens ONLY when using the HTML Helpers in Razor code.
If I use the traditional <input> elements (not ideal), it works fine.
Question:
Has anyone ever run into this issue before? Or does anyone know why this is happening, or what I'm doing wrong?
Please check out my code below and thanks for checking my question!
Controller:
[HttpGet]
public ActionResult Index()
{
List<Car> cars = new List<Car>
{
new Car { ID = 1, Make = "BMW 1", Model = "325" },
new Car { ID = 2, Make = "Land Rover 2", Model = "Range Rover" },
new Car { ID = 3, Make = "Audi 3", Model = "A3" },
new Car { ID = 4, Make = "Honda 4", Model = "Civic" }
};
CarModel model = new CarModel();
model.Cars = cars;
return View(model);
}
[HttpPost]
public ActionResult Index(CarModel model)
{
// This is for debugging purposes only
List<Car> savedCars = model.Cars;
return View(model);
}
Index.cshtml:
As you can see, I have "Make" and "Actual Make" inputs. One being a HTML Helper and the other a traditional HTML Input, respectively.
#using (Html.BeginForm())
{
<div class="col-md-4">
#for (int i = 0; i < Model.Cars.Count; i++)
{
<div id="car-row-#i" class="form-group row">
<br />
<hr />
<label class="control-label">Make (#i)</label>
#Html.TextBoxFor(m => m.Cars[i].Make, new { #id = "car-make-" + i, #class = "form-control" })
<label class="control-label">Actual Make</label>
<input class="form-control" id="car-make-#i" name="Cars[#i].Make" type="text" value="#Model.Cars[i].Make" />
<div>
<input type="hidden" name="Cars.Index" value="#i" />
</div>
<br />
<button id="delete-btn-#i" type="button" class="btn btn-sm btn-danger" onclick="DeleteCarRow(#i)">Delete Entry</button>
</div>
}
<div class="form-group">
<input type="submit" class="btn btn-sm btn-success" value="Submit" />
</div>
</div>
}
Javascript Delete Function
function DeleteCarRow(id) {
$("#car-row-" + id).remove();
}
What's happening in the UI:
Step 1 (delete row)
Step 2 (Submit form)
Step 3 (results)
The reason for this behavior is that the HtmlHelper methods use the value from ModelState (if one exists) to set the value attribute rather that the actual model value. The reason for this behavior is explained in the answer to TextBoxFor displaying initial value, not the value updated from code.
In your case, when you submit, the following values are added to ModelState
Cars[1].Make: Land Rover 2
Cars[2].Make: Audi 3
Cars[3].Make: Honda 4
Note that there is no value for Cars[0].Make because you deleted the first item in the view.
When you return the view, the collection now contains
Cars[0].Make: Land Rover 2
Cars[1].Make: Audi 3
Cars[2].Make: Honda 4
So in the first iteration of the loop, the TextBoxFor() method checks ModelState for a match, does not find one, and generates value="Land Rover 2" (i.e. the model value) and your manual input also reads the model value and sets value="Land Rover 2"
In the second iteration, the TextBoxFor() does find a match for Cars[1]Make in ModelState so it sets value="Land Rover 2" and manual inputs reads the model value and sets value="Audi 3".
I'm assuming this question is just to explain the behavior (in reality, you would save the data and then redirect to the GET method to display the new list), but you can generate the correct output when you return the view by calling ModelState.Clear() which will clear all ModelState values so that the TextBoxFor() generates the value attribute based on the model value.
Side note:You view contains a lot of bad practice, including polluting your markup with behavior (use Unobtrusive JavaScript), creating label element that do not behave as labels (clicking on them will not set focus to the associated control), unnecessary use of <br/> elements (use css to style your elements with margins etc) and unnecessary use of new { #id = "car-make-" + i }. The code in your loop can be
#for (int i = 0; i < Model.Cars.Count; i++)
{
<div class="form-group row">
<hr />
#Html.LabelFor(m => m.Cars[i].Make, "Make (#i)")
#Html.TextBoxFor(m => m.Cars[i].Make, new { #class = "form-control" })
....
<input type="hidden" name="Cars.Index" value="#i" />
<button type="button" class="btn btn-sm btn-danger delete">Delete Entry</button>
</div>
}
$('.delete').click(function() {
$(this).closest('.form-group').remove();
}
this is a tricky one to explain, so I'll try bullet pointing.
Issue:
Dynamic rows (collection) available to user on View (add/delete)
User deletes row and saves (POST)
Collection passed back to controller with non-sequential indices
Stepping through code, everything looks fine, collection items, indices etc.
Once the page is rendered, items are not displaying correctly - They are all out by 1 and therefore duplicating the top item at the new 0 location.
What I've found:
This happens ONLY when using the HTML Helpers in Razor code.
If I use the traditional <input> elements (not ideal), it works fine.
Question:
Has anyone ever run into this issue before? Or does anyone know why this is happening, or what I'm doing wrong?
Please check out my code below and thanks for checking my question!
Controller:
[HttpGet]
public ActionResult Index()
{
List<Car> cars = new List<Car>
{
new Car { ID = 1, Make = "BMW 1", Model = "325" },
new Car { ID = 2, Make = "Land Rover 2", Model = "Range Rover" },
new Car { ID = 3, Make = "Audi 3", Model = "A3" },
new Car { ID = 4, Make = "Honda 4", Model = "Civic" }
};
CarModel model = new CarModel();
model.Cars = cars;
return View(model);
}
[HttpPost]
public ActionResult Index(CarModel model)
{
// This is for debugging purposes only
List<Car> savedCars = model.Cars;
return View(model);
}
Index.cshtml:
As you can see, I have "Make" and "Actual Make" inputs. One being a HTML Helper and the other a traditional HTML Input, respectively.
#using (Html.BeginForm())
{
<div class="col-md-4">
#for (int i = 0; i < Model.Cars.Count; i++)
{
<div id="car-row-#i" class="form-group row">
<br />
<hr />
<label class="control-label">Make (#i)</label>
#Html.TextBoxFor(m => m.Cars[i].Make, new { #id = "car-make-" + i, #class = "form-control" })
<label class="control-label">Actual Make</label>
<input class="form-control" id="car-make-#i" name="Cars[#i].Make" type="text" value="#Model.Cars[i].Make" />
<div>
<input type="hidden" name="Cars.Index" value="#i" />
</div>
<br />
<button id="delete-btn-#i" type="button" class="btn btn-sm btn-danger" onclick="DeleteCarRow(#i)">Delete Entry</button>
</div>
}
<div class="form-group">
<input type="submit" class="btn btn-sm btn-success" value="Submit" />
</div>
</div>
}
Javascript Delete Function
function DeleteCarRow(id) {
$("#car-row-" + id).remove();
}
What's happening in the UI:
Step 1 (delete row)
Step 2 (Submit form)
Step 3 (results)
The reason for this behavior is that the HtmlHelper methods use the value from ModelState (if one exists) to set the value attribute rather that the actual model value. The reason for this behavior is explained in the answer to TextBoxFor displaying initial value, not the value updated from code.
In your case, when you submit, the following values are added to ModelState
Cars[1].Make: Land Rover 2
Cars[2].Make: Audi 3
Cars[3].Make: Honda 4
Note that there is no value for Cars[0].Make because you deleted the first item in the view.
When you return the view, the collection now contains
Cars[0].Make: Land Rover 2
Cars[1].Make: Audi 3
Cars[2].Make: Honda 4
So in the first iteration of the loop, the TextBoxFor() method checks ModelState for a match, does not find one, and generates value="Land Rover 2" (i.e. the model value) and your manual input also reads the model value and sets value="Land Rover 2"
In the second iteration, the TextBoxFor() does find a match for Cars[1]Make in ModelState so it sets value="Land Rover 2" and manual inputs reads the model value and sets value="Audi 3".
I'm assuming this question is just to explain the behavior (in reality, you would save the data and then redirect to the GET method to display the new list), but you can generate the correct output when you return the view by calling ModelState.Clear() which will clear all ModelState values so that the TextBoxFor() generates the value attribute based on the model value.
Side note:You view contains a lot of bad practice, including polluting your markup with behavior (use Unobtrusive JavaScript), creating label element that do not behave as labels (clicking on them will not set focus to the associated control), unnecessary use of <br/> elements (use css to style your elements with margins etc) and unnecessary use of new { #id = "car-make-" + i }. The code in your loop can be
#for (int i = 0; i < Model.Cars.Count; i++)
{
<div class="form-group row">
<hr />
#Html.LabelFor(m => m.Cars[i].Make, "Make (#i)")
#Html.TextBoxFor(m => m.Cars[i].Make, new { #class = "form-control" })
....
<input type="hidden" name="Cars.Index" value="#i" />
<button type="button" class="btn btn-sm btn-danger delete">Delete Entry</button>
</div>
}
$('.delete').click(function() {
$(this).closest('.form-group').remove();
}
I have below code and I want to change label text base on selected role.
For example, If I select Admin then in label Text I need "Admin Name" etc.
I use below code but I is not working.
<div class="editor-field">
#Html.DropDownListFor(model => model.RoleID, new SelectList(ViewBag.RoleLsit, "Id", "RoleName"), "Select Category", new { onchange = "SelectedIndexChanged()" })
</div>
<div>
#Html.LabelFor(model => model.RoleName)
#Html.EditorFor(model => model.RoleName)
#Html.ValidationMessageFor(model => model.RoleName)
</div>
<script type="text/javascript">
function SelectedIndexChanged() {
var sub = document.getElementsByName("RoleName");
sub.value = "Selected Role Name";
}
Thanks
You could find your elements using #Html.IdFor, if your js is in your view.
function SelectedIndexChanged() {
//find the label
var label = document.querySelector('label[for="#Html.IdFor(m => m.RoleName)"]');
//get the dropDown selected option text
var dropDown = document.getElementById("#Html.IdFor(m => m.RoleId)");
var selectedText = dropDown.options[dropDown.selectedIndex].text;
//set the label text
label.innerHTML=selectedText
}
By the way, taking a look a jQuery might be an "interesting" option.
You could simply remove the new { onchange = "SelectedIndexChanged()" , then
$('##Html.IdFor(m => m.RoleId)').change(function() {
$('label[for="#Html.IdFor(m => m.RoleName)"]').text($(this).children('option:selected').text());
});
function SelectedIndexChanged() {
document.getElementsById("RoleName").InnerHtml="Role name goes here";
}
you should use get element by id instead of name because when this html helper renders the strongly typed binded attributes becomes the id of that control . so in your case role name will be the ID
I have set of textboxes on my form which are generated in a foeach like so:
View:
#for (int i = 0; i < Model.TransomeList.Count; i++)
{
ItemDrops tranItem = Model.TransomeList.ElementAt(i);
<div class="form-group">
#Html.Label(tranItem.ItemName.ToString(), new { #class = "col-sm-6 control-label" })
<div class="col-sm-6">
#Html.TextBoxFor(x => x.TransomeList[i].ItemPossInfo, new { #class = "form-control" })
#Html.HiddenFor(x => x.TransomeList[i].ItemName)
</div>
</div>
}
I'm using fluent validation and want to make sure each text box is required (ideally stating which text box too in the error message)
In my Validator class I have:
RuleFor(x => x.TransomeList).SetCollectionValidator(new TransDropValidator());
with:
public class TransDropValidator : AbstractValidator<ItemDrops>
{
public TransDropValidator()
{
RuleFor(x => x.ItemPossInfo)
.NotNull().WithMessage("Transom position required{O}", x => x.ItemPossInfo);
}
}
However this is not validating anything...what do i need to do?
Thanks
You also need the
#Html.ValidationMessageFor()
I assume you are doing server side validation. If not then futher work is need on your validator and you need to generate the JavaScript component.
I have a report that shows a list with the following columns:
A key column
A dropdown list showing a list of possible status
A title
This is the razor code:
#foreach (AdminSummary adminSummary in #Model.AdminSummaries) {
index++;
<div class="rep_tr0">
<div class="rep_td0" id="rk_#(index)">#adminSummary.RowKey</div>
<div class="rep_td0">Edit</div>
<div class="rep_td0">Delete</div>
<div class="rep_td0">#Html.DropDownListFor(??, ??)</div>
</div>
}
I also have the following class:
public static class AdminStatusReference
{
public static IEnumerable<SelectListItem> GetAdminStatusOptions()
{
return new[]
{
new SelectListItem { Value = "1", Text = "Released" },
new SelectListItem { Value = "2", Text = "Review" },
new SelectListItem { Value = "3", Text = "New" }
};
}
}
What I am not sure is how I can link these up. I know in the model I can store the list of
status options something like this:
vm.StatusList = GetAdminStatusOptions();
But how can I create the dropdownlist. I am very confused with all of the options of DropDownList and
DropDownListFor. Which should I be using and how can I send in the list of statuses?
What I am not sure is how I can link these up.
This will depend on how the model looks like.
But here's what you could do:
#for (var index = 0; index < Model.AdminSummaries.Count; index++)
{
<div class="rep_tr0">
<div class="rep_td0" id="rk_#(index)">
#Model.AdminSummaries[index].RowKey
</div>
<div class="rep_td0">
#Html.DropDownListFor(
x => x.AdminSummaries[index].Status,
AdminStatusReference.GetAdminStatusOptions()
)
</div>
<div class="rep_td0">
#Model.AdminSummaries[index].Title
</div>
</div>
}