I have a view with a form, simplified below:
@using (Html.BeginForm("New", "User", FormMethod.Post, new { id = "newUserForm" }))
{
@Html.AntiForgeryToken()
@Html.ValidationSummary("There was a problem saving the user:", new { @class = "text-danger" })
<div class="form-group">
@Html.LabelFor(model => model.RoleID, new { htmlAttributes = new { @class = "form-label" } })
<select class="form-control" name="RoleID" id="RoleID" data-val-required="The Role field is required." data-val-number="The field Role must be a number." data-val="true">
@foreach(RoleOption role in Model.RoleOptions)
{
<option value="@role.ID" data-isdefault="@role.IsADefault" @(role.ID == Model.RoleID ? "selected" : "")>@role.DisplayName</option>
}
</select>
@Html.ValidationMessageFor(model => model.RoleID, "", new { @class = "text-danger" })
</div>
<div class="form-group">
@Html.LabelFor(model => model.Teams, new { htmlAttributes = new { @class = "form-label" } })
@Html.ListBoxFor(model => model.Teams, Model.TeamsOptions, new { @class = "form-control"})
@Html.ValidationMessageFor(model => model.Teams, "", new { @class = "text-danger" })
</div>
<button type="submit" class="btn btn-primary w-100">Save</button>
}
I'm using jQuery validation and I have a configured the validator as follows:
$("#newUserForm").validate({
invalidHandler: function (event, validator) {
var errors = validator.numberOfInvalids();
},
submitHandler: function (form) {
let selectedRole = $("[name='RoleID']").find("option:selected");
let selectedRoleADefault = selectedRole.data("isdefault").toLowerCase() === "true";
let selectedTeams = $("[name='Teams']").val();
if (!selectedRoleADefault && selectedTeams.length === 0) {
$("[data-valmsg-for='Teams']").text("You must select at least one Team");
}
else {
form.submit();
}
}
});
The idea of the submit handler is to do some fairly complicated validation for which I, frankly, couldn't be bothered creating a custom rule, especially since it won't be needed on any other forms. Go with it. The invalid handler is just there to try and help me figure this problem out.
Finally, the relevant excerpt from my view model is:
[Display(Name = "Role")]
[Required]
public int RoleID { get; set; }
I've had to manually add the data-val
attributes to the select
with the name RoleID
because I'm not using the usual Html.DropdownFor
HTML Helper, so as to allow me to add the data-isdefault
attribute to each option
, but I still want that field to be validated.
With the code as it appears above, when I click the submit button on my form the breakpoints I've added in both the invalidHandler
and submitHanlder
functions are never hit. That's the case whether I select a value for RoleID
or not. In other words, whether the form is valid or otherwise, neither callback is ever fired. However, the client-side validation is nevertheless working because when no value is selected the error message for the RoleID
field is displayed. When a value is selected, whilst the callback doesn't fire the form does get posted to the server. I find that very odd.
If I remove the data-val
attributes that I've added to the RoleID
control and submit the form, the breakpoint in the submitHandler
function does fire. To a point that makes sense - having removed the data-val
attributes that field is no longer being validated and so the form is being considered valid. But that just makes it every weirder to me that neither callback is fired when those attributes are present, even though validation does take place.
After further thought, I've just remembered that in a separate Javascript file I have overridden some of the defaults for the validator:
$.validator.setDefaults({
ignore: "hidden:not(.collapse :hidden)",
showErrors: function() {...}
});
I'd forgotten about this, so I've just tried adding a submitHandler
property to that code and, sure enough, that is fired when I submit my form with the data-val
attributes.
It's fairly obvious at this point that I just don't fully understand how to work with jQuery validation (not the first time I've been made aware of this!), but nevertheless I don't understand:
- Why the
submitHandler
for my particular form fires when thedata-val
attributes are missing from theRoleID
control but not when they are present. - How to successfully specify a
submitHandler
function that will be called only when this one particular form is validated, whilst using thedata-val
attributes on myRoleID
control (and still picking up the overridden values in mysetDefaults
call).