Radio Button generates duplicate HTML id-s - html

It seems that the default ASP.NET MVC2 Html helper generates duplicate HTML IDs when using code like this (EditorTemplates/UserType.ascx):
<%# Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<UserType>" %>
<%: Html.RadioButton("", UserType.Primary, Model == UserType.Primary) %>
<%: Html.RadioButton("", UserType.Standard, Model == UserType.Standard) %>
<%: Html.RadioButton("", UserType.ReadOnly, Model == UserType.ReadOnly) %>
The HTML it produces is:
<input checked="checked" id="UserType" name="UserType" type="radio" value="Primary" />
<input id="UserType" name="UserType" type="radio" value="Standard" />
<input id="UserType" name="UserType" type="radio" value="ReadOnly" />
That clearly shows a problem. So I must be misusing the Helper or something.
I can manually specify the id as html attribute but then I cannot guarantee it will be unique.
So the question is how to make sure that the IDs generated by RadioButton helper are unique for each value and still preserve the conventions for generating those IDs (so nested models are respected? (Preferably not generating IDs manually.)

In addition to PanJanek's answer:
If you don't really need the elements to have an id, you can also specify id="" in the htmlAttributes (i.e. new { id="" }) parameter of helpers. This will result in the id attribute being left out completely in the generated html.
source: https://stackoverflow.com/a/2398670/210336

I faced the same problem. Specyfying IDs manually seems to be the only solution. If you don't need the ids for anything (like javascript), but want it only to be unique you could generate Guids for them:
<%: Html.RadioButton("", UserType.Primary, Model == UserType.Primary, new { id="radio" + Guid.NewGuid().ToString()}) %>
A more elegant solution would be to create your own extension method on HtmlHelper to separate ID creation logic from the view. Something like:
public static class HtmlHelperExtensions
{
public static MvcHtmlString MyRadioButtonFor<TModel, TValue>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TValue>> expression, bool value)
{
string myId = // generate id...
return htmlHelper.RadioButtonFor(expression, value, new {id=myId});
}
}
The helper method could use ViewContext and Model data to create more meaningfull IDs.
UPDATE:
If you use EditorTemplates to render the control like this
<%= Html.EditorFor(m=>m.User, "MyUserControl") %>
Then inside the MyUserControl.ascx (placed in ~/Views/Shared/EditorTemplates) you can use ViewData.TemplateInfo.HtmlFieldPrefix property to access the parent control ID or Html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId("MyPostfixPart") to generate prefixed id. Theese methods could be used in the helper extension above.
The same works with controls rendered with Html.Editor(...) and Html.EditorForModel(...). In the Html.Editor helper you can also specify htmlFiledName manually if you want.
When you embed the control with
<%= Html.Partial("UserControl", Model.User) %>
generation of meaningfull IDs is harder because the Html.Partial will not provide information about the prefix - the ViewData.TemplateInfo.HtmlFieldPrefix will be always empty. Then, the only solution would be to pass the prefix manually to the ascx control in as ViewData key of as a model field which is not as elegant a solution as the previous one.

If you do need to access the radio buttons via jQuery, I find that often the better place to set the id's will be on the page, close to their intended usage. This is how I did the same:
#Html.Label(Resource.Question)
#Html.RadioButtonFor(m => m.Question, true, new {id = "QuestionYes"}) Yes
#Html.RadioButtonFor(m => m.Question, false, new {id = "QuestionNo"}) No
Then my jQuery:
if ($("input[id='QuestionNo']").is(":checked")) {
$('#YesOptionalBlock').removeClass('showDiv');
$('#YesOptionalBlock').addClass('hideDiv');
}

Related

How to set programatically the Id, Name and Class atribute of a #Html.EditFor or #Html.TextBoxFor - MVC 4

