I have a property on one of my objects that is a nullable boolean, I want my logic to have true represent Yes, false to be No and null to be N/A. Now because I am going to have multiple properties like this on many different objects it makes the most sense to make an editor and display templates for these properties. I am going to use jQuery UI to apply a visual element of buttonset after this is all working but for now, that's beyond the scope of my problem. My editor template looks like this.
#model bool?
<div data-ui="buttonset">
#Html.RadioButtonFor(x => x, true, new { id = ViewData.TemplateInfo.GetFullHtmlFieldId("") + "Yes"}) <label for="#(ViewData.TemplateInfo.GetFullHtmlFieldId(""))Yes">Yes</label>
#Html.RadioButtonFor(x => x, false, new { id = ViewData.TemplateInfo.GetFullHtmlFieldId("") + "No" }) <label for="#(ViewData.TemplateInfo.GetFullHtmlFieldId(""))No">No</label>
#Html.RadioButtonFor(x => x, "null", new { id = ViewData.TemplateInfo.GetFullHtmlFieldId("") + "NA" }) <label for="#(ViewData.TemplateInfo.GetFullHtmlFieldId(""))NA">N/A</label>
</div>
My problem is that under no circumstances can I get this editor template to show the current value of the model correctly. Because I am not rendering a property of a model at this scope but the model itself, the built in logic in MVC3 will not set the checked property correctly because of a check that is made to verify the name is not empty or null (See MVC3 source, InputExtensions.cs:line#259). I can't set the checked attribute dynamically by comparing to the Model because the browser checks the radiobutton on the presence of the checked attribute not it's value, so even though my radio buttons would look like the following the last one is still the one selected.
<div class="span-4" data-ui="buttonset">
<input checked="checked" id="MyObject_BooleanValueYes" name="MyObject.BooleanValue" type="radio" value="True" /><label for="MyObject_BooleanValueYes">Yes</label>
<input checked="" id="MyObject_BooleanValueNo" name="MyObject.BooleanValue" type="radio" value="False" /><label for="MyObject_BooleanValueNo">No</label>
<input checked="" id="MyObject_BooleanValueNA" name="MyObject.BooleanValue" type="radio" value="null" /><label for="MyObject_BooleanValueNA">N/A</label>
</div><span class="field-validation-valid" data-valmsg-for="MyObject.BooleanValue" data-valmsg-replace="true"></span> </div>
I can't conditionally add the HtmlAttribute using something like if?truevalue:falsevalue becuase the true/false parts would be of different anonymous types and I get an error.
I'm struggling on how this should be done and am hoping one of you have a suggestion on how to tackle this problem?
#model bool?
<div data-ui="buttonset">
#{
Dictionary<string, object> yesAttrs = new Dictionary<string, object>();
Dictionary<string, object> noAttrs = new Dictionary<string, object>();
Dictionary<string, object> nullAttrs = new Dictionary<string, object>();
yesAttrs.Add("id", ViewData.TemplateInfo.GetFullHtmlFieldId("") + "Yes");
noAttrs.Add("id", ViewData.TemplateInfo.GetFullHtmlFieldId("") + "No");
nullAttrs.Add("id", ViewData.TemplateInfo.GetFullHtmlFieldId("") + "NA");
if (Model.HasValue && Model.Value)
{
yesAttrs.Add("checked", "checked");
}
else if (Model.HasValue && !Model.Value)
{
noAttrs.Add("checked", "checked");
}
else
{
nullAttrs.Add("checked", "checked");
}
}
#Html.RadioButtonFor(x => x, "true", yesAttrs) <label for="#(ViewData.TemplateInfo.GetFullHtmlFieldId(""))Yes">Yes</label>
#Html.RadioButtonFor(x => x, "false", noAttrs) <label for="#(ViewData.TemplateInfo.GetFullHtmlFieldId(""))No">No</label>
#Html.RadioButtonFor(x => x, "null", nullAttrs) <label for="#(ViewData.TemplateInfo.GetFullHtmlFieldId(""))NA">N/A</label>
</div>
How about some extension method fun to keep that "one line to rule them all". :-)
public static class DictionaryHelper
{
// This returns the dictionary so that you can "fluently" add values
public static IDictionary<TKey, TValue> AddIf<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, bool addIt, TKey key, TValue value)
{
if (addIt)
dictionary.Add(key, value);
return dictionary;
}
}
And then in your template file you simply change the signature of how you are adding the additional parameters including the checked="checked" attribute to the element.
#model bool?
<div data-ui="buttonset">
#Html.RadioButtonFor(x => x, true, new Dictionary<string,object>()
.AddIf(true, "id", ViewData.TemplateInfo.GetFullHtmlFieldId("") + "Yes")
.AddIf(Model.HasValue && Model.Value, "checked", "checked")
) <label for="#(ViewData.TemplateInfo.GetFullHtmlFieldId(""))Yes">Yes</label>
#Html.RadioButtonFor(x => x, false, new Dictionary<string,object>()
.AddIf(true, "id", ViewData.TemplateInfo.GetFullHtmlFieldId("") + "No")
.AddIf(Model.HasValue && !Model.Value, "checked", "checked")
) <label for="#(ViewData.TemplateInfo.GetFullHtmlFieldId(""))No">No</label>
#Html.RadioButtonFor(x => x, "null", new Dictionary<string,object>()
.AddIf(true, "id", ViewData.TemplateInfo.GetFullHtmlFieldId("") + "NA")
.AddIf(!Model.HasValue, "checked", "checked")
) <label for="#(ViewData.TemplateInfo.GetFullHtmlFieldId(""))NA">N/A</label>
</div>
The problem is that you need to set the checked attribute because the Html.RadioButtonFor does not check a radio button based on a nullable bool (which appears to be a flaw).
Also by putting the radio buttons inside of the label tag, you can select value by clicking the label.
Shared/EditorTemplates/Boolean.cshtml
#model bool?
<label>
<span>n/a</span>
#Html.RadioButtonFor(x => x, "", !Model.HasValue ? new { #checked=true } : null)
</label>
<label>
<span>Yes</span>
#Html.RadioButtonFor(x => x, true, Model.GetValueOrDefault() ? new { #checked = true } : null)
</label>
<label>
<span>No</span>
#Html.RadioButtonFor(x => x, false, Model.HasValue && !Model.Value ? new { #checked = true } : null)
</label>
You just need to handle the special null case like so:
<label class="radio">
#Html.RadioButtonFor(x => x.DefaultBillable, "", new { #checked = !this.Model.DefaultBillable.HasValue })
Not set
</label>
<label class="radio">
#Html.RadioButtonFor(x => x.DefaultBillable, "false")
Non-Billable
</label>
<label class="radio">
#Html.RadioButtonFor(x => x.DefaultBillable, "true")
Billable
</label>
You can use a #helper to simplify the accepted answer:
#model bool?
<div data-ui="buttonset">
#Radio(true, "Yes", "Yes")
#Radio(false, "No", "No")
#Radio(null, "N/A", "NA")
</div>
#helper Radio(bool? buttonValue, string buttonLabel, string buttonId)
{
Dictionary<string, object> attrs = new Dictionary<string, object>();
// Unique button id
string id = ViewData.TemplateInfo.GetFullHtmlFieldId("") + buttonId;
attrs.Add("id", id);
// Check the active button
if (Model == buttonValue)
{
attrs.Add("checked", "checked");
}
#Html.RadioButtonFor(m => m, buttonValue, attrs) <label for="#id">#buttonLabel</label>
}
using Canvas example above I built a customization model and view so you can control the values via a model and edit them in code, bools aren't always a yes/no/(n/a) so Here's how it looks in MVC5.
using a generic model for the nullable bool
public class Customisable_NullableRadioBool
{
public bool? Result { get; set; }
public string TrueLabel { get; set; }
public string FalseLabel { get; set; }
public string NullLabel { get; set; }
public string AttributeTitle { get; set; }
}
Here's the CSHTML to be stored in:
~/Views/Shared/EditorTemplates/Customisable_NullableRadioBool.cshtml
#model Customisable_NullableRadioBool
#Model.AttributeTitle<br />
<div class="control-group">
<label>
#Html.RadioButtonFor(x => x.Result, "", !Model.Result.HasValue ? new { #checked = true } : null)
<span>#Model.NullLabel</span>
</label>
<br />
<label>
#Html.RadioButtonFor(x => x.Result, true, Model.Result.GetValueOrDefault() ? new { #checked = true } : null)
<span>#Model.TrueLabel</span>
</label>
<br />
<label>
#Html.RadioButtonFor(x => x.Result, false, Model.Result.HasValue && !Model.Result.Value ? new { #checked = true } : null)
<span>#Model.FalseLabel</span>
</label>
</div>
And then you can reference the generic class and the editor template through the rest of your project and render the editor template like this.
#Html.EditorFor(m => m.YourCustomisable_NullableBool, "Customisable_NullableRadioBool")
And the rendered output examples
Related
<div class="row mt-4">
<div class="col-md-12 correct_answer">
<lable>Correct Answer</lable><br /><br />
<label class="radio-label">
#Html.RadioButtonFor(x => x.IsCorrectAnswerOption1, new { #class = "form-control", #type = "radio", id = "IsCorrectAnswerOption1", name = "IsCorrectAnswerOption1"})
<span>A</span>
</label>
<label class="radio-label">
#Html.RadioButtonFor(x => x.IsCorrectAnswerOption2, new { #class = "form-control", #type = "radio", id = "IsCorrectAnswerOption2", name = "IsCorrectAnswerOption2" })
<span>B</span>
</label>
<label class="radio-label">
#Html.RadioButtonFor(x => x.IsCorrectAnswerOption3, new { #class = "form-control", #type = "radio", id = "IsCorrectAnswerOption3", name = "IsCorrectAnswerOption3" })
<span>C</span>
</label>
<label class="radio-label">
#Html.RadioButtonFor(x => x.IsCorrectAnswerOption4, new { #class = "form-control", #type = "radio", id = "IsCorrectAnswerOption4", name = "IsCorrectAnswerOption4" })
<span>D</span>
</label>
<br />
<span id="CorrectAnswerError" class="error">Correct Answer is required</span>
</div>
</div>
Here i have four different radio buttons with different id's and name in that model i have one true value but here i am unable to bind the selected radio value i.e true.
Instead of using boolean variables for each option you can create an enum and give it as a property in a model. In post method you can directly map selected option by the user by
MyOptions Property itself.
Note: Do not change the name attribute of the element.
//Your Model
public class Answer
{
public Options MyOptions { get; set; }
}
public enum Options
{
None,
Option1,
Option2,
Option3,
Option4,
}
//Your Controller
public ActionResult SelectOption()
{
Answer ans = new Answer();
return View(ans);
}
[HttpPost]
public ActionResult SelectOption(Answer answer)
{
var selectedOption = answer.MyOptions;
return View();
}
//Your cshtml page
#model Answer;
#using (Html.BeginForm(FormMethod.Post))
{
#Html.RadioButtonFor(x => x.MyOptions, Options.Option1) <span>Option 1</span>
#Html.RadioButtonFor(x => x.MyOptions, Options.Option2)<span>Option 2</span>
#Html.RadioButtonFor(x => x.MyOptions, Options.Option3)<span>Option 3</span>
#Html.RadioButtonFor(x => x.MyOptions, Options.Option4)<span>Option 4</span>
<button type="submit">Submit</button>
}
I have MVC class and one of the variables has been declared as:
[UIHint("YesNoRadio")]
[Required(ErrorMessage = "test")]
public bool? Emergency { get; set; }
this creates HTML as
<div class="radio-inline"><label>
<input data-val="true" data-val-required="Test" id="Emergency" name="Emergency" type="radio" value="true">Yes</label>
</div>
<div class="radio-inline"><label>
<input id="Emergency" name="Emergency" type="radio" value="false">No</label>
</div>
what i want is to add new attribute, lets say div-effect = "emergencyExplain" and radio button to come as
<label><input id="Emergency" name="Emergency" type="radio" value="false" div-effect = "emergencyExplain">No</label>
YesNoRadio.cshtml is below:
#model bool?
<div class="radio-inline">
<label>
#if (Model.HasValue && Model.Value)
{
#Html.RadioButtonFor(x => x, "true", new { #checked = "checked" });
}
else
{
#Html.RadioButtonFor(x => x, "true");
}
Yes
</label>
</div>
<div class="radio-inline">
<label>
#if (Model.HasValue && !Model.Value)
{
#Html.RadioButtonFor(x => x, "false", new { #checked = "checked" });
}
else
{
#Html.RadioButtonFor(x => x, "false");
}
No
</label>
</div>
and its called as:
#Html.EditorFor(m => m.Emergency, new { htmlAttributes = new { #class = "form-control" } })
New to MVC form creation so any help in pointing in right direction will be appreciated.
Thanks
Using the [UIHint] attribute just instructs the EditorFor() method to use that template. It does not pass any additional data to the template other that the modell property. You need to use this overload of EditorFor() where you pass the name of the template and an object representing the additionalViewData.
You have no shown the model property that contains the value that you want to add to the data-effect attribute, but assuming its
public string Effect { get; set; }
and you set its value in the GET method before you pass the model to the view, then delete the [UIHint] attribute from the Emergency property and modify the main view to
#Html.EditorFor(m => m.Emergency, "YesNoRadio", new { effect = Model.Effect })
Then change the YesNoRadio.cshtml template to
<div class="radio-inline">
<label>
#Html.RadioButtonFor(x => x, true, new { id = "", div_effect = ViewData["effect"] })
<span>Yes</span>
</label>
<label>
#Html.RadioButtonFor(x => x, false, new { id = "", div_effect = ViewData["effect"] })
<span>No</span>
</label>
</div>
Which will generate
<input data-val="true" data-val-required="Test" div-effect="emergencyExplain" name="Emergency" type="radio" value="True">
A few things to note about your current view code.
Using new { htmlAttributes = new { #class = "form-control" } }
wont do anything when using a custom EditorTemplate - its only
applicable using the built-in templates (how would it know which
element to apply that class to). If you want the class name applied
to the radio buttons, add that in the RadioButtonFor() method in
the template
You do not need to (and should not) set the checked attribute.
That attribute is set by the RadiobuttonFor() method based on the
value of the property (if its null, no buttons will be selected,
and if its true or false then the appropriate button will be
selected
Note also the use of new { id = "" } which removes the id
attribute which would other wise be generating duplicates which is
invalid html
I choose true or false but always I saw false default value of bool. Please help me.
MODEL
public partial class Istek
{
public bool iade { get; set; }
}
VIEW
I tried this
#Html.RadioButtonFor(x => x.iade, "true", new { #id = "iade-evet", #class = "md-radiobtn" })
#Html.RadioButtonFor(x => x.iade, "false", new { #id = "iade-hayir", #class = "md-radiobtn" })
and this
#Html.RadioButtonFor(x => x.iade, true, new { #id = "iade-evet", #class = "md-radiobtn" })
#Html.RadioButtonFor(x => x.iade, false, new { #id = "iade-hayir", #class = "md-radiobtn" })
and this
<input type="radio" id="iade-evet" name="iade" class="md-radiobtn">
<input type="radio" id="iade-hayir" name="iade" class="md-radiobtn">
but all of these not worked.
CONTROLLER
Your attempt is mostly correct but not broken. It's not necessary to escape the id. class is marked because it is a C# reserved keyword.
#Html.RadioButtonFor(x => x.iade, true, new { id = "iade-evet", #class = "md-radiobtn" })
#Html.RadioButtonFor(x => x.iade, false, new { id = "iade-hayir", #class = "md-radiobtn" })
Now check the rendered HTML
<input id="iade-evet" name="iade" value="True" ... />
<input id="iade-hyir" name="iade" value="False" ... />
The name should match the model's property name. If you have a multi-part identifier like x.iade (this often happens when you reference a child model) then you'll need to change the parameter names so they match.
Either the action signature
public ActionResult Ekle(Istek x, bool zararliEtki)
{
}
Or the model's identifier in the view
#Html.RadioButtonFor(x => istek.iade, true, new { id="iade-evet", #class="md-radiobtn" })
Add value to radio button. Its not a checkbox which automatically sends true or false. radio button must have a value.
Since I'm handling a extremely complex model and forms I will reduce my problem in a more understandable example (please excuse if there is any typo).
First I will show the scenario:
The model...
public class Foo
{
[Required]
public int fooId {get; set;}
public string fooName {get; set;}
public List<Bar> barList {get; set;}
}
public class Bar
{
[Required]
public int barId {get; set;}
public string barName {get; set;}
}
The view...
#model Foo
#using (Html.BeginForm("Save", "form", FormMethod.Post))
{
<div class="control-group">
<div class="controls">
#Html.TextBoxFor(model => Model.fooId)
</div>
</div>
<div class="control-group">
<div class="controls">
#Html.TextBoxFor(model => Model.fooName)
</div>
</div>
#for (int i = 0; i < Model.barList.Count(); i++)
{
#Html.EditorFor(m => m.barList[i])
}
}
The "bar" editor template...
#model Bar
<div class="control-group">
<div class="controls">
#Html.TextBoxFor(m => m.barId)
</div>
</div>
<div class="control-group">
<div class="controls">
#Html.TextBoxFor(m => m.barName)
</div>
</div>
The problem that I'm having is during the client-side validation for inputs in nested collections, in this case I'm not able to validate the "barId" input field. It simply ignores it...
In the case of the fooId field, it's validated OK.
If we go deeper, a "foo" object with 2 "bar" items would generate something like this:
<div class="control-group">
<div class="controls">
<input class="input-validation-error" id="fooId" name="fooId" type="text" value="">
</div>
</div>
<div class="control-group">
<div class="controls">
<input id="fooName" name="fooName" type="text" value="">
</div>
</div>
<div class="control-group">
<div class="controls">
<input id="barList_0__barId" name="barList[0].barId" type="text" value="">
</div>
</div>
<div class="control-group">
<div class="controls">
<input id="barList_0__barName" name="barList[0].barName" type="text" value="">
</div>
</div>
<div class="control-group">
<div class="controls">
<input id="barList_1__barId" name="barList[1].barId" type="text" value="">
</div>
</div>
<div class="control-group">
<div class="controls">
<input id="barList_1__barName" name="barList[1].barName" type="text" value="">
</div>
</div>
As you can see, the items inside the "bar" collection have received a different id and name. This is the normal behaviour for rendering the collections.
But it seems to be that the client-side validation doesn't work with these ids and names. The validation will work only if I change the id & name to "barId", removing the collection index..
After hours of investigation, I've found some articles and posts regarding issues like this, but nothing concrete and I still could not solve this.
IValidatableObject in MVC3 - client side validation
mvc clientside validation for nested (collection) properties
I did not find a solution, but I did find a workaround.
Functional explanation: the Model is called "InsuranceLine", and it has a collection "InsuranceLine.Letters." Each Letter has a nullable Boolean property "Letter.IsDeficient". If "IsDeficient" is changed from False to True then the string field "Letter.ReasonCode" is required. "IsDeficient" is rendered as a checkbox, and "ReasonCode" is rendered as two radio buttons, "Corrected" and "Waived".
Here is the custom attribute:
public class ReasonCodeAttribute : ValidationAttribute, IClientValidatable
{
private const string errorMessage = "When 'Deficient' is changed from True to False you must select a Reason.";
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
{
yield return new ModelClientValidationRule
{
ErrorMessage = errorMessage,
ValidationType = "reasoncoderequired"
};
}
protected override ValidationResult IsValid(object value, ValidationContext validationContext)
{
Letter letter = validationContext.ObjectInstance as Letter;
if(!letter.IsDeficient.GetValueOrDefault()
&& letter.IsDeficient_OriginalState.GetValueOrDefault()
&& (value == null || string.IsNullOrEmpty(value.ToString())))
{
return new ValidationResult(errorMessage);
}
else
{
return null;
}
}
}
I decorate Letter.ReasonCode with the custom attribute:
[ReasonCodeAttribute]
public string ReasonCode { get; set; }
I render the nested Letters collection in the *.cshtml page:
#for (int i = 0; i < Model.Letters.Count; i++)
{
#Html.EditorFor(m => m.Letters[i].IsDeficient, "MyCustomTemplate", new { htmlAttributes = new { #class="cb-is-deficient" } })
<div class="rb-reason-code">
<label>#Html.RadioButtonFor(m => m.Letters[i].ReasonCode, myVar == "C", new { id = id + "C", #class ="rb-reason-code" }) Corrected</label>
<label>#Html.RadioButtonFor(m => m.Letters[i].ReasonCode, myVar == "W", new { id = id + "W", #class = "rb-reason-code" }) Waived</label>
</div>
}
The GetClientValidationRules() method of the ReasonCode attribute causes the asp.net runtime to generate the following attribute when it renders the ReasonCode into an html radio button:
data-val-reasoncoderequired="When 'Deficient' is changed from True to False you must select a Reason.".
In JavaScript I add the 'reasoncoderequired' method to the validator like so in the document ready method. As part of my workaround I need to manually add the class "error" to my display so that the user gets a visual hint of the invalid state of the model:
$.validator.addMethod('reasoncoderequired', function (value, element) {
var $parent = $(element).closest('div.parent');
var $cb = $parent.find('input[type="checkbox"].cb-is-deficient');
if ($cb.prop('defaultChecked')) {
var $selectedRadioButton = $parent.find('div.rb-reason-code').find('input[type="radio"]:checked');
if ($selectedRadioButton.length == 0) {
$parent.addClass('error');
return false;
}
}
$parent.removeClass('error');
return true;
});
Finally, I add the reasoncoderequired rule to each ReasonCode radio button like so, also in the document ready method. The "messages" simply reads from the data-val-reasoncoderequired attribute rendered with each input to display the error message:
$form.find('input[type="radio"].rb-reason-code').each(function () {
$(this).rules('add',
{
reasoncoderequired: true,
messages: { reasoncoderequired: $(this).attr('data-val-reasoncoderequired') }
});
})
I'm attempting to find the correct Razor syntax for mutually exclusive radio buttons that both reflect the value of a boolean property on my model. My model has this:
public bool IsFemale{ get; set; }
I would like to display this with two radio buttons, one "Male" and the other "Female," but everything I've tried so far has not reflected the actual value of the IsFemale property on the model. Currently, I have this:
#Html.RadioButtonFor(model => model.IsFemale, !Model.IsFemale) Male
#Html.RadioButtonFor(model => model.IsFemale, Model.IsFemale) Female
This seems to persist the value correctly if I change and update, but does not mark the correct value as checked. I'm sure this is something stupid, but I'm stuck.
Try like this:
#Html.RadioButtonFor(model => model.IsFemale, "false") Male
#Html.RadioButtonFor(model => model.IsFemale, "true") Female
And here's the full code:
Model:
public class MyViewModel
{
public bool IsFemale { get; set; }
}
Controller:
public class HomeController : Controller
{
public ActionResult Index()
{
return View(new MyViewModel
{
IsFemale = true
});
}
[HttpPost]
public ActionResult Index(MyViewModel model)
{
return Content("IsFemale: " + model.IsFemale);
}
}
View:
#model MyViewModel
#using (Html.BeginForm())
{
#Html.RadioButtonFor(model => model.IsFemale, "false", new { id = "male" })
#Html.Label("male", "Male")
#Html.RadioButtonFor(model => model.IsFemale, "true", new { id = "female" })
#Html.Label("female", "Female")
<button type="submit">OK</button>
}
In MVC 6 (ASP.NET Core) this can also be achieved with tag helpers:
<label>
<input type="radio" asp-for="IsFemale" value="false" /> Male
</label>
<label>
<input type="radio" asp-for="IsFemale" value="true" /> Female
</label>
As far as September 2022.
Boolean radio button using: .NET Framework 4.7.2, MVC 5.2.7, Bootstrap 5.1.3.
Model
public class TheViewModel
{
public bool IndustrialDegree { get; set; }
}
View
<div class="col-4 col-md-2 mb-3">
#Html.Label("Industrial Degree", new { #class = "d-block form-label" })
<div class="btn-group d-flex" role="group" aria-label="Basic radio toggle button group">
#Html.RadioButtonFor(Model => Model.IndustrialDegree, true, new { #class = "btn-check", #id = "IndustrialDegreeTrue" })
#Html.Label("IndustrialDegreeTrue", "Yes", new { #class = "btn btn-outline-primary" })
#Html.RadioButtonFor(Model => Model.IndustrialDegree, false, new { #class = "btn-check", #id = "IndustrialDegreeFalse" })
#Html.Label("IndustrialDegreeFalse", "No", new { #class = "btn btn-outline-primary" })
</div>
</div>
Notice that I'm returning a pure boolean value to the controller.
Result
It works in both directions.
This is what you'll see on the controller when debugging the view model.
and