Until today I thought the way I use Rails forms + jQuery UJS was the right way to do it, but the upgrade to jQuery 1.7 'broked' the way I did it so far.
I want to use Ajax with my forms. The ajax response should either render the form again (e.g. when errors occur) or redirect to a success page. (It should maybe do more, like showing modals, but I want to keep this example simple).
That is what I did until to date. My ajax response was an ejs template which returned javascript code to render the form again (with errors) or depending if it was an error redirected the user to success page:
<% unless @success_submit %>
$('#form_wrapper').html('<%= escape_javacsript( render :partial => 'form' ) %>');
<% else %>
document.location = '/success_thank_you';
<% endif %>
Now imagine the form contains an error message div like
<div class="error_message">BlaBla</div>
To add a nice effect I had a general jQuery.live event bound on ajax complete which highlighted errors.
$('form').live('ajax:complete', function() {
// do stuff like highlighting all error messages in that form
});
That doesn't work with jQuery1.7 + jquery-ujs anymore (probably by good reasons). So I guess the way I did it was not right.
Instead of binding the ajax:complete event I could do the "error highlighting stuff" in the EJS like
$('#form_wrapper').html('<%= escape_javascript( render :partial => 'form' ) %>');
$('#form_wrapper form .error_message').fadeIn();
But that would mean I would have to repeat the second line in almost each EJS which renders forms. And of course I want to keep it DRY.
A complete different solution would be that the ajax response simply would render pure html and my custom ajax:complete handler would take care of displaying the form. The handler would look like
$('form').live('ajax:success', function(ev, data, status, xhr) {
$(this).html(data);
// and e.g. highlight stuff
$(this).children('.error_message').fadeIn();
});
That would work. But now what should I do if my server decides not to render the form again (e.g. after successful signup) but instead redirect to another url or showing a modal form. The server could respond with something like that
<script>
document.location = '/success.blabla/';
</script>
But is that a good solution ?
Probably a good solution would be to use version 3 but instead of simply replacing the current form with the returned html we could create some custom json protocol. That way we could even let the server respond with stuff like
The ajax:success handler could check if the response is pure html, in that case it would replace the current form with the html code. But if the response is a JSON array it would handle that, e.g. server responds with
{ html: '<%= render :partial => 'something' %>',
show_modal: '<%= render :partial => 'modal_template' %>',
redirect_after_modal: '/login_page'
}
The ajax:success handler would have handle it like
$('form').live('ajax:success', function(ev, data, status, xhr) {
// try parsing json
if (data.json['show_modal') { // show modal code.... };
if (data.json['redirect']) { document.location=data.json['redirect']);...
});
Obviously there are many ways how you handle ajax forms. But how are you doing it, and what is considered best practice with Rails ?
So, I was able to boil your issue down to jquery only, and sure enough, it works in jquery 1.6.4, but not in jquery 1.7.
It seems that if you replace an element in the DOM, and then trigger an event on the jquery object created from the original element, 1.6.4 would still trigger the event in the elements original location and allow it to propagate up the DOM. 1.7, however, will not trigger the element (which makes more sense).
I just did a search through the jquery 1.7 changelog and sure enough, here are the two tickets which rectified this behavior: 9951 and 10489.
To answer your question about what is the best practice for accomplishing something like this, I would say, take control over what order your events fire. jQuery automatically executes JS returned in ajax responses, which means you have no control over when that happens.
The easiest way to modify your code would be to return the HTML partial itself, and then use jquery to replace the form with the returned HTML in the ajax:complete
or ajax:success
/ajax:error
handlers. This way you can be sure that things happen in the exact order you want.
To see how exactly to accomplish this, try reading [my articles]:
Or see the jquery-ujs wiki for these links and more.