Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AJAX form (using simple_form) with preserved error validation

This might be one of the more difficult questions I've asked, but I have been stuck for a few days, and I thought it'd be best to get support from the more experienced rails community.

I am working on a dummy app that follows this guide roughly... ajax/jquery tutorial...

Making my own customizations, the guide helped me to create a "brand" (includes nested models "model", "submodel", and "style") submission form (using simple_form) and brands listing on the same page.. This works perfectly, and the validation (for my models) is enforced.. However, when I put the form and brand listing on the same page I lose the neat inline validation errors that were appearing on submission failure (see image below). Not being able to get the inline errors to work has made me realize that I have more to learn about rendering.. and that is why I am fixated on finding the answer to this question

enter image description here

...and here is the listing:

enter image description here

Below is the controller for the index action:

  def index

    #This is for the product listing

    @brands = Brand.all.reverse 

    #This is for the form   

    @brand = Brand.new
    @model = Model.new
    @submodel = Submodel.new
    @style = Style.new

    respond_to do |format|
      format.html
    end
  end

The above form creates the brand, model, submodel, and style for use in the nested submission form... Below is the code for the form:

<%= simple_form_for @brand, :html => { :class => 'form-horizontal' }, :remote => true do |m| %>

  <fieldset style = "margin-top:34px;">
    <legend></legend>
    <%= m.input :name, :label => 'Brand' %>         
    <%= m.simple_fields_for :models, @model do |p| %>    
      <%= p.input :name, :label => 'Model' %>
      <%= p.simple_fields_for :submodels, @submodel do |s| %>
        <%= s.input :name, :label => 'Submodel' %>
        <%= s.simple_fields_for :styles, @style do |ss| %>
          <%= ss.input :name, :label => 'Style' %>
        <% end %>
      <% end %>
    <% end %>

    <div class="form-actions">
      <%= m.submit nil, :class => 'btn btn-primary' %>
      <%= link_to 'Cancel', brands_path, :class => 'btn' %>
    </div>
  </fieldset>
<% end %>

Now as you can see I am using :remote => true ... I would like for the form to either create the new "brand", or for the form to reload with inline validation errors. At the moment my create action looks like:

def create
    @brand = Brand.new(params[:brand])

    respond_to do |format|
      if @brand.save
        format.html { redirect_to brands_url, notice: 'Brand was successfully created.' }
        format.json { render json: @brand, status: :created, location: @brand }
        format.js
      else
        format.html { render action: "index" }
        format.json { render json: @brand.errors, status: :unprocessable_entity }
        format.js { render 'reload' }
      end
    end
  end

The code that appears under if @brand.save seems to work.. but the code below "else" doesn't work the way I'd like. So what is happening when @brand does NOT save? I believe that the code within the index action is being run, then @brand.errors is converted to json (which I assume is for the simple_form validation errors), and then reload.js.erb is being run..

In an attempt to reload the form (with validation errors) I have put the line $("#entryform").load(location.href+" #entryform>*",""); into reload.js.erb ... When I put invalid data into my form reload.js.erb is being called, but all that happens is that the form fields reload blank, instead of having the data entered and inline validation errors.

I hope I have provided enough info here to get help.. Really struggling on this one. Thanks!

like image 541
Abram Avatar asked Apr 07 '12 01:04

Abram


2 Answers

I think you can simplify this by putting your form in a partial (we'll call it simple_form), and putting this code in reload.js.erb:

$("#entryform").html("<%= escape_javascript(render :partial => 'simple_form') %>");

I'm not familiar with jQuery's load method, but I assume it does a second request. The key with rendering errors is the instance variable (@brand) you're using while rendering the form has to be the same one you tried to save in create, so you can check the errors on it. This is why you always use render instead of redirect_to when you want to render errors. If you do a redirect, you start a new request, meaning @brand is reinitialized. I'm assuming your $(...).load(...) is having the same issue.

like image 147
tsherif Avatar answered Nov 10 '22 03:11

tsherif


Thanks for all the Q&A on this one. It saved me.

Simplifying @tsherif's file structure one notch (assuming you already have a js file), I solved this issue by called render directly on the form and showing it via javascript/coffeescript.

#immediate_controller
def action_with_modal
  @brand = params[:failed_brand] || Brand.new
end

#brands_controller
def create
  @brand = Brand.new(params[:brand]) 

  respond_to do |format|
    if @brand.save
      ...
    else
      params[:failed_brand] = @brand
      ...
      format.js { render 'simple_form' }
    end
  end
end

#coffeescript
$('form-selectors-here').on 'ajax:error', @reRegister

reRegister: (e, data) =>
  $("div-that-contains-form").html(data.responseText)
like image 44
steel Avatar answered Nov 10 '22 01:11

steel