3

I'm having a hard time trying to make jquery.form with a cross-domain request. I'm having issues with Firefox and Chrome (didn't even try IE yet).

Explanation: my whole site is located inside http://www.mysite.com. However, my contact form is on another server, referenced by http://contact.mysite.com . I thought that putting it on a subdomain would sidestep the issues regarding cross-domain requests, but apparently it didn't. http://contact.mysite.com is implemented in Sinatra.

My javascript setup is nothing fancy. The form's action points to http://contact.mysite.com and the method is POST:

<form id="contact" action="http://contact.mysite.com/" method="post">

jquery.form is configured with an ajaxForm call:

$(document).ready(function() {

  $('#contact').ajaxForm({
    success: function() { $('#success').fadeIn("slow"); },
    error: function() {  $('#error').fadeIn("slow"); }
  });

});

The first problem I encountered was with Firefox 3.5 - apparently it sends an OPTIONS request expecting an specific answer from the server. I used this question to configure my Sinatra app so it did what was expected (it seems that more recent versions of sinatra include an options verb):

require 'rubygems'
require 'sinatra'
require 'pony'

# patch sinatra so it handles options requests - see https://stackoverflow.com/questions/4351904/sinatra-options-http-verb
configure do
  class << Sinatra::Base
    def options(path, opts={}, &block)
      route 'OPTIONS', path, opts, &block
    end
  end
  Sinatra::Delegator.delegate :options
end

# respond to options requests so that firefox can do cross-domain ajax requests
options '/' do
  response['Access-Control-Allow-Origin'] = '*'
  response['Access-Control-Allow-Methods'] = 'POST'
  response['Access-Control-Max-Age'] = '2592000'
end

post '/' do
  # use Pony to send an email
  Pony.mail(...)
end

With jquery 1.4.3, I saw on firebug an OPTIONS request followed by a POST request (status 200. The email was sent). With jquery 1.3.2 or 1.5, only the OPTIONS request was shown (the email was not sent).

Nevertheless, the error callback is always fired with all versions of jquery I tried. I traced that down to the $.ajax(...) call, so I'm not sure of whether this problem comes from jquery.form or jquery itself.

I tried logging out the information coming from the error:

$('#contact').ajaxForm({
  success: function() { $('#success').fadeIn("slow"); },
  error: function(jqXHR, textStatus, errorThrown) {
    console.log(jqXHR.status);
    console.log(jqXHR.statusText);
  }
}); 

Output on jquery 1.4.3 (after the OPTIONS & POST requests are sent, both with status 200):

0
(empty string)

Output on jquery 1.5 (after OPTIONS returns with a 200 status; POST is never sent)

302
error

I'm really lost here.

  • Is there a plugin that handles this?
  • Am I missing something somewhere?

Any help will be greatly appreciated.

Community
  • 1
  • 1
kikito
  • 51,734
  • 32
  • 149
  • 189

4 Answers4

8

AJAX requests cannot be executed cross-domain (UPD: not true anymore, all modern browsers support CORS), but you can use JSONP instead. Although JSONP works cross-domain, it can't be used for POST requests, and you'll need to change you form's method to get and use this:

$('#contact').ajaxForm({
  success: function() { $('#success').fadeIn("slow"); },
  error: function() {  $('#error').fadeIn("slow"); },
  dataType: 'jsonp'
});

The solution above relies on your server responding with a valid jsonp response, otherwise success handler won't be executed. e.g: response.write(request.callback + '(' + result.to_json + ')')


Latest versions of jQuery can serialize forms without the ajaxForm plugin. If you don't need file uploads you can use this:

$('form').submit(function() {
  var url = $(this).attr('action')
  var params = $(this).serialize()
  $.getJSON(url + '?' + params + "&callback=?", function(data) {
    // success
  })
  return false
});
Alexey Lebedev
  • 11,988
  • 4
  • 39
  • 47
  • Thanks for answering. I'm still trying to wrap my head around this jsonp business. One question thought: $.ajax seems to be able to do jsonp requests (or so it says in the documentation, I'm still not sure of how it works). Since jquery.form uses $.ajax internally, wouldn't it be able to use jsonp too? – kikito Feb 22 '11 at 08:53
  • You know what, I looked at the $.ajaxForm source and I think it can do it. I can't try the example now, but I hope it works. – Alexey Lebedev Feb 22 '11 at 09:01
  • 1
    See my new update, I tested and it works fine if you return a valid json response from your server. – Alexey Lebedev Feb 22 '11 at 09:22
  • Hi Alexeley, thanks for your update. Will try on my server this evening. One question though - you mention something about jsonp only working with GET requests. Is that true? I didn't find any references on that. – kikito Feb 22 '11 at 09:53
  • 1
    JSONP works by adding a – Alexey Lebedev Feb 22 '11 at 10:08
  • 1
    Your solution works beautifully. I ended up ditching jquery.form and using getJSON directly (you need to replace the `+ '/' +` by `+ '?' +` though; I've changed that in your answer) – kikito Feb 26 '11 at 02:26
  • What if you DO need this for file uploads? Having the same issue, but with a form where you upload a file. Can't seem to get it to work cross-domain. – Whatevo Nov 25 '13 at 01:21
  • @Whatevo: If you have control over the receiving server you can set a special header allowing cross-domain requests, and `$.ajaxForm` should be able to work. http://www.html5rocks.com/en/tutorials/cors/ and http://www.kendoui.com/blogs/teamblog/posts/11-10-03/using_cors_with_all_modern_browsers.aspx – Alexey Lebedev Nov 25 '13 at 17:47
1

I think JSONP the only AJAX request which can cross domain.

http://en.wikipedia.org/wiki/JSON#JSONP

Mark Holland
  • 846
  • 4
  • 8
  • Thanks for your answer. It is not a complete answer, but at least gives me a hint. I'll investigate JSONP. – kikito Feb 22 '11 at 08:53
1

You can also use a local proxy URL to perform the request as servers can generally make cross-domain calls using something like HttpRequest or cURL. So basically you make a call using ajax to a URL on the local domain and then forward the request to the cross-domain URL and pass the response from the HttpRequest/cURL back to the browser in the response from the local domain.

Rob
  • 10,004
  • 5
  • 61
  • 91
  • That is not an option with my current setup. The server serving the main app is out of my control. Thanks for your answer anyway. – kikito Feb 22 '11 at 09:29
1

After lots of fighting, I finally ended up conquering this, with the help of Alexey. Here's my solution, for now:

Javascript (using jquery directly, without jquery.form):

$(document).ready(function() {
  $('#contact').submit(function() {
    $('#success').fadeOut("slow");
    $('#bademail').fadeOut("slow");

    var url = $(this).attr('action')
    var params = $(this).serialize()
    $.getJSON(url + '?' + params + "&callback=?", function(data) {
      if(data == true) { // success
        $('#success').fadeIn("slow");
        $('#contact')[0].reset();
      } else { // error
        $('#bademail').fadeIn("slow");
      }
    });

    return false;
  });
});

With Sinatra, I used the sinatra-jsonp gem. I make the get action return "true" or "false" depending on whether the emails can be sent or not (for example, for an invalid email address).

require 'rubygems'
require 'sinatra'
require 'sinatra/jsonp'
require 'pony'


get '/' do

  # check for blanks, etc
  return jsonp false unless fields_valid(params)

  Pony.mail(
    ...
  )

  return jsonp true

end
kikito
  • 51,734
  • 32
  • 149
  • 189