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.
@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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With