Avir94 Avir94 - 2 months ago 8
jQuery Question

Rails: creating a form_for for a belongs_to association within a bootstrap modal

In my rails app, I have a has_many/belongs_to association between Costs/Cost_Dependencies

The user is able to make a new cost which then gets viewed via the index action, nothing special. But I'm trying to add the ability to create a Cost_dependency for the cost after the cost has been created. I have all the pieces in place, I just need help with the execution within a bootstrap modal (for the sake of efficiency).

Within the Index view of my site, the user sees a table of all the costs:
costs_table

Underneath the dependencies column, I want the user to be able to click the + button and a modal to pop up with the form.

Here is what I have in place so far:
My routes give me this:

cost_cost_dependencies GET /costs/:cost_id/cost_dependencies(.:format) cost_dependencies#index
POST /costs/:cost_id/cost_dependencies(.:format) cost_dependencies#create
new_cost_cost_dependency GET /costs/:cost_id/cost_dependencies/new(.:format) cost_dependencies#new
edit_cost_cost_dependency GET /costs/:cost_id/cost_dependencies/:id/edit(.:format) cost_dependencies#edit
cost_cost_dependency GET /costs/:cost_id/cost_dependencies/:id(.:format) cost_dependencies#show
PATCH /costs/:cost_id/cost_dependencies/:id(.:format) cost_dependencies#update
PUT /costs/:cost_id/cost_dependencies/:id(.:format) cost_dependencies#update
DELETE /costs/:cost_id/cost_dependencies/:id(.:format) cost_dependencies#destroy


So I obviously have the ability to create this thing through the costs. (I'm trying to avoid having a separate controller for the cost_dependencies if I can, but if not, let me know.)

Within the index for costs view, I have these pieces:

<div class="btn-group" style="margin:0; height:100%; float: center !important">
<%= link_to '<i class="glyphicon glyphicon-eye-open"></i>'.html_safe, "/costs/#{cost.id}/cost_dependencies", class: 'btn btn-warning' %>
<%= link_to '<i class="glyphicon glyphicon-plus"></i>'.html_safe, "/costs/#{cost.id}/cost_dependencies/new", {:remote => true, 'data-toggle' => "modal", 'data-target' => '#modal-window', :class => 'btn btn-success'} %>
</div>


and

<div id="modal-window" class="modal fade" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<%= render 'costs/new_cost_dependency' %>
</div>
</div>
</div>


I do plan on implementing a modal for viewing the dependencies as well, but I figure getting this question answered will help me with that.

Then for the modal-body within the partial:

<div class="modal-body">
<%= form_for @cost_dependency, :html => {:class => "form-group"} do |f| %>
Blah Blah form stuff looking like <%= f.label ... %> and <%= f.text_field ... %>
<% end %>
</div>


The piece I'm stuck on is how to instantiate the @cost_dependency variable within the form through the database_association in the controller because I have been receiving a

First argument in form cannot contain nil or be empty


Error upon trying to reload the page.

Any help to achieve this would be very helpful.
Thank you very much

Edit: Here is my cost controller:

def index
@costs = Cost.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: @costs }
end
end

def show
end

def new
@cost = Cost.new
@new = true

respond_to do |format|
format.html # new.html.erb
format.json { render json: @cost }
end
end

def edit
@edit = true
@cost = Cost.find(params[:id])
end

def create

@cost = Cost.new(cost_params)

if Cost.exists?(:category => @cost.category, :option => @cost.option)
redirect_to action: 'update', id: Cost.where(:category => @cost.category, :option => @cost.option).first.id
else
respond_to do |format|
if @cost.save
format.html { redirect_to action: 'index', status: 303, notice: [true, 'Cost was successfully created.'] }
format.json { render json: @cost, status: :created, location: @cost }
else
format.html { render action: "new" }
format.json { render json: @cost.errors, status: :unprocessable_entity }
end
end
end
end

def update
@cost = Cost.find(params[:id])

