How to get unobtrusive jquery remote validator to perform async..?

danludwig picture danludwig · Oct 20, 2011 · Viewed 8.3k times · Source

In an MVC3 app, using jquery unobtrusive validation, and a view/model with a [Remote] validator: I am trying to disable the submit button and display a wait icon during remote validation, and when a valid form is submitted to the server. I thought I had it nailed until I tried it in IE8.

The problem was, GC and FF were not firing the form's submit event when the form was invalid, so I just disabled the submit button during this event. However IE8 is firing this event when the form is invalid, causing the user to never be able to click it again. (IE8 does not submit the form, but the event is fired.)

I tried attaching a function to the submit button's click event. In there, I disabled the submit button, showed the wait icon, and had this:

$('[data-app-form-submit-button="true"]').live('click', function (e) {
    var form = $(this).parents('form');
    var icon = form.find('[data-app-form-submitting-icon="true"]');
    icon.show();
    $(this).attr('disabled', 'disabled');
    $.ajaxSetup({ async: false });
    var isValid = form.valid();
    $.ajaxSetup({ async: true });
    alert(isValid);
});

The problem is, the ajax setup call is not really turning off async. It does if I move it out of the click function, but then it disables async for everything. Instead, the page alerts "true" immediately, tested by setting a breakpoint on the remote validation action method.

Any ideas?

Additional Note:

I forgot to mention, in IE8, the submit event is only being fired when the text box in question fails a validation that can happen on the client. For example, if it fails required or regex, submit() is fired. For the remote validation action method, it is not fired. However, as soon as it fails a client validation, subsequent remote validations also trigger the IE8 submit event.

Response to Russ Cam (comment #1)

Here is the relevant code in the viewmodel:

public class SignUpForm : IValidatableObject
{
    [DataType(DataType.EmailAddress)]
    [Display(Name = "Email Address")]
    [Required(ErrorMessage = "Email Address is required.")]
    [RegularExpression(@"^(email regex here)$",
        ErrorMessage = "This is not a valid email address.")]
    [Remote("Validate", "ControllerName", "AreaName", HttpMethod = "POST")]
    public string EmailAddress { get; set; }

    public IEnumerable<ValidationResult> Validate(
        ValidationContext validationContext)
    {

I'm glad you had me look at the rendered <form>. The form tag and input elements look like this:

<form action="/post-action-method" method="post" novalidate="novalidate">
...
<input class="text-box single-line" data-app-focus="true" data-val="true" 
    data-val-regex="This is not a valid email address." 
    data-val-regex-pattern="^(email regex here)$" 
    data-val-remote="&amp;#39;Email Address&amp;#39; is invalid." 
    data-val-remote-additionalfields="*.EmailAddress" 
    data-val-remote-type="POST" 
    data-val-remote-url="/validate-action-method" 
    data-val-required="Email Address is required." 
    id="EmailAddress" name="EmailAddress" type="text" value=""> 
 ... 
<input type="submit" value="Submit this form" 
    data-app-form-submit-button="true" />

I never saw the novalidate="novalidate" attribute until now. Here is what it looks like in the cshtml file:

@using (Html.BeginForm())
{
    @Html.EditorForModel()
    @Html.AntiForgeryToken("assault")
}

I am also using anti-forgery token, if that makes a difference. Thanks.

Answer

counsellorben picture counsellorben · Oct 20, 2011

It's kind of hacky, but I would suggest trying the following.

First, hide the submit button with display="none", and show your own "submit" button, which runs your script above.

Second, in your page, add a flag var [var remotePending = false;] and a variable for a setInterval call [var intervalPending;], and in your script, set the flag to true, then call the following function using intervalPending = setInterval('remoteCheck()', 200);

function remoteCheck() {
    if ($.validator.pendingRequest == 0) {
        // requests are done
        // clear interval
        clearInterval(intervalPending);
        // re-enable our "submit" button

        // "click" the hidden button
        $("#hiddenSubmit").click();
    }
    // we will try again after the interval passes
}

This will allow you to wait for the completion of the pending remote validation, then proceed normally. I have not tested, so you may have to play around to get this working as you want.