Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Display inline errors with simple_form in a Bootstrap Ajax modal

I've found similar StackOverflow questions here and here but still can't get this to work.

I'm using Rails 3.2.8, SimpleForm 2.0.4, and Twitter Bootstrap 2.1.1 (via the bootstrap-sass gem 2.1.1.0).

The user should be able to add a contact from a modal popup window. If there are validation errors, they should appear inline, just as if the user were using a non-modal version of the form (red border around field, error message next to field).

I load the modal like this:

<a data-toggle="modal" data-target="#new-contact-modal">Go modal!</a>

Here is the Bootstrap modal, which calls the same contacts/contact_fields partial used in the non-modal version. app/views/contacts/_new_modal.html.erb:

<div id="new-contact-modal" class="modal hide fade" tabindex="-1" 
     role="dialog" aria-labelledby="new-contact-modal-label" 
     aria-hidden="true">
  <div class="modal-header">
    <button type="button" class="close" data-dismiss="modal" 
     aria-hidden="true">×</button>
    <h3 id="new-contact-modal-label"><%= t("contacts.new.header") %></h3>
  </div>
  <%= simple_form_for(@contact, 
      :remote => true,
      :html => {:class => "form-horizontal", 
                "data-type" => :json }) do |contact_form| %>
    <div id="new-contact-body" class="modal-body">
      <%= render 'contacts/contact_fields', :f => contact_form %>
    </div>
    <div class="modal-footer">
      <%= contact_form.submit :class => "btn btn-primary", 
          :"data-loading-text"=> ('simple_form.creating') %>
      <%= t('simple_form.buttons.or') %>
      <a data-dismiss="modal" aria-hidden="true">
        <%= t('simple_form.buttons.cancel') %>
      </a>
    </div>
  <% end %>
</div>

app/controllers/contacts_controller.rb (intentionally commented out the format.json line since I'm trying to send the whole modal back using JavaScript):

def create
  @contact = Contact.new(params[:contact])
  <...some additional processing...>
  respond_to do |format|
    if @contact.save
      format.html { flash[:success] = "Contact added."
                    redirect_to @contact }
      format.json { render json: @contact, status: :created, location: @contact}
    else
      format.html { render action: "new" }
      #format.json { render json: @contact.errors, status: :unprocessable_entity }
      format.js { render 'new_modal_error' }
    end

app/views/contacts/new_modal_error.js.erb

var modal = "<%= escape_javascript(render :partial => 'contacts/new_modal', :locals => { :contact => @contact.errors }) %>";
$("#new-contact-modal").html($(modal));

app/assets/javascripts/contacts.js Some JQuery to reset the form and close the modal on success.

$(function($) {
  $("#new_contact")
    .bind("ajax:success", function(event, data, status, xhr) {
      // http://simple.procoding.net/2008/11/22/how-to-reset-form-with-jquery :
      $(this).each(function(){
        this.reset();
      });       
      $("#new-contact-modal").modal("hide");
    })
});

The good news is, this works when the form has no errors: the contact is added and the modal is hidden. However if there are validation errors, I get the message "JSON.parse: unexpected character". This comes from jquery.js, line 515, which is the return statement in this snippet:

// Attempt to parse using the native JSON parser first
if ( window.JSON && window.JSON.parse ) {
    return window.JSON.parse( data );
}

If I inspect data, I see that is is the content of my new_modal_error.js file, fully expanded with the form errors, and escaped for JavaScript. But it's not JSON.

What am I missing? How do I get the page to process new_modal_error.js as a JavaScript file, not a JSON variable? Or is there an easier way to handle this altogether?

like image 783
Mark Berry Avatar asked Nov 03 '22 11:11

Mark Berry


1 Answers

The main issue here was calling the form with data-type => :json. That means it will expect a JSON response, but I want it to pass back JavaScript. The solution is to set data-type => :script (or just leave it off and let Rails infer JavaScript):

:html => {:class => "form-horizontal", 
          "data-type" => :script }) do |contact_form| %>

Thanks to this article for explaining the various data-types, and clarifying that data-type refers to the response, not the submission of data:

Rails 3 Remote Links and Forms Part 2: Data-type (with jQuery)

"jQuery’s .ajax() method provides an optional parameter called dataType to specify the desired data-type of the response."

Another issue that I think @wanghq was pointing to is that JQuery's .html() method updates the innerHTML of an object. I wound up creating a partial containing only the form, calling the partial inside a <div id="new-contact-form-wrapper"> wrapper, then targeting the wrapper to replace the form:

var myForm = "<%= escape_javascript(render :partial => 'contacts/new_modal_form', :locals => { :contact => @contact.errors }) %>";
$("#new-contact-form-wrapper").html(myForm);

I am still working on getting variables passed, the form cleared on success, etc. Since I'm using a partial from the controller, I'll probably just go ahead and put all the JQuery (for both success and failure) in there so I won't need app/assets/javascripts/contacts.js.

like image 77
Mark Berry Avatar answered Nov 07 '22 21:11

Mark Berry