I am trying to generate a unique label and and input text box for a partial view that is being used to render a list of user input rows.
By unique I mean that each input text box should have its unique html "id" and "name" so that when is submitted each input can be identified
In the View I have
#model UserDataModel
#{
var inpName = "benefName" + #Model.Row;
var inpAge = "benefAge" + #Model.Row;
}
#Html.LabelFor(x => x.Name, new { #class="labelhalf"})
#Html.EditorFor(x => x.Name, new { id = #inpName, htmlAttributes = new { #class = "form-control animated" } })
When the view is being render this is what I am seeing
<label class="labelhalf" for="Name">Nombre (Opcional)</label>
<input class="text-box single-line" id="Name" name="Name" type="text" value="">
As you can see the "name" and "id" attributes of the text input is "Name" and "Name" and is not using the value of the #inpName variable ("benefName1" for example)
Also I am trying to assign some CSS classes to that same input using "htmlAttributes"
I had previously tried this with this approach
<label form="FormStep_01" for=#inpName class="labelhalf">Nombre (Opcional)</label>
<input form="FormStep_01" id=#inpName class="form-control animated" pattern="^[_A-z0-9]{1,}$" type="text" placeholder="" required="">
...but the content of the input fields with this approach are not being submited and that is the reason I am trying to use the #Html.EditorFor
UPDATE
I am now using the TextBoxFor which takes the "id" and the "class" fine but not the "name" which is used in the submit
#Html.LabelFor(x => x.Name, new { #class = "labelhalf" })
#Html.TextBoxFor(x => x.Name, new { #id = #inpName, name = #inpName, #class = "form-control animated" })
Please let me know how to achieve this in MVC4
Issue 1 (Using EditorFor())
You cannot add html attributes using EditorFor() in MVC-4. This feature was not introduced until MVC-5.1, and then the correct usage is
#Html.EditorFor(m => m.SomeProperty, new { htmlAttributes = new { someAttribute = "someValue" }, })
Issue 2 (Using TextBoxFor())
You cannot change the value of the name attribute using new { name = "someValue" }. The MVC team built in a safe guard to prevent this because the whole purpose of using the HtmlHelper methods to generate form controls is to bind to your model properties, and doing this would cause binding to fail. While there is a workaround, if you do discover it, don't do it! As a side note - the following line of the private static MvcHtmlString InputHelper() method in the source code
tagBuilder.MergeAttribute("name", fullName, true);
is what prevents you overriding it.
Issue 3 (Manual html)
You not giving the inputs a name attribute. A form posts back a name/value pair based on the name and value attributes of successful controls, so if there is no name attribute, nothing will be sent to the controller.
Side note: If your manually generating html, there is no real need to add an id attribute unless your referring to that element in javascript or css.
Its unclear why your trying to create a input for something that does not appear to relate to your model, but if your trying to dynamically generate items for adding items to a collection property in your model, refer the answers here and here for some options which will allow you to bind to your model.

Input field set as 'Value=' instead of 'value='

