1

I have a modelview that contains a list of ICustomInput values

public class DemoViewModel {
    [Required]
    public string FirstName {get; set;}
    [Required]
    public string LastName {get; set;}

    [RequiredIf("DayPhoneRequired", true)]
    public string DayPhone {get; set;}
    public bool DayPhoneRequired {get; set;} = false;
    public List<ICustomInput> CustomInputFields { get; set; } = new List<ICustomInput>();
}

an example of an ICustomInput

public class CustomTextInput : ICustomInput
    {
        public CustomField Field { get; }
        public string DisplayName { get; set; }

        [RequiredIf("DataValueRequired", true, ErrorMessage = "This is a required field")]
        public virtual string DataValue { get; set; }
        public bool DataValueRequired { get; set; } = false;
        public virtual string ClassName => "CustomTextInput";
        public string AssemblyName => "Application.Models";
    }

The purpose of this is so that i can pull information from the DB about the custom input fields that the logged in client has requested on the form. One client may want a couple text fields, another client may want a drop down. These custom fields may or may not require input as well. (The CustomField object is an older object returned by the dataLayer and used heavily, I don't want to rebuild it, but assume it's just full of strings)

I have an editor template for the concrete implementations of ICustomInputs as well as custom binders that allow me to get the data on post. But the issue I'm having is that the RequiredIf attribute is setting the unobtrusive data values for client side validation the same for all ICustomInputs. It makes sense since they all have the same name for their dependent property, but it doesn't solve the issue I have.

My view displays the list of ICustomInput by simply:

@Html.EditorFor(model => model.CustomInputFields)

Then each concrete type that implements ICustomInput has it's own editorTemplate similar to:

<div class="columnPositioner">
    <div class="inputContainer">
        @Html.TextBoxFor(model => model.DataValue, new
   {
       @class = "inputFields input-lg form-control",
       placeholder = Model.Field.Display
   })
        <span class="inputLabel">
            @Html.LabelFor(model => model.Field.Display, Model.Field.Display)
        </span>
        @Html.ValidationMessageFor(model => model.DataValue, "", new { @class = "text-danger" })
        @Html.HiddenFor(model => model.DataValueRequired)
    </div>
</div>

The resulting HTML looks like:

<select name="CustomInputFields[0].DataValue" class="inputFields input-lg form-control" id="CustomInputFields_0__DataValue" data-val="true" data-val-requiredif-operator="EqualTo" data-val-requiredif-dependentvalue="True" data-val-requiredif-dependentproperty="DataValueRequired" data-val-requiredif="This is a required field"><option value="">TEST01</option>
<option value="01">01</option>
<option value="02">02</option>
<option value="03">03</option>
</select>
<input name="CustomInputFields[0].DataValueRequired" class="hasContent" id="CustomInputFields_0__DataValueRequired" type="hidden" value="True" data-val-required="The DataValueRequired field is required." data-val="true">
<input name="CustomInputFields[1].DataValue" class="inputFields input-lg form-control" id="CustomInputFields_1__DataValue" type="text" placeholder="TEST02" value="" data-val="true" data-val-requiredif-operator="EqualTo" data-val-requiredif-dependentvalue="True" data-val-requiredif-dependentproperty="DataValueRequired" data-val-requiredif="This is a required field">
<input name="CustomInputFields[1].DataValueRequired" id="CustomInputFields_1__DataValueRequired" type="hidden" value="False" data-val-required="The DataValueRequired field is required." data-val="true">

The hidden field is named properly, but how can I get the attribute to set the data-val-requiredif-dependentproperty to the actual id/name on the hidden field?

I do not currently have a custom editor template for the List. I did have one, but couldn't get it to bind the data back correctly. Dropping the editor template on the List and building unique editor templates for the concrete implementations of ICustomInput gave me all the UI layout control I needed and bound the data correctly, but now I can't get the client side validation to work properly. If it's just a editor template, what might that look like?

Update

This is A fix, but I don't like it. I have a javascript that's already doing an .each through inputs to apply styles so I added this to the .each:

function requiredIfHack($input) {
    var depPropVal = $input.data("val-requiredif-dependentproperty");
    //return if the value exists
    if ($("#" + depPropVal).length) return;

    //it doesn't. it's missing the parent object name
    var parentName = $input.attr("name").split(".")[0].replace("[", "_").replace("]", "_");
    $input.data("val-requiredif-dependentproperty", parentName + "_" + depPropVal);
}

It solves the problem, but I don't think it should be a problem that is the js responsibility to solve. And since it's a pretty sneaky fix, it could trip up others trying to work on this code in the future. I still want to find a better way to do it.

MitchelWB
  • 619
  • 1
  • 7
  • 18
  • @Stephen Muecke - I added my editor templates in to the original post as well as cleaned up my actual question. The hidden that contain the dependant property value are named correctly, it's only the data-val-requiredif-dependentproperty on the input that has the wrong value. – MitchelWB Aug 15 '17 at 12:31
  • That all look good. Are you sure you have `mvcfoolproof.unobtrusive.js` included in the view? –  Aug 15 '17 at 12:39
  • yeah, it's there and working for other fields in the ViewModel (like DayPhone). Are you saying that the data-val-requiredif-dependentproperty on the select field looks correct? It's value is set to "DataValueRequired" because that's what the attribute in the concrete type is set to. That works in the binding because it's correct in scope, but in the rendered HTML that field doesn't exist. The same problem exists on the text input CustomInputFields[1].DataValue as well. – MitchelWB Aug 15 '17 at 12:45
  • It all looks good (foolproof calls its `getName` to get the corresponding dependent property - i.e. with the same prefix). Its late here but I'll test this in the morning. –  Aug 15 '17 at 12:54
  • I have tested this and it all works fine (it generates the identical html you have shown and I get the error message if the value of `DataValueRequired` is `false`). You should not have to make the modification in your edit. All I can assume is you do not have the correct foolproof script included (there are 3 of them, and you must include only `mvcfoolproof.unobtrusive.js`) –  Aug 16 '17 at 02:56
  • Ok, I'm seeing this now. I assumed that this conversion was something that would have happened server-side. So you're right, I don't need to do my hack function. It's all good now. Thanks for steering me in the right direction! – MitchelWB Aug 16 '17 at 21:23

0 Answers0