MVC3 EditorTemplate for a nullable boolean using RadioButtons

你说的曾经没有我的故事 提交于 2019-11-26 22:53:58

问题


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>&nbsp;</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?


回答1:


@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>



回答2:


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>



回答3:


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>



回答4:


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>



回答5:


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>
}



回答6:


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



来源:https://stackoverflow.com/questions/6752013/mvc3-editortemplate-for-a-nullable-boolean-using-radiobuttons

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!