Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reusable remote modal Rails

I am trying to create a reusable remote modal. The idea is to create a model container and render content with yield into it and render content when needed.

Note: all HTML code will be written in HAML. I am using bootstrap-modal-rails gem to handle my modals.

My modal container layout (not a partial):

%div(class="modal" id="mainModal ajax-modal" tabindex="-1" role="dialog"){:'aria-labelledby'=>"mainModalLabel", :'aria-hidden'=>"true"}
  %div(class="c-modal__container")
    %div(class="c-modal__layout modal__content") 
      = yield

In my application.html.haml, I have a div that defines the modal location as follows:

-# ...
%div(id="modal-holder")

- unless params[:nojs]
  = javascript_include_tag 'desktop'

-# ...

Here is where my problem comes from. I have a book controller where the user can add books. I have a multi-step registration and my new action have to be my modal. When the user clicks New book link, I am loading a new modal.

My New book link:

= link_to "New book", new_book_path, class: "c-link--navbar", data: { modal: true }

CoffeeScript(re-written in jQuery) for the modal (this code from this article):

$(function() {
  var modal_holder_selector, modal_selector;
      modal_holder_selector = '#modal-holder';
      modal_selector = '.modal';
  $(document).on('click', 'a[data-modal]', function() {
    var location;
    location = $(this).attr('href');
    $.get(location, function(data) {
      return $(modal_holder_selector).html(data).find(modal_selector).modal();
    });
    return false;
  });
  return $(document).on('ajax:success', 'form[data-modal]', 

  function(event, data, status, xhr) {
    var url;
    url = xhr.getResponseHeader('Location');
    if (url) {
      window.location = url;
    } else {
      $('.modal-backdrop').remove();
      $(modal_holder_selector).html(data).find(modal_selector).modal();
    }
    return false;
  });
});

And now my new action for the yield that will be loaded inside my remote modal:

= form_for(@book, remote: remote, html: {role: :form}, class: "c-form") do |f|
  -# Forms here...
  = f.submit "Add a new book", class: "c-btn"

If the user's information correct, my modal will redirect the user to general action else I want to stay on the same page and flash an error message but the problem is that I am not using a partial and it automatically redirects me to a new page with new action.

My books_controller.rb:

class BooksController < ApplicationController
  respond_to :html, :json, :js
  ...

  def index
    @books = current_user.books
  end

  def new
    @book = current_user.books.build
    respond_modal_with @book
  end

  def create
    @book = current_user.books.build(book_params)

    if @book.save
      redirect_to general_book_path(@book), notice: "Saved."
    else
      flash.now[:notice] = "Something went wrong."
      render :new
    end
  end

  ...
end

Modal Responder:

class ModalResponder < ActionController::Responder
  cattr_accessor :modal_layout
  self.modal_layout = 'modal'

  def render(*args)
    options = args.extract_options!
    if request.xhr?
      options.merge! layout: modal_layout
    end
      controller.render *args, options
  end

  def default_render(*args)
    render(*args)
  end

  def redirect_to(options)
    if request.xhr?
      head :ok, location: controller.url_for(options)
    else
      controller.redirect_to(options)
    end
  end
end

I tried to add something like

respond_to do |format|
  format.html
  format.json { render json: @book }
end

to my create method but it doesn't work and still redirects me to the new page. Also, I've already asked a similar question and I've noticed that my JavaScript code works on a separate page. As long as I understand the problem is with my remote modal action page. Can someone please help me to figure out how to stay on the same page when the user entered the wrong input and turn on my javascript?

like image 703
Anton S. Avatar asked Dec 28 '17 14:12

Anton S.


Video Answer


1 Answers

TBH that's an abstract solution which is not directly related to your question, it works though (bootstrap+jq, based on production code)

Coffe script

$.showModal = (title, body) ->
    $("#modal").find(".modal-title").html(title)
    $("#modal").find(".modal-body").html(body)
    $("#modal").modal("show")
$("#modal").find(".modal-submit").click () ->
    $("#modal").find("form").submit()

Html - typical bootstrap modal

<div id="modal" class="modal" tabindex="-1" role="dialog">
  <div class="modal-dialog modal-lg" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h5 class="modal-title"></h5>
        <button type="button" class="close" data-dismiss="modal" aria-label="<%= t :close %>" title="<%= t :close %>">
          <span aria-hidden="true"></span>
        </button>
      </div>
      <div class="modal-body">
        <p></p>
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-danger" data-dismiss="modal"><%= t :close %></button>
        <button type="button" class="btn btn-success modal-submit"><%= t :save %></button>
      </div>
    </div>
  </div>
</div>

new.js.erb or another rails view

$.showModal(
    "<%= escape_javascript t :edit_comment %>",
    "<%= escape_javascript(render 'form', :comment => @comment) %>"
);

link to open modal

<%= link_to t(:new), [:new, comment], :remote => true, class: "btn" %>

You probably would like to use your own ajax request - this one would do nothing in case of server error/timeout

like image 84
goose3228 Avatar answered Sep 19 '22 03:09

goose3228