MVC - Require text box filled in when radio button

2019-09-10 01:59发布

问题:

Trying to figure out the best way to handle this.

I need to have logic in where it requires: 1) the Amount if a the color type allows an amount.
2) Credit cards if applicable (only allowed on 1 color at this time).

In the View, I had the required validator however since it applies to the Amount, it shows on ALL amounts, not just the textbox for the radio button selected.

I've tried jQuery to put Required attributes on the fields when clicked but seems like when I set it it applies to every item: Example: $('#TxtColorType_Green').rules("add", "required");

Sample Screen Shot

Example below demos how it would work:

  1. Red - Allows an amount, but no credit cards
  2. Green - Allows an amount and Credit card
  3. Blue - Does not allow amount

MODELS

public class ColorSelected
{
    [Required(ErrorMessage = "Color Required")]
    [Display(Name = "Color Type:")]
    public string ColorTypeId { get; set; }

    // [Required(ErrorMessage = "Color Amount Required")]  since Radio Button, need to set Required attribute when selected **
    [Display(Name = "Color Amount:")]
    public decimal? CostAmt { get; set; }
}

public class ColorType
{
    Public String ColorTypeId;
    Public String ColorTypeShortDesc;
    Public String Description;
    Public String AmountDescription;
    Public String DefaultChoice;
    public bool? DefaultChoice { get; set; }
    public bool? AllowCreditCard { get; set; }

    public ColorType()
    {
        init();
    }
}

public class ColorTypeRed : ColorType
{
    public override void init()
    {
        ColorTypeId = "R";
        ColorTypeShortDesc = "Red";
        Description = "Red";
        AmountDescription = "Pay Later.";
        DefaultChoice = true;
    }
}

public class ColorTypeGreen : ColorType
{
    public override void init()
    {
        ColorTypeId = "G";
        ColorTypeShortDesc = "Gr";
        Description = "Green";
        AmountDescription = "Pay Now";
        AllowCreditCard = true;    
    }
}

public class ColorTypeBlue : ColorType
{
    public override void init()
    {
        ColorTypeId = "B";
        ColorTypeShortDesc = "Bl";
        Description = "Blue";
        AmountDescription = null;
    }
}

ViewModel

        public class ColorViewModel
        {
            public ColorSelected colorSelected;
            public List<ColorType> ColorTypes;
            public ColorViewModel()
            {
                ColorTypes.Add(new ColorTypeBlue());
                ColorTypes.Add(new ColorTypeRed());
                ColorTypes.Add(new ColorTypeGreen());

            }
        }

View

    @model ColorViewModel
    <div id="divColorType" class="ec-radiobtnlist">
        @{
            foreach (var ColorType in Model.ColorTypes)
            {
                var idVal = string.Format("ColorType_{0}", ColorType.ColorTypeShortDesc);
                <div>
                    <label>
                        @Html.RadioButtonFor(m => m.ColorSelected.ColorTypeId,
                                            ColorType.ColorTypeId,
                                            new { id = string.Format("Rb{0}", idVal) }
                                        )
                        <span>@ColorType.Description</span>
                    </label>

                    @{
                        if (ColorType.AmountDescription != null)
                        {
                            <text>
                                @Html.TextBoxFor(m => m.ColorSelected.CostAmt,
                                        new { @id = string.Format("TxtAmt{0}", idVal), @class = "txtAmt", @maxlength = "10", @size = "3" }).00,
                                @Html.Raw(ColorType.AmountDescription)
                                @*@Html.ValidationMessageFor(m => m.ColorSelected.CostAmt)  Unfortunately right now if put this in the error will occur on ALL Color types as it uses the same property*@
                            </text>
                        };
                        if (ColorType.AllowCreditCard == true)
                        {
                            <div id=@string.Format("{0}_PmtCC", idVal)>
                                @Html.Partial("_CreditCardDetails")
                            </div>
                        };
                    }
            </div>
            }
        }
    </div>

Thanks in Advance!

回答1:

There are multiple reasons why you code will fail, including that your generating multiple textboxes for the same property (CostAmt) and the DefaultModelBinder will only bind the first value (so if you select 'Green', the value of the textbox associated with 'Red' will be bound). In fact nothing will be bound in the POST method because your view model contains only fields, not properties.

To have both client and server side validation, you need to use a conditional validation attribute that implements IClientValidatable, for example a foolproof [RequiredIf("SelectedColor", "G")] applied to the properties describing the 'CreditCard', and those properties must also be in the view model. AFAIK, there is nothing out of the box to handle a 'RequiredIf' for multiple values ('Red' or 'Green'), but you can download my RequiredIfContains attribute from GitHub

Your view model should be

public class ColorViewModel
{
    public string SelectedColor { get; set; } // set this to "R" or "G" or "B" initially to select a radio button
    public bool IsAmountHidden { get; set; } // set based on the value of SelectedColor
    public bool IsCreditCardHidden { get; set; }
    public List<ColorType> ColorTypes { get; set; }
    [RequiredIfContains("SelectedColor", "R, G")] // custom validation attribute
    public decimal? Amount { get; set; } // not clear why this is decimal when your view shows the fractions as .00
    [RequiredIf("SelectedColor", "G")]
    public string SelectedCreditCard { get; set; }
    ... more properties of Credit Card
    public IEnumerable<SelectListItem> CreditCardTypeList { get; set; }
}

Then in the view

// Generate radio buttons
@foreach (var ColorType in Model.ColorTypes)
{
    <label>
        @Html.RadioButtonFor(m => m.SelectedColor, ColorType.ColorTypeId, new { id = "", @class = "color" })
       <span>@ColorType.Description</span>
    </label>
}
// Generate textbox
@{ var amountClass = Model.IsAmountHidden ? "hidden" : null; }
<div id="cost" class="@amountClass ">
    @Html.TextBoxFor(m => m.Amount)
    @Html.ValidationMessageFor(m => m.Amount)
</div>
// Generate credit card controls
@{ var creditCardClass = Model.IsCreditCardHidden ? "hidden" : null; }
<div id="creditcard" class="@creditCardClass">
    @Html.DropDownListFor(m => m.SelectedCreditCard, Model.CreditCardTypeList)
    @Html.ValidationMessageFor(m => m.SelectedCreditCard)
    .... // more properties of Credit Card
</div>

Where hidden is styled with display: none;

You will then need some javascript to handle the radio buttons .change() event to toggle the visibility of the controls based on the value of the selected radio button

$('.color').change(function() {
    var selected = $('.color:checked').val();
    // add or remove the 'hidden' class name based on the value
});