respond_to do |format|
if @cost.update_attributes(cost_params)
format.html { redirect_to action: 'index', status: 303, notice: [true, 'Cost was successfully updated.'] }
format.json { head :no_content }
else
format.html { render action: "edit" }
format.json { render json: @cost.errors, status: :unprocessable_entity }
end
end
end


So I realize that my
@cost_dependency
variable is not here. If I put it in new, it wouldn't get called because we are on the
index
action currently. So help with that regard would also be helpful.

In the time since I've posted this question I did make a
CostDependency
Controller with similar functionality.

Edit2: So I have kept trying new things and I have gotten a bit further, but stuck again.

I have a new index page:

<tbody>
<% @costs.each do |cost| %>
<tr>
<td style="vertical-align:middle !important"><%= cost.category %></td>
<td style="vertical-align:middle !important"><%= cost.option %></td>
<td style="vertical-align:middle !important"><%= '$'+cost.per_job.to_s %></td>
<td style="vertical-align:middle !important"><%= '$'+cost.per_page.to_s %></td>
<td style="vertical-align:middle !important">
<div class="btn-group" style="margin:0; height:100%; float: center !important">
<%if Cost.where(:id => cost.id)[0].cost_dependencies.empty? %>
<span class="btn btn-danger" data-toggle="popover" data-trigger="hover" data-placement="top" data-html="true" data-content="<h5 style='color: black;'>No Dependencies for this Cost</h5>"><i class="glyphicon glyphicon-eye-close"></i></span>
<% else %>
<%= link_to '<i class="glyphicon glyphicon-eye-open"></i>'.html_safe, "", {:remote => true, 'data-toggle' => "modal", 'data-target' => '#modal-window', :class => 'btn btn-warning', :cost => cost.id} %>
<% end %>
<%= link_to '<i class="glyphicon glyphicon-plus"></i>'.html_safe, "", {:remote => true, 'data-toggle' => "modal", 'data-target' => '#modal-new-dependency', :cost => cost.id, :class => 'btn btn-success'} %>
</div>
</td>
<td style="vertical-align:middle !important">
<div class="btn-group" style="margin:0; height:100%; float: center !important">
<%= link_to '<i class="glyphicon glyphicon-pencil"></i>'.html_safe, "/costs/#{cost.id}/edit", class: 'btn btn-info'%>
<%= link_to '<i class="glyphicon glyphicon-trash"></i>'.html_safe, cost, method: :delete, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger' %>
</div>
</td>
</tr>
<div id="modal-window" class="modal fade" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<%= render 'costs/dependency_index', :cost => Cost.where(:id => cost.id)[0] %>
</div>
</div>
</div>
<div id="modal-new-dependency" class="modal fade" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<%= render 'costs/new_cost_dependency', :cost => Cost.where(:id => cost.id)[0] %>
</div>
</div>
</div>
<% end %>
</tbody>


This doesn't throw an error, but when I click on the
+
I am receiving only the first Cost, in this case
Size - 8.5" x 11"
and my log blows up as though it is trying to select all of them, which would make sense slightly because it is in the
.each
loop.

But if I pull it out, I get an
undefined variable
error for the
cost.id
because it doesn't exist outside the scope

Answer

Okay, I figured out this beast. Stick with me as I answer.

ajax is your best friend here.

So: first, change the link_to for the new (plus) method to a button:

<button class="btn btn-success" data-toggle="modal" data-target="#new-dependency-modal-window" id="newdependency">
    <%= hidden_field_tag "cost_id", cost.id %>
    <span class="glyphicon glyphicon-plus"></span>
</button> 

Like this. Notice there is no link at all to the /cost/:id/cost_dependencies/new that will come into play later.

below the final div (at the end of your file), add this code for the existence of the modal

<div id="new-dependency-modal-window" class="modal fade" role="dialog" aria-labelledby="myNewDependencyModalLabel" aria-hidden="true">
    <div class="modal-dialog" role="document" id="new_cost_dependency_modal" style="width: 85%">

    </div>
