How to get unobtrusive jquery remote validator to

2019-04-07 09:56发布

问题:

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.

回答1:

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.



回答2:

I'm a bit late to the table with this, but I stumbled across this question while trying to solve a similar problem. I too didn't want to use Interval, so I cooked up a different solution and thought it might be worth detailing here for others.

In the end I decided to override the jQuery validator startRequest and stopRequest methods in order to add my own logic. After executing my own logic I simply call the old validator methods.

// Extend jQuery validator to show loading gif when making remote requests
var oldStartRequest = $.validator.prototype.startRequest;
$.validator.prototype.startRequest = function (element) {
    var container = $('form').find("[data-valmsg-for='" + element.name + "']");
    container.addClass('loading');       

    oldStartRequest.apply(this, arguments);
};

var oldStopRequest = $.validator.prototype.stopRequest;
$.validator.prototype.stopRequest = function (element) {
    var container = $('form').find("[data-valmsg-for='" + element.name + "']");
    container.removeClass('loading');

    oldStopRequest.apply(this, arguments);
};

All I wanted to do for my purposes was simply add a 'loading' css class to an adjacent validation message field in order to show a loading gif, but you could easily extend this to enable/disable a button, etc.



回答3:

I found myself puzzling over this last night and after trying various approaches I ended up with an evolution of counsellorbens. Since counsellorbens didn't work straight out of the box I thought I'd share it to prevent others having the same headache. It's not as "clean" a solution as I would like (I really hate using intervals). But it seems to suffice.

var iIntervalId = null;

//
// DECLARE FUNCTION EXPRESSIONS
//

//==============================================================================
// function that triggers update when remote validation completes successfully
//==============================================================================
var fnPendingValidationComplete = function () {

  var validator = $("#frmProductType").data("validator");
  if (validator.pendingRequest === 0) {

    clearInterval(iIntervalId);

    //Force validation to present to user (this will not retrigger remote validation)
    if ($("#frmProductType").valid()) {

      alert("valid - you can submit now if you like");

    }
    //else { alert("invalid"); }
  }
  //else { alert("NOT YET"); }
},

//==============================================================================
// Trigger validation
//==============================================================================
fnTriggerValidation = function (evt) {

  //Remove any cached values to ensure that remote validation is retriggered
  $("#txtProductType").removeData("previousValue");

  //Trigger validation
  $("#frmProductType").valid();

  //Setup interval which will evaluate validation (this approach because of remote validation)
  iIntervalId = setInterval(fnPendingValidationComplete, 50);
};

fnTriggerValidation();


回答4:

I ended up solving this, but the button disable / wait image show only happen when there is a valid submitted form. It's okay though, since multiple invalid form submissions are idempotent, the user can click as much as they want. (Also, the remote validation action method is cached against its parameter values.)

$(function () {
    $('form').bind('invalid-form.validate', function () {
        $('form').each(function () {
            $(this).find(
                '[data-app-form-submitting-icon="true"]').hide();
            var button = $(this).find(
                'input[type="submit"][data-app-form-submit-button="true"]');
            setTimeout(function () {
                button.removeAttr('disabled');
            }, 1);
        });
    });
    $('form').live('submit', function (e) {
        $(this).find('[data-app-form-submitting-icon="true"]').show();
        var button = $(this).find(
            'input[type="submit"][data-app-form-submit-button="true"]');
        setTimeout(function () {
            button.attr('disabled', 'disabled');
        }, 0);
    });
});

This is a little hacky too, but when I didn't use setTimeout, the submit action was never firing the invalid handler.