I have 2 controller fields say Type and Data.
Depending on value selected for Type (Date or Text), I want to display Data field dynamically as either a text input or a custom timepicker input.
Since only one will be rendered at any time, I need to bind with the same property name (Data).
This is what I am trying:
#if (Model.Type == "Date")
{
// custom timepicker control goes here
<input asp-for="Data" class="form-control timepicker"/>
}
else
{
<input asp-for="Data" class="form-control text-input" type="text"/>
}
On page load only text input is rendered, and it shows/hides based on Type selected. The timepicker input is never displayed (the html is not generated at all).
Is there a way to achieve this in MVC?
You can not have two <input> elements with the same name. If a <form> containing multiple inputs with the same name is posted, the MVC model binder will only bind one value from the last input.
To achieve what you want, you have two options:
Either have only one input with name="Data" of type="text" in the View, and let the timepicker write the time as a string to this input. Then in the controller, parse this input value depending on the selected Type.
Or have two inputs with name="TextData" and name="TimeData", and disable and hide one of these inputs using JS depending on the selected Type. In the controller, read the value from the right input depending on the selected Type. This is arguably the cleaner solution.
In MVC5 the second solution would look like this (I am not familiar with MVC-Core):
#model MyViewModel
#using (Html.BeginForm("Submit", "MyController", FormMethod.Post)) {
#Html.EditorFor(m => m.Type)
#Html.EditorFor(m => m.TextData, new { #class = "text-input"})
#Html.EditorFor(m => m.TimeData, new { #class = "timepicker"})
}
<script type="text/javascript">
function toggleInput_() {
if ($('##Html.IdFor(m => m.Type)').val() === 'Text') {
$('##Html.IdFor(m => m.TextData)').prop('disabled', false).show();
$('##Html.IdFor(m => m.TimeData)').prop('disabled', true).hide();
}
else {
$('##Html.IdFor(m => m.TextData)').prop('disabled', true).hide();
$('##Html.IdFor(m => m.TimeData)').prop('disabled', false).show();
}
}
$(document).ready(function() {
$('##Html.IdFor(m => m.Type)').on('change', function() {
toggleInput_(); // toggle when drop down changes
});
toggleInput_(); // toggle initially on page load
});
</script>
Controller:
[HttPost]
public ActionResult Submit(MyViewModel postData) {
string textValue = null;
DateTime? timeValue = null;
if (postData.Type == "Text") {
textValue = postData.TextData;
}
else {
timeValue = postData.TimeData;
}
// ...
}
ASP MVC already has this functionality built in with Editor Templates. By following the convention, you can specify a template to be used for any type (including user-defined complex types) which will be rendered with #Html.EditorFor().
In a nutshell, just place two partial views in your ~/Views/Shared/EditorTemplatesfolder, one with model type DateTime and the other string. The correct partial view will be rendered when using #Html.EditorFor(m => m.Property) based on the type of Property.
Note: the default editor for a string property will already be an input with type="text", so you don't necessarily need to specify that template.
See this link for a tutorial on Editor templates (and Display templates):
https://exceptionnotfound.net/asp-net-mvc-demystified-display-and-editor-templates/
in an MVC 4 application this works:
#if (ViewBag.AdvisoryMessage != null)
{
#Html.LabelFor(m => m.CourseApplication.CourseReason, (string)ViewBag.AdvisoryMessage )
}
but this doesn't:
#if (ViewBag.AdvisoryMessage != null)
{
#Html.Label((string)ViewBag.AdvisoryMessage ))
}
I'd really like to know why. The text in AdvisoryMessage is page-specific and doesn't relate to CourseApplication.CourseReason.
Thanks!
A Label is related to something... so it has a "for" attribute.
Html.Label(string expression, string labelText, IDictionary<string, object> htmlAttributes);
expression - Populates the "for" attribute for your label (indicating what it is for). This will also populate the content of the Label if labelText is not present
labelText - This populates the content / text for the Label
htmlAttributes - This defines all of the specific styles or attributes that you want to add to the element such as class, id, data- attributes, style and more.
Check this uses:
#Html.Label("Name")
<label for="Name">
Name
</lable>
#Html.Label("Name", "First Name")
<label for="Name">
First Name
</lable>
I am trying to render the following HTML using an MVC3 Razor View:
<input id="EffectiveDate" name="EffectiveDate" type="date" data-options='{"mode": "flipbox"}' />
I have been unable to get the quotation marks in the data-options attribute to render. No matter what I try, they are rendered as "
Here are a couple of the many approaches I have tried in my View:
#Html.TextBoxFor(model => model.EffectiveDate, new { type = "date", data_options= " { 'mode':'flipbox' }"})
and
#Html.TextBoxFor(model => model.EffectiveDate, new { type = "date", data_options= #Html.Raw("{\"mode\":\"flipbox\"}")})
Any suggestions on how to decode the quotation marks?
You can do this by creating an MVC Editor template. First, create a folder called "EditorTemplates" inside the "Views\Shared" folder. Then put a file called DateTime.cshtml inside the EditorTemplates folder.
Then you can simply use the EditorFor() method against your view model's property like this (provided that the EffectiveDate property of of type DateTime):
#Html.EditorFor(m => m.EffectiveDate)
The complete code for the DateTime.cshtml editor template looks like this:
#model System.DateTime
#{
var id = this.ViewData.TemplateInfo.GetFullHtmlFieldId("");
var name = this.ViewData.TemplateInfo.GetFullHtmlFieldName("");
}
<input id="#id" name="#name" type="date" data-options='{"mode": "flipbox"}' />
This will produce the exact output that you are seeking.
One thing is certain: special symbols will always be encoded when you use any of the default MVC input extensions (i.e. TextBoxFor). That is because TagBuilder itself, which is used to build the tags for the HtmlHelper extensions, HtmlEncodes each attribute value in a tag. You can see this in the TagBuilder source:
private void AppendAttributes(StringBuilder sb)
{
foreach (var attribute in Attributes)
{
string key = attribute.Key;
if (String.Equals(key, "id", StringComparison.Ordinal /* case-sensitive */) && String.IsNullOrEmpty(attribute.Value))
{
continue; // DevDiv Bugs #227595: don't output empty IDs
}
string value = HttpUtility.HtmlAttributeEncode(attribute.Value);
sb.Append(' ')
.Append(key)
.Append("=\"")
.Append(value)
.Append('"');
}
}
Since you have no way to send that value already decoded, you have to decode it yourself in JavaScript. Here is a nice little jQuery trick that will do it:
var value = $('<textarea/>').html(yourElement.data('options')).val();
You might want to make a function for that, of course.
Sources:
http://aspnet.codeplex.com/
http://refresh-sf.com/blog/2009/05/decode-html-entities-with-jquery/
I'm having a problem trying to add a custom HTML5 data attribute to the table that is rendered using the WebGrid helper. I want the table tag look as follows:
<table data-test="testdata"><!-- Table Content --></table>
Here is a sample view using the Razor view engine:
#{
var myUser = new
{
Id = 1,
Name = "Test User"
};
var users = new[] { myUser };
var grid = new WebGrid(users);
}
#grid.GetHtml(htmlAttributes: new { data-test = "testdata"})
The last line will produce a "Invalid anonymous type member declarator." error, because of the hyphen in data-test.
With some of the other input HtmlHelpers, you can use an underscore in place of the hyphen and it will be automatically changed to a hyphen when rendered. This does not happen with the WebGrid.
If I pass in a dictionary for htmlAttributes:
#grid.GetHtml(htmlAttributes: new Dictionary<string, object> {{ "data-test", "testdata"}})
the table gets rendered as such:
<table Comparer="System.Collections.Generic.GenericEqualityComparer`1[System.String]" Count="1" Keys="System.Collections.Generic.Dictionary`2+KeyCollection[System.String,System.Object]" Values="System.Collections.Generic.Dictionary`2+ValueCollection[System.String,System.Object]"><!-- Table Content --></table>
What am I doing wrong and what should I do render the attribute as desired?
I am afraid that this is not possible. Unfortunately the WebGrid it doesn't support the same syntax as standard HTML helper such as TextBoxFor where you could:
#Html.TextBoxFor(x => x.SomeProp, new { data_test = "testdata" })
and the underscore would be automatically converted to dash.
I have an editor template for a custom object. Inside that editor template I use a couple of DropDownListFor helpers. In each of them I specify a unique model property (with the pre-selected value) and the select list containing all the select options.
Example:
<%=Html.DropDownListFor(m => m.DocumentCategoryType, Model.DocumentCategoryTypeList) %>
I know that the option values are being populated (from viewing source) and that my Model is passed in with the correct ID value (DocumentCategoryType).
When the view is rendered, there is no selected item in my dropdown and therefore it defaults to the first (non-selected) value.
Does anyone have any ideas?
Thanks.
We also solved the solution by populating a new SelectList that has the appropriate SelectListItem selected, but created this extension method to keep the call to DropDownListFor a little cleaner:
public static SelectList MakeSelection(this SelectList list, object selection)
{
return new SelectList(list.Items, list.DataValueField, list.DataTextField, selection);
}
Then your DropDownListFor call becomes:
<%= Html.DropDownListFor(m => m.DocumentCategoryType, Model.DocumentCategoryTypeList.MakeSelection(Model.DocumentCategoryType)) %>
Looking through the ASP.NET MVC 2 source code reveals some solutions to this problem. Essentially, any SelectListItem in the SelectList passed in the helper extension method that has the Selected property set to true does not have any bearing over the <option> element rendered with the selected attribute applied for the item.
The selected attribute on <option> elements is determined by
1) checking that the helper extension method was passed a SelectList. If this is null, the framework will look in the ViewData for a value corresponding to the key that is the view model property for which you wish to render the drop down list for. If the value is a SelectList, this will be used to render the <select> including taking any selected values, so long as the model state for the model property is null.
2) If a SelectList has been passed in the helper extension method and the model state for the model property is null, the framework will look in the ViewData for a default value, using the model property name as the key. The value in view data is converted to a string and any items in the SelectList passed to the helper extension method that have a value (if no value is set, then the Text will be checked) that matches the default value will have the Selected property set to true which in turn will render an <option> with the attribute selected="selected".
Putting this together, there are two plausible options that I can see to have an option selected and use the strongly typed DropDownListFor:
Using the following view model
public class CategoriesViewModel
{
public string SelectedCategory { get; private set ; }
public ICollection<string> Categories { get; private set; }
public CategoriesViewModel(string selectedCategory, ICollection<string> categories)
{
SelectedCategory = selectedCategory;
Categories = categories;
}
}
Option 1
Set a value in the ViewData in the controller rendering your view keyed against the property name of the collection used to render the dropdown
the controller action
public class CategoriesController
{
[HttpGet]
public ViewResult Select()
{
/* some code that gets data from a datasource to populate the view model */
ICollection<string> categories = repository.getCategoriesForUser();
string selectedCategory = repository.getUsersSelectedCategory();
CategoriesViewModel model = new CategoriesViewModel(selectedCategory, categories);
this.ViewData["Categories"] = selectedCategory;
return View(model);
}
[HttpPost]
public ActionResult Select(CategoriesViewModel model)
{
/* some code that does something */
}
}
and in the strongly typed view
<%: Html.DropDownListFor(m => m.Categories, Model.Categories.Select(c => new SelectListItem { Text = c, Value = c }), new { #class = "my-css-class" }) %>
Option 2
Render the dropdown using the name of the property of the selected item(s)
the controller action
public class CategoriesController
{
[HttpGet]
public ViewResult Select()
{
/* some code that gets data from a datasource to populate the view model */
ICollection<string> categories = repository.getCategoriesForUser();
string selectedCategory = repository.getUsersSelectedCategory();
CategoriesViewModel model = new CategoriesViewModel(selectedCategory, categories);
return View(model);
}
[HttpPost]
public ActionResult Select(CategoriesViewModel model)
{
/* some code that does something */
}
}
and in the strongly typed view
<%: Html.DropDownListFor(m => m.SelectedCategory, Model.Categories.Select(c => new SelectListItem { Text = c, Value = c }), new { #class = "my-css-class" }) %>
It is confirmed as a bug # aspnet.codeplex.com
and only behaves like this for strongly typed views.
Workaround: populate your SelectList in the view code
like
<%= Html.DropDown("DocumentCategoryType", new SelectList(Model.Categories,"id","Name",Model.SelectedCategory")) =>
Yuck. I ended up solving it like this. I hope this gets fixed for RTM.
<%if(Model!=null){ %>
<%= Html.DropDownListFor(m => m.DocumentCategoryType, new SelectList(Model.DocumentCategoryTypeList,"Value","Text", Model.DocumentCategoryType))%>
<%}else{%>
<%=Html.DropDownListFor(m => m.DocumentCategoryType, Model.DocumentCategoryTypeList) %>
<%}%>
Make sure you have a value assigned to m.DocumentCategoryType when you send it to the view.
Generally this value will get reset when you do a post back so you just need to specify the value
when returning to your view.
When creating a drop down list you need to pass it two values. 1. This is where you will store the selected value 2. Is the actual List
Example
<%=Html.DropDownListFor(m => m.DocumentCategoryType, Model.DocumentCategoryTypeList) %>
I made the mistake of setting the select list item Selected value to True. This won't do anything. Instead just assign a value to m.DocumentCategoryType in your controller and this will actually do the selection for you.
Here's another good solution if the source for your drop down list is an IEnumerable instead of a SelectList:
public static SelectList MakeSelection(this IEnumerable<SelectListItem> list, object selection, string dataValueField = "Value", string dataTextField = "Text")
{
return new SelectList(list, dataValueField, dataTextField, selection);
}
Model.DocumentCategoryTypeList
This is probably your problem. On the SelectListItems, do you set the value to the .ToString() output?
var list = new List<SelectListItem>()
{
new SelectListItem()
{
Value = Category.Book.ToString(),
Text = "Book"
},
new SelectListItem()
{
Value = Category.BitsAndPieces.ToString(),
Text = "Bits And Pieces" },
new SelectListItem()
{
Value = Category.Envelope.ToString(),
Text = "Envelopes" }
};
Works for me after doing that. It just needs to be able to match the value from the object
I managed to solve the same problem by saying the folling:
new SelectList(sections.Select(s => new { Text = s.SectionName, Value = s.SectionID.ToString() }), "Value", "Text")
This trick is converting to the value to a string. I know this has been mentioned in previous answers but i find my solution a little cleaner :). Hope this helps.
Copied na pasted from my project:
<%= Html.DropDownListFor(m => m.Profession.Profession_id, new SelectList(Model.Professions, "Profession_id", "Profession_title"),"-- Profession --")%>
Model that is passed:
...
public Profession Profession { get; set; }
public IList<Profession> Professions { get; set; }
...
Html generated:
<select id="Profession_Profession_id" name="Profession.Profession_id">
<option value="">-- Profesion --</option>
<option value="4">Informatico</option>
<option selected="selected" value="5">Administracion</option>
</select>
Works for me. I have this on the form and the only disadvantage is that if model is not valid and I return the model back to the view I have to reload the list of Professions.
obj.Professions = ProfileService.GetProfessions();
return View(obj);
I also had this problem with a field ProgramName. Turns out we used ViewBag.ProgramName in the BaseController and Layout.cshtml, and this was for a different purpose. Since the value in ViewBag.ProgramName was not found in the dropdownlist, no value was selected even though the SelectListItem.Selected was true for one item in the list. We just changed the ViewBag to use a different key and the problem was resolved.
Here is a drop-in DropDownListFor replacement that varies only slightly from the original MVC source.
Example:
<%=Html.FixedDropDownListFor(m => m.DocumentCategoryType,
Model.DocumentCategoryTypeList) %>
I was worried about the performance of making so many copies of my selectList, so instead, I added the selectedvalue as a custom attribute, then used jquery to actually perform the item select:
#Html.DropDownListFor(item => item.AttendeeID, attendeeChoices, String.Empty, new { setselectedvalue = Model.AttendeeID })
........
jQuery("select[setselectedvalue]").each(function () { e = jQuery(this); e.val(e.attr("setselectedvalue")); });