Modal does not close properly when the controller calls the format.js

advertisements

I have a modal which pops up as an edit form for an object. It opens and speaks to the controller just fine, but when the action completes, the modal does not close.

Everything works fine when the controller renders a new page, but when the form (which sits in the modal) is set to remote: true, I get problems.

So the modal code goes something like -

boilerplate bootstrap modal markup
id='edit_stream_4'
form_for(@stream, remote: true) do |f|
...etc...
f.submit

... and in the controller I have

def update
  respond_to do |format|
    if @stream.update
      format.js
    ...etc...

... and in /streams/update.js.erb

$('#edit_stream_4').modal("hide");
$('#workspace').html("<%= escape_javascript(render :partial => 'streams/show', stream: @stream) %>");

When I hit the update button in the form in the modal, the record is updated and I can see the partial re-rendering in the background, but the modal remains up.

I guess this is because I'm leaving the modal js when I go into the controller, and that the js I've put in update.js.erb is a bit rubbish and not doing what I want it to?

Can anyone give me some advice as to how to make my javascript do what I want?

Thanks for any advice. I guess what I'm really looking for is advice about an approach to handling what I think are conflicts between what bootstrap is doing with the modal [= click a link to trigger the modal, when you exit the modal jQuery closes it] and what Rails is doing with ajax [= part way through modal cycle, trigger a partial on a div elsewhere on the page using ajax] - because I think by using Rails ajax generation I'm not exiting the modal properly.

Anyway, the code snippets above are presented the way they are because I didn't want to confuse the question with other specifics of the app - but responding to the comments I've got, I've included the full code below.

The app is my learning attempt at a single page todo. The viewport is divided in two - a list of workstreams in the top half, and a list of stream-related tasks in the bottom half. The tasks are rendered as partials, triggered by the user clicking on stream links in the top half.

So my main page is =

container.html.erb

<div id='viewport'>
  <div class='row'>
    <div class='col-md-4'>
      <%= render 'streams/index', streams: @streams %>
    </div>
    <div class='col-md-4'>
      <%= render 'layouts/object_views/index_settings', settings: @settings %>
    </div>
    <div class='col-md-4'>
      <%= render 'layouts/tips' %>
    </div>
  </div>
  <div class='row' id='workspace'>
    <%= render 'layouts/workspace/workspace' %>
  </div>
</div>

When the page first loads, the 'layouts/workspace/workspace' partial just contains an image:

/layouts/workspace/_workspace.html.erb

<div id='workspace'>
    <%= image_tag("WIN1.png", class:'center-block img-responsive') %>
</div>

The streams index contains a list of work streams with some links =

/streams/_index.html.erb

  <table class='table'>
    <thead>
      <tr>
        <th>Streams</th>
      </tr>
    </thead>
    <tbody>
      <% streams.each do |stream| %>
        <tr>
          <td><strong><%= link_to "#{stream.name}", stream, remote: true %></strong></td>
          <td><%= render 'layouts/components/object_modal', modal_params: {modal_action: :edit, modal_object_class: :stream, modal_object_id: stream.id} %></td>
        </tr>
      <% end %>
      <tr>
        <td>
          <%= render 'layouts/components/object_modal', modal_params: {modal_action: :new, modal_object_class: :stream} %>
        </td>
      </tr>
    </tbody>
  </table>

... you can see this partial does a couple of things: it iterates through @streams, and for each one it provides a link which triggers an action on the controller to display a partial - <%= link_to "#{stream.name}", stream, remote: true %>, and passes params to a generic modal: <%= render 'layouts/components/object_modal', modal_params: {modal_action: :edit, modal_object_class: :stream, modal_object_id: stream.id} %>.

The modal looks like this:

layouts/components/_object_modal.html.erb

<a href="#" data-toggle="modal" data-target=<%=modal_data_target(modal_params)%>>
  <span><%=modal_params[:modal_alt_title] || "#{modal_params[:modal_action]} #{modal_params[:modal_object_class]}"%></span>
</a>

<div class="modal fade bannerformmodal" tabindex="-1" role="dialog" aria-labelledby="bannerformmodal" aria-hidden="true" id=<%=modal_id(modal_params)%>>
  <div class="modal-dialog modal-lg">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
        <h4 class="modal-title" id="myModalLabel"><%=modal_title(modal_params)%></h4>
      </div>
      <div class="modal-body">
        <%= render partial: "#{modal_params[:modal_object_class]}s/#{modal_params[:modal_action]}", locals: {id: modal_params[:modal_object_id], parent_id: modal_params[:parent_id]} %>
      </div>
    </div>
  </div>
</div>

All this does is call a couple of helpers to insert the correct id etc. and then call a partial with the actual form in the modal body: <%= render partial: "#{modal_params[:modal_object_class]}s/#{modal_params[:modal_action]}", locals: {id: modal_params[:modal_object_id], parent_id: modal_params[:parent_id]} %>.

This partial unpacks the params which determine the class, action and object id, and call the appropriate form and action. There is a good reason for doing things this way - it will allow the user to group task s in a number of different ways, and is sort of the point of the exercise. So if for example you are calling the edit method on the stream class, you'd get this form:

streams/_edit.html.erb

<%= bootstrap_form_for(stream = Stream.find_by_id(id), remote: true) do |f| %>
  <% if stream.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(stream.errors.count, "error") %> prohibited this stream from being saved:</h2>

      <ul>
      <% stream.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= f.text_field :name %>
  </div>
  <div class="actions">
    <%= f.submit %>
  </div>
<% end %>
<%= button_to 'Delete Stream', stream_path(stream), method: :delete, class: 'btn btn-danger', data: { confirm: "Are you sure?" }  %>

... and this in the controller:

  def edit
  end

  def update
    respond_to do |format|
      if @stream.update(stream_params)
        # format.html { redirect_to whatisnext_path, notice: 'Stream was successfully updated.' }
        # format.json { render :show, status: :ok, location: @stream }
        format.js
      else
        format.html { render :edit }
        format.json { render json: @stream.errors, status: :unprocessable_entity }
      end
    end
  end

... triggering the following js -

streams/update.js.erb

$('#viewport').html("<%= escape_javascript(render 'whatisnext_signed_in_dynamic' %>");

... the intention being that we leave the modal and go back to main view. Everything happens nicely up to the point where the user clicks the submit button - the object is updated and changes saved to the database, but the problem is that the modal is just hanging.

So the question is - how do I make the Rails ajax and the bootstrap jQuery play nicely so that the modal closes as it should?


I too had a similar issue using modal on edit functionality , my update would work but model wasnt closing properly leaving behind a dark transparent page. In my case adding following in update.js.erb solved the issue .

$(".modal-backdrop").remove();