</div>

next, create a controller for the Cost Dependencies, and add the normal methods:

class CostDependenciesController < ApplicationController

  def new
    @cost = Cost.find(params[:cost_id])
    @cost_dependency = CostDependency.new
    respond_to do |format|
        format.html # new.html.erb
        format.json { render json: @cost_dependency }
    end
  end

  def edit
    @edit = true
    @cost = Cost.find(params[:cost_id])
    @cost_dependency = @cost.cost_dependencies.find(params[:id])
  end

  def index

  end

  def create
    @cost = Cost.find(params[:cost_id])
    @cost_dependency = @cost.cost_dependencies.new(cost_dependencies_params)
    respond_to do |format|
      if @cost_dependency.save
        format.html { redirect_to controller: 'costs', action: 'index', status: 303, notice: [true, 'Cost Dependency was successfully created.'] }
        format.json { render json: @cost_dependency, status: :created, location: @cost }
      else
        format.html { render action: "new" }
        format.json { render json: @cost_dependency.errors, status: :unprocessable_entity }
      end
    end
  end

  def update
    @cost = Cost.find(params[:cost_id])
    @cost_dependency = @cost.cost_dependencies.find(params[:id])

    respond_to do |format|
      if @cost_dependency.update_attributes(cost_dependencies_params)
        format.html { redirect_to controller: 'costs', action: 'index', status: 303, notice: [true, 'Cost was successfully updated.'] }
        format.json { head :no_content }
      else
        format.html { render action: "edit" }
        format.json { render json: @cost.errors, status: :unprocessable_entity }
      end
    end
  end

def destroy
  @cost = Cost.find(params[:cost_id])
  @cost_dependency = @cost.cost_dependencies.find(params[:id])
  @cost_dependency.destroy

  respond_to do |format|
    format.html { redirect_to controller: 'costs', action: 'index', status: 303, alert: [true, 'Cost Dependency has been deleted.'] }
    format.json { head :no_content }
  end
end


private
  def cost_dependencies_params
    params.require(:cost_dependency).permit(:cost_id, :dependency_category, :dependency_option, :per_job, :per_page)
  end

end

Notice how in each method there is the @cost = Cost.find(params[:cost_id]) this will grab you the correct Cost for which you can call the @cost.cost_dependencies.new method. This is the reason for the hidden_field tag inside the button tag, to make the parameter findable by jQuery.

From here, the goal is to call the new method from jQuery using an ajax call, which we will then grab the content and shove it inside the modal.

$(document).on "turbolinks:load", ->
    $('button[id="newdependency"]').click ->
        cost_id = $(this).find('#cost_id').val()
        $.ajax({
            url: 'costs/'+cost_id+'/cost_dependencies/new',
            type: 'GET',
            data: {cost_id: cost_id},
            dataType: 'html',
            success: (data) -> 
                dependencyform = $($.parseHTML(data)).find("#new_dependency_modal")
                $('#new_cost_dependency_modal').html(dependencyform)
        })

So we execute once the button with the newdependency id has been clicked, we get the html back from the page load (which will be the entire page, navbar and all. but we labeled the content we want (see new_dependency_modal) so we can find() it. We take that html and set it inside the modal div on the page we are still currently on.

Note: The this is important here, otherwise it will grab the first cost_id it finds on the page, which would not be good, so we localize the scope.

and just for clarity sake, here is my new.html.erb page that gets called.

<div class="modal-content" id="new_dependency_modal">

  <div class="modal-header">
    <button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
    <h2 style="color: #49afcd" class="center-block westmontTextMuseo3">New Cost Dependency for - <%= @cost.category+" - "+@cost.option %></h2>
  </div>
  <div class=modal-body>
    <%= render 'form' %>
  </div>
</div>

Where form is just the form to create the new cost_dependency.

Comments