I have a project written in C# MVC using Razor templates. On one of my pages I have several input fields that contain numeric values. The Razor code that sets the values of these input fields looks like this:
#Html.Editor(Model.DesignParams[i].ParamId,
new {
htmlAttributes = new
{
#Value = Model.DesignParams[i].DefaultValue,
#class = "form-control text-right",
#type = "text",
id = "_" + Model.DesignParams[i].ParamId,
uomid = Model.DesignParams[i].UOMId,
measureid = Model.DesignParams[i].MeasureId
}
})
The above code works fine using FireFox and Chrome and generates an input field that looks like this:
<input type="text" uomid="MBH" name="HeatOfRejection" measureid="HeatLoad"
id="_HeatOfRejection" class="form-control text-right text-box single-line"
value="5000.0">
But the same Razor code, identical #Model values viewed with IE generates this:
<input Value="5000" class="form-control text-right text-box single-line"
id="_HeatOfRejection" measureid="HeatLoad" name="HeatOfRejection"
type="text" uomid="MBH" value="" />
As you can see, there is a difference between the value= attribute generated for IE in that the value attribute that gets my actual value begins with an uppercase 'V' and the lowercase value is an empty string. I'm stumped on this...
Can anyone tell me why this is happening and possibly how to handle it?
This difference effects jQuery's ability to return the input's value with:
var value = $(inputfield).attr("value");
Maybe .val() will retrieve the input field value, but this is going to require a rewrite of core jQuery code that supports this page and others, so I wanted to ask if anyone can tell me why this 'Value=' gets created for IE only and if there is a way of overcoming the problem.
Update:
Changing #Value to #value (or just value) results in an empty value attribute in Firefox and IE:
<input type="text" value="" uomid="MBH" name="HeatOfRejection" measureid="HeatLoad"
id="_HeatOfRejection" class="form-control text-right text-box single-line">
As StuartLC points out, you are trying to get Html.Editor to do something it wasn't designed to do.
What happens when you pass a #value or #Value key to the htmlAttributes is that the rendering engine produces an attribute with that name in addition to the value attribute it's already generating:
<input type="text" name="n" value="something" value="somethingElse" />
or
<input type="text" name="n" value="something" Value="somethingElse" />
In both cases, you're giving the browser something bogus, so it can't be expected to exhibit predictable behavior.
As alluded above, Html.Editor has functionality to generate the value attribute based on the expression argument you pass to it. The problem is that you are using that incorrectly as well. The first argument to Html.Editor() needs to be an expression indicating the model property that the editor should be bound to. (e.g. the string value "DesignParams[0].ParamId") Nowadays, the preferred practice is to use the more modern EditorFor that takes a lambda function, as StuartLC showed in his post:
#Html.EditorFor(model => model.DesignParams[i].ParamId, ...)
You are "capitalising" the value html attribute. Change this to lower case...
#Value = Model.DesignParams[i].DefaultValue
as below ...
#value = Model.DesignParams[i].DefaultValue
IE is not the smartest of web browsers and there's definitely something wrong in the way Trident (they're parsing engine) validates elements' attributes as seen in these threads...
https://github.com/highslide-software/highcharts.com/issues/1978
Highcharts adds duplicate xmlns attribute to SVG element in IE
Also, as already noted somewhere else. What's the need for the Editor extension method? Isn't it simpler to just use TextBoxFor instead?
#Html.TextBoxFor(model => model.DesignParams[i].ParamId
, new
{
#class = "form-control text-right"
, uomid = Model.DesignParams[i].UOMId
, measureid = Model.DesignParams[i].MeasureId
})
Editor works with metadata. then you need to more about this,
http://aspadvice.com/blogs/kiran/archive/2009/11/29/Adding-html-attributes-support-for-Templates-2D00-ASP.Net-MVC-2.0-Beta_2D00_1.aspx
But the easiest way is go with
#model Namespace.ABCModel
#using (Html.BeginForm("Action", "Controller", FormMethod.Post, new { enctype = "multipart/form-data" }))
{
#Html.TextBoxFor(model => model.DesignParams[i].ParamId, new { #class = "form-control text-right", uomid = Model.DesignParams[i].UOMId, measureid = Model.DesignParams[i].MeasureId })
}
You shouldn't be using invalid Html attributes in this way. Use the data- attributes in Html 5.
Also, your use of #Html.Editor(Model.DesignParams[i].ParamId (assuming ParamId is a string) deviates from the helper's purpose, which is to reflect the property with the given name off the Model, and use the value of this property as the Html value attribute on the input. (MVC will be looking for a property on the root model with whatever the value of ParamId is, which seems to silently fail FWR)
I would do the defaulting of Model.DesignParams[i].ParamId = Model.DesignParams[i].DefaultValue in the Controller beforehand, or in the DesignParams constructor.
#Html.EditorFor(m => m.DesignParams[0].ParamID,
new {
htmlAttributes = new
{
// Don't set value at all here - the value IS m.DesignParams[0].ParamID
#class = "form-control text-right",
#type = "text",
id = "_" + Model.DesignParams[i].ParamId,
data_uomid = Model.DesignParams[i].UOMId,
data_measureid = Model.DesignParams[i].MeasureId
}
Note that this will give the input name as DesignParams[0].ParamID, which would be needed to post the field back, if necessary.
Here's a Gist of some example code
(The underscore will be converted to a dash)
Use data() in jQuery to obtain these values:
var value = $(inputfield).data("uomid");

Disable HTML5 form validation in Grails Project

I've started to design and implement a blog-homepage in Grails to get practice with Grails development and HTML. I'm not that experienced with HTML4/5 yet.
My problem is that i want to disable HTML5's form validation which standard message is:"Please fill in this field" if the field is required and not filled, and instead use my own custom error-text that can be typed into the file i18n/messages.properties.
I have read these two question on how to disable form validation in plain HTML5 with either novalidate="" or autocomplete="off"
I have generated the scaffolded templates in my Grails project, by typing: install-templates.
My plan was to change the _form.gsp to include either autocomplete="off" or novalidate="" in the method renderFieldForProperty(), but neither are working.
Hope somebody have solved this problem and want to share knowledge ;)
Edit: Code from scaffolded renderFieldForProperty():
private renderFieldForProperty(p, owningClass, prefix = "") {
boolean hasHibernate = pluginManager?.hasGrailsPlugin('hibernate')
boolean display = true
boolean required = false
if (hasHibernate) {
cp = owningClass.constrainedProperties[p.name]
display = (cp ? cp.display : true)
required = (cp ? !(cp.propertyType in [boolean, Boolean]) && !cp.nullable && (cp.propertyType != String || !cp.blank) : false)
}
if (display) { %>
<div class="fieldcontain \${hasErrors(bean: ${propertyName}, field: '${prefix}${p.name}', 'error')} ${required ? 'required' : ''}"> <-- At the end of this line i have tried the to attributes mentioned above
<label for="${prefix}${p.name}">
<g:message code="${domainClass.propertyName}.${prefix}${p.name}.label" default="${p.naturalName}" />
<% if (required) { %><span class="required-indicator">*</span><% } %>
</label>
${renderEditor(p)}
</div>
<% } } %>
Above if you scroll right i have written where i have tried the 2 attributes i mentioned in my post.
You need to customize renderEditor.template this file is responsible for render the form fields according to the Domain Class. As example I changed the isRequired() method:
private boolean isRequired() {
//!isOptional()
return false //always return false, not including the required='' in the field.
}

Webmatrix: passing ModelState to partial page

