Feb 3, 2011

jQuery.validation breaks jQuery 1.5 ajax API

Today I found out (the hard way) that the use of the jQuery.validation plugin breaks the jQuery.ajax API. Since the Asp.net MVC 3 uses jQuery.validation for its unobtrusive validation it is included in the default project template. You can imagine my surprise when I updated jQuery to v1.5, and my heavy ajaxified MVC page stopped working correctly. Every ajax request contained an parseerror with an error message stating that the callback jQuery[some random string] was not called. I tried to fiddle with the ajax settings for JSONP (namely jsonp and jsonpCallback), but no matter how I set them, the error was still present.

error_example
Error message

So I started digging around, trying to replicate the problem. When I ran simple html pages $.ajax worked as expected. Then I started to gradually test all the jQuery plugins and found out that the plugin causing the error was jQuery.validation. So here is my reproduction and a simple workaround (the whole project is available here):

The controller:

using System.Linq;
using System.Web.Mvc;

public partial class DefaultController : Controller
{
  private static readonly int[] numbers = Enumerable.Range(1, 10).ToArray();

  public virtual ActionResult Index()
  {
    return View();
  }

  [HttpPost]
  [ValidateAntiForgeryToken]
  public virtual JsonResult SimpleArray(int id)
  {
    return Json(numbers.Concat(new [] { id }));
  }
}

The view:

@{ Layout = null; }
<!DOCTYPE html>
<html>
<head>
<title>Index</title>
  <script src="/Scripts/jquery-1.5.js" type="text/javascript"></script>
  <script src="/Scripts/jquery.validate.js" type="text/javascript"></script>
</head>
  <body>
  <script type="text/javascript">
    $(function () {
      $('a').click(function (e) {
        var req = $.ajax({
            url: '@Url.Action(MVC.Default.SimpleArray())',
            type: 'POST',
            data: $('form').serializeArray(),
            dataType: 'json'
        });
        req.success(function (response, status, xhr) { alert('Success: ' + response); });
        req.error(function (xhr, error, msg) { alert('Error "' + error + '": ' + msg); });
      });
    });
  </script>
  <a href="javascript:void(0);">Do request</a>
  <form action="#">
    <input type="hidden" value="-1" name="id" />
    @Html.AntiForgeryToken()
  </form>
</body>
</html>

The workaround:

$(function () {
$.ajaxSettings.cache = false;
$.ajaxSettings.jsonp = undefined;
$.ajaxSettings.jsonpCallback = undefined;
})


The cause of the problem is this line of JavaScript in jQuery.validate.js, that overrides the settings you pass into the $.ajax call with all the default ones (and jQuery.ajaxSettings defaults to { jsonp: "callback", jsonpCallback: function() {...}}):


// create settings for compatibility with ajaxSetup 
settings = $.extend(settings, $.extend({}, $.ajaxSettings, settings));

Cheers!

1 comment:

Julian said...

Nullifying the jsonp and jsonpCallback properties of ajaxSettings will break jsonp.

I forked jQuery validation and made a patch to make it compatible with jQuery 1.5 by feature-detecting if jQuery supported ajax prefilters and using them when available.

Project page: https://github.com/jaubourg/jquery-validation

Full text script: https://github.com/jaubourg/jquery-validation/raw/master/jquery.validate.js

Minified script: https://github.com/jaubourg/jquery-validation/raw/master/jquery.validate.min.js