I am building a WebPages site and have an issue when I try to pass ModelState data to a partial page.
Here is the code for Create.cshtml:
#{
Page.Title = "Create Fund";
var name = "";
if (IsPost) {
name = Request.Form["name"];
if (!name.IsValidStringLength(2, 128)) {
ModelState.AddError("name", "Name must be between 2 and 128 characters long.");
}
}
}
#RenderPage("_fundForm.cshtml", name)
Here is the code for _fundForm.cshtml:
#{
string name = PageData[0];
}
<form method="post" action="" id="subForm">
<fieldset>
#Html.ValidationSummary(true)
<legend>Fund</legend>
<p>
#Html.Label("Name:", "name")
#Html.TextBox("name", name)
#Html.ValidationMessage("name", new { #class = "validation-error" })
</p>
<p>
<input type="submit" name="submit" value="Save" />
</p>
</fieldset>
</form>
The issue I am having is when there is an error for "name", the validation error does not display. Is there a special way to pass ModelState between the two pages?
_fundForm is going to be shared between Create.cshtml and Edit.cshtml.
ModelState is a readonly property of System.Web.WebPages.WebPage class. Its backing field is a private ModelStateDictionary and is initialized at first access. I can't see any way to force ModelState across pages, apart from doing it via reflection as seen in SO question: Can I change a private readonly field in C# using reflection?
Otherwise, you can simply use a third parameter in the invocation, like this:
#RenderPage("_fundForm.cshtml", name, ModelState);
In effect, the first parameter after the page name will become the Model of the new page, so there is enough space (i.e. the next parameter) to pass the ModelState.
In your "_fundForm.cshtml" merge the ModelState received by the calling page with the local one, like this:
#{
//In _fundForm.cshtml
var ms = PageData[1];
ModelState.Merge((ModelStateDictionary)ms);
}

Why is Html.DropDownList() producing <select name="original_name.name"> instead of just <select name="name">?

I have an editor template whose job is to take a SelectList as its model and build a select element in html using the Html.DropDownList() helper extension.
I'm trying to assign the name attribute for the select based on a ModelMetadata property. (Reason: on post-back, I need to bind to an object that has a different property name for this item than the ViewModel used to populate the form.)
The problem I'm running into is that DropDownList() is appending the name I'm providing instead of replacing it, so I end up with names like categories.category instead of category.
Here is some code for you to look at...
SelectList.ascx
<%# Control Language="C#"
Inherits="System.Web.Mvc.ViewUserControl<System.Web.Mvc.SelectList>" %>
<%= Html.DropDownList(
(string)ViewData.ModelMetadata.AdditionalValues["PropertyName"], Model) %>
Resulting HTML
<select id="SkillLevels_SkillLevel" name="SkillLevels.SkillLevel">
<option value="1">High</option>
<option value="2">Med</option>
<option selected="selected" value="3">Low</option>
</select>
Expected HTML
<select id="SkillLevels_SkillLevel" name="SkillLevel">
<option value="1">High</option>
<option value="2">Med</option>
<option selected="selected" value="3">Low</option>
</select>
Also tried
<%= Html.Encode((string)ViewData.ModelMetadata.AdditionalValues["PropertyName"])%>
...which resulted in "SkillLevel" (not "SkillLevels.SkillLevel"), proving that the data stored in metadata is correct.
and
<%= Html.DropDownList(
(string)ViewData.ModelMetadata.AdditionalValues["PropertyName"], Model,
new { name = (string)ViewData.ModelMetadata.AdditionalValues["PropertyName"] }) %>
...which still resulted in <select name=SkillLevels.Skilllevel>.
Questions
What's going on here? Why does it append the name instead of just using it? Can you suggest a good workaround?
Update:
I ended up writing a helper extension that literally does a find/replace on the html text:
public static MvcHtmlString BindableDropDownListForModel(this HtmlHelper helper)
{
var propertyName = (string)helper.ViewData.ModelMetadata.AdditionalValues["PropertyName"];
var compositeName = helper.ViewData.ModelMetadata.PropertyName + "." + propertyName;
var rawHtml = helper.DropDownList(propertyName, (SelectList)helper.ViewData.Model);
var bindableHtml = rawHtml.ToString().Replace(compositeName, propertyName);
return MvcHtmlString.Create(bindableHtml);
}
>>> I'd still like to understand why this workaround is necessary. Why does the select element's get assigned a composite name rather than the exact name I provide?
I have struggled with the MVC drop down - it just does not act right. So, I just roll my own html with a foreach on my model data. That may not be an acceptable workaround for you but MVC is flexible on purpose.
Disclaimer: I am not sure if this is totally safe and wont break anything.
But I faced the exact same situation. However, once i set
ViewData.TemplateInfo.HtmlFieldPrefix = String.Empty ,
the problem went away. This behavior seems to be by design inside of custom templates - take a look at ViewData.TemplateInfo.GetFullHtmlFieldName for example.