Bootstrap Modal for Show Page

I managed to get the _form to display inside of the modal and fire off.
I'm having trouble with displaying show actions inside of the modal.

The error that I'm receiving is undefined method 'user' for
nil:NilClass. The data request is not transferring over to the show
action when I click the link_to button. I'll post what I wrote beneath
this. Thanks.

posts/_posts.html.erb

<% @post_items.each do |post| %>

<%= link_to image_tag(post.photo.url(:medium), style: 'height: 300px;
width: 500px;', lazy: true), post_path(controller: :posts, :action =>
:show, :id => post.id), remote: true, data: {:toggle => 'modal', :target
=> '#myModal'} %>

<% end %>

   <div id="post-modal" class="modal fade"></div>

        <!-- Modal -->
        <div class="modal fade" id="myModal" tabindex="-1" role="dialog"
aria-labelledby="myModalLabel" aria-hidden="true">
          <div class="modal-dialog" role="document">
            <div class="modal-content">
              <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal"
aria-label="Close">
                  <span aria-hidden="true">&times;</span>
                </button>
                <h4 class="modal-title" id="myModalLabel">Show Page for
Post</h4>
              </div>
              <div class="modal-body">
                <%= render 'posts/post', :locals => {post: post } %>
              </div>
            </div>
          </div>
        </div>

_post.html.erb

<%= @post.user.username.capitalize %>

<%= image_tag(@post.photo.url(:large)) %>

etc.

As always it is best to paste the complete error message and tell us
which line it refers to. If it is the line above that is saying
undefined method 'user' for nil:NilClass
then that means @post is nil.

Colin

Colin Law wrote in post #1182722:

Colin Law wrote in post #1182722:

_post.html.erb

<%= @post.user.username.capitalize %>

As always it is best to paste the complete error message and tell us
which line it refers to. If it is the line above that is saying
undefined method 'user' for nil:NilClass
then that means @post is nil.

Colin

I added the modal before the end block, and then I used the larger image
inside of the modal body (instead of the partial posts/post partial).
The problem with that approach is that it doesn't work for more than one
post. I need the modal button to listen for changes, so that it can open
up with new content.

This is a shortcoming of the Bootstrap modal design. In Bootstrap 3 documentation, they say that being able to reload a modal with different content is deprecated, and in Bootstrap 4, they are going to remove the possibility (IIRC). The docs mention that if you want to do something like this, you should use a client-side framework (I am not a fan of this idea).

Here's what I use in Bootstrap 3 to work around this:

$(document).on('page:change', function(){
  // shims for the Bootstrap Modal, which is bloody-minded about caching content per modal
  $(document).on('click', '#reusable_modal [data-dismiss="modal"]', function (e) {
    $(e.target).removeData('bs.modal');
    $('#reusable_modal .modal-content').empty();
  });
  $(document).on('click', '[data-target="#reusable_modal"]', function(e) {
    $("#reusable_modal .modal-content").load($(this).attr("href"));
  });
});

Walter

Walter Davis wrote in post #1182728:

Here's what I use in Bootstrap 3 to work around this:

$(document).on('page:change', function(){
  // shims for the Bootstrap Modal, which is bloody-minded about caching
content per modal
  $(document).on('click', '#reusable_modal [data-dismiss="modal"]',
function (e) {
    $(e.target).removeData('bs.modal');
    $('#reusable_modal .modal-content').empty();
  });
  $(document).on('click', '[data-target="#reusable_modal"]', function(e)
{
    $("#reusable_modal .modal-content").load($(this).attr("href"));
  });
});

Walter

Where should I place id="reusable_modal"? modal-content is already set.
Thanks.

Walter Davis wrote in post #1182728:

undefined method 'user' for nil:NilClass
then that means @post is nil.

I made some changes, is this ok?

<div class="modal-body">
                <%= image_tag(post.photo.url(:large), style: 'max-width:
570px;') %>
                <%= sanitize content_with_emoji(post.body) %>
              </div>

$(document).on('page:change', function(){
        // shims for the Bootstrap Modal, which is bloody-minded about
caching content per modal
        $(document).on('click', '#reusable_modal
[data-dismiss="modal"]',
            function (e) {
              $(e.target).removeData('bs.modal');
              $('#reusable_modal').find('.modal-content').empty();
            });
        $(document).on('click', '[data-target="#reusable_modal"]',
function(e)
        {
          $("#reusable_modal").find(".modal-content").load($(this).attr("href"));
        });
      });

Will it still work if I add comments inside of the modal? Will it
basically clear everything inside of the container? I'll wait for your
response.

It's still not dynamically updating.

  <!-- Modal -->
        <div class="modal fade reusable_modal" id="myModal"
tabindex="-1" role="dialog" aria-labelledby="myModalLabel"
aria-hidden="true">
          <div class="modal-dialog" role="document">
            <div class="modal-content">
              <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal"
aria-label="Close">
                  <span aria-hidden="true">&times;</span>
                </button>
                <%= image_tag(post.user.avatar_url(:thumb), class:
'round-image-50') %>
                <%= post.user.username %>
                <h4 class="modal-title" id="myModalLabel"></h4>
              </div>
              <div class="modal-body">
                <%= image_tag(post.photo.url(:large), style: 'max-width:
570px;') %>
                <%= sanitize content_with_emoji(post.body) %>
              </div>
              <div class="modal-footer">
                <h5>Hello Test</h5>
              </div>
            </div>
          </div>
        </div>

  $(document).on('page:change', function(){
        // shims for the Bootstrap Modal, which is bloody-minded about
caching content per modal
        $(document).on('click', '#reusable_modal
[data-dismiss="modal"]',
            function (e) {
              $(e.target).removeData('bs.modal');
              $('#reusable_modal').find('.modal-content').empty();
            });
        $(document).on('click', '[data-target="#reusable_modal"]',
function(e)
        {
          $("#reusable_modal").find(".modal-content").load($(this).attr("href"));
        });
      });

Walter Davis wrote in post #1182728:

Here's what I use in Bootstrap 3 to work around this:

$(document).on('page:change', function(){
// shims for the Bootstrap Modal, which is bloody-minded about caching
content per modal
$(document).on('click', '#reusable_modal [data-dismiss="modal"]',
function (e) {
   $(e.target).removeData('bs.modal');
   $('#reusable_modal .modal-content').empty();
});
$(document).on('click', '[data-target="#reusable_modal"]', function(e)
{
   $("#reusable_modal .modal-content").load($(this).attr("href"));
});
});

Walter

Where should I place id="reusable_modal"? modal-content is already set.
Thanks.

I put that as the id of the modal itself.

Walter

It's still not dynamically updating.

<!-- Modal -->
       <div class="modal fade reusable_modal" id="myModal"
tabindex="-1" role="dialog" aria-labelledby="myModalLabel"
aria-hidden="true">
         <div class="modal-dialog" role="document">
           <div class="modal-content">
             <div class="modal-header">
               <button type="button" class="close" data-dismiss="modal"
aria-label="Close">
                 <span aria-hidden="true">&times;</span>
               </button>
               <%= image_tag(post.user.avatar_url(:thumb), class:
'round-image-50') %>
               <%= post.user.username %>
               <h4 class="modal-title" id="myModalLabel"></h4>
             </div>
             <div class="modal-body">
               <%= image_tag(post.photo.url(:large), style: 'max-width:
570px;') %>
               <%= sanitize content_with_emoji(post.body) %>
             </div>
             <div class="modal-footer">
               <h5>Hello Test</h5>
             </div>
           </div>
         </div>
       </div>

$(document).on('page:change', function(){
       // shims for the Bootstrap Modal, which is bloody-minded about
caching content per modal
       $(document).on('click', '#reusable_modal
[data-dismiss="modal"]',
           function (e) {
             $(e.target).removeData('bs.modal');
             $('#reusable_modal').find('.modal-content').empty();
           });
       $(document).on('click', '[data-target="#reusable_modal"]',
function(e)
       {
         $("#reusable_modal").find(".modal-content").load($(this).attr("href"));
       });
     });

I think you're missing something, then. Look at this Gist: https://gist.github.com/walterdavis/262e8ae9fa0a8447bd7336c6ae518f56

The first snippet is the HTML that your server would return if you requested modal-content.html. Imagine if there was another snippet, called other-modal-content.html, with different contents.

Then, in your page, you have a link to `modal-content.html` and another to `other-modal-content.html`. Note how they are constructed: they have a data-target to the same modal (using the id #reusable_modal) and they each differ by their href attribute. The href is what jQuery reads and uses to populate the body of the modal dialog.

Below those links, you have the outer "frame" of the #reusable_modal itself. Note that it is empty, and will be filled by (and emptied again by) the script, depending on whether you click on a URL with its data-target, or click on a button with data-dismiss within that same element.

I suspect that your only issue here is getting the HTML correct. Having the right ID (as noted in the jQuery) in your HTML will mean that the selector "finds" the content you wish to change.

In the Rails context, you would obviously not have static HTML for these URLs and their snippets, but rather controller methods and routes to them to construct your modal contents.

Walter

This link is rendering the entire show action inside of the modal.
(Including the navigation bar)

<%= link_to image_tag(post.photo.url(:medium), style: 'height: 300px;
width: 500px;'), post_path(controller: :posts, :action => :show, :id =>
post.id), remote: true, data: {:toggle => 'modal', :target =>
'#reusable_modal'}, lazy: true %>

Should I take a different approach, than hard-coding the show action in
the link itself.

The JavaScript is working from what I can see.

This link is rendering the entire show action inside of the modal.
(Including the navigation bar)

<%= link_to image_tag(post.photo.url(:medium), style: 'height: 300px;
width: 500px;'), post_path(controller: :posts, :action => :show, :id =>
post.id), remote: true, data: {:toggle => 'modal', :target =>
'#reusable_modal'}, lazy: true %>

Should I take a different approach, than hard-coding the show action in
the link itself.

You need a different method to render this, or if it will only ever appear in the modal, then you need to add layout: false to your controller so it doesn't render the whole page. Make sure that your `show` template only renders the stuff that will appear within the modal, nothing else, and then turn off the layout so you don't get the entire page.

If the modals will be the only consumer of the "remote: true" requests, then you can make that change in your show controller method, like this:

def show
  #whatever else you regularly have here
  respond_to do |format|
    format.html { }
    format.js { layout: false, template: 'modal' }
  end
end

# views/posts/modal.html.erb

<div class="modal-header">
  <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
  <h4><%= @post.title %></h4>
</div>
<div class="modal-body">
  <%= @post.whatever_else_you_want_to_render %>
</div>
<div class="modal-footer">
  <button class="btn btn-default" data-dismiss="modal">Close</button>
</div>

Walter

Walter Davis wrote in post #1182737:

Ok, I tested a second link method

<%= link_to image_tag(post.photo.url(:medium), style: 'height: 300px;
width: 500px;'), post, remote: true, data: {:toggle => 'modal', :target
=> '#reusable_modal'}, lazy: true %>

When I remove the post local variable -> targeting the action. The modal
doesn't pop up an image at all.

Walter Davis wrote in post #1182737:

Ok, I tested a second link method

<%= link_to image_tag(post.photo.url(:medium), style: 'height: 300px;
width: 500px;'), post, remote: true, data: {:toggle => 'modal', :target
=> '#reusable_modal'}, lazy: true %>

When I remove the post local variable -> targeting the action. The modal
doesn't pop up an image at all.

I suspect the issue is in how you are constructing this link, and how you have directed your controller to respond to a "format: js" request. When you say "remote: true", then your controller must be set up to respond with your desired layout in the format.js section of the respond_to block.

In the past, I have done these without using the remote: true thing at all. I suspect you can remove it as well. Try the following:

# posts_controller.rb
# add a new method next to show()

def modal
  @post = Post.find params[:id]
end

# views/posts/modal.html.erb

<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4><%= @post.title %></h4>
</div>
<div class="modal-body">
<%= @post.whatever_else_you_want_to_render %>
</div>
<div class="modal-footer">
<button class="btn btn-default" data-dismiss="modal">Close</button>
</div>

(be sure to put whatever actual erb you want to use in your modal-body section)

# routes.rb

  resources :posts do
    member do
      get :modal
    end
  end

Now to link to it, you will have a new modal_post_path() helper to play with, so your link would look like this:

<%= link_to image_tag(post.photo.url(:medium), style: 'height: 300px;
width: 500px;'), modal_post_path(post), data: {:toggle => 'modal', :target =>
'#reusable_modal'}, lazy: true %>

See how that goes for you.

Walter

Walter Davis wrote in post #1182743:

Should I take a different approach, than hard-coding the show action in
the link itself.

You need a different method to render this, or if it will only ever
appear in the modal, then you need to add layout: false to your
controller so it doesn't render the whole page. Make sure that your
`show` template only renders the stuff that will appear within the
modal, nothing else, and then turn off the layout so you don't get the
entire page.

If the modals will be the only consumer of the "remote: true" requests,
then you can make that change in your show controller method, like this:

def show
  #whatever else you regularly have here
  respond_to do |format|
    format.html { }
    format.js { layout: false, template: 'modal' }
  end
end

Walter

Thank you for helping me again. I've added a modal.html.erb file. I
included all of the code needed for the modal itself.

Inside of the show action for the posts_controller.rb

def show
    @post = Post.find(params[:id])
    @new_comment = Comment.build_from(@post, current_user.id, "")
    respond_to do |format|
      format.html
      format.js { render layout: false , template: 'modal'}
      format.json {render json: @post }
    end
  end

But, for whatever reason, the link_to image_tag button is targeting the
old show.html.erb action for the post.

How can it be targeting code that you have deleted?

Colin

Walter Davis wrote in post #1182743:

Should I take a different approach, than hard-coding the show action in
the link itself.

You need a different method to render this, or if it will only ever
appear in the modal, then you need to add layout: false to your
controller so it doesn't render the whole page. Make sure that your
`show` template only renders the stuff that will appear within the
modal, nothing else, and then turn off the layout so you don't get the
entire page.

If the modals will be the only consumer of the "remote: true" requests,
then you can make that change in your show controller method, like this:

def show
#whatever else you regularly have here
respond_to do |format|
   format.html { }
   format.js { layout: false, template: 'modal' }
end
end

Walter

Thank you for helping me again. I've added a modal.html.erb file. I
included all of the code needed for the modal itself.

Inside of the show action for the posts_controller.rb

def show
   @post = Post.find(params[:id])
   @new_comment = Comment.build_from(@post, current_user.id, "")
   respond_to do |format|
     format.html
     format.js { render layout: false , template: 'modal'}
     format.json {render json: @post }
   end
end

But, for whatever reason, the link_to image_tag button is targeting the
old show.html.erb action for the post.

Please have a look at this very simple Rails scaffold app with a modal: https://github.com/walterdavis/modal-test

git clone it, rake db:migrate, rails s, and look at it in a browser. Add some posts, then return to the index page and click on the Show link and then the Modal link (they are different). See how the modal appears and shows you any post you click on when you use the modal link.

Walter

Walter Davis wrote in post #1182744:

# views/posts/modal.html.erb

# routes.rb

  resources :posts do
    member do
      get :modal
    end
  end

I tried this method, but it went back to the default one image showing
problem that I had earlier. I will see if I can find a way around it.

Walter Davis wrote in post #1182747:

modal, nothing else, and then turn off the layout so you don't get the

Please have a look at this very simple Rails scaffold app with a modal:
https://github.com/walterdavis/modal-test

Walter

I'm checking it out now. Thanks.

David Williams wrote in post #1182748:

Walter Davis wrote in post #1182744:

# views/posts/modal.html.erb

# routes.rb

  resources :posts do
    member do
      get :modal
    end
  end

I tried this method, but it went back to the default one image showing
problem that I had earlier. I will see if I can find a way around it.

The screen dims, but the modal itself doesn't show. I wrote it exactly
the same way you put it.

<div class="modal-header">
  <button type="button" class="close" data-dismiss="modal"
aria-label="Close">
    <span aria-hidden="true">&times;</span>
  </button>
  <i><%= image_tag(@post.user.avatar_url(:thumb), class:
'round-image-50') %></i>
  <h4 class="modal-title" id="myModalLabel" style="margin-left: 60px;
margin-top: -42px;"><%= post.user.username %></h4>
</div>
<div class="modal-body">
  <% if @post.photo.present? %>
  <%= image_tag(@post.photo.url(:large), style: 'max-width: 570px;') %>
  <% end %>
  <div class="modal_post_pos">
    <%= sanitize content_with_emoji(@post.body) %>
  </div>
</div>
<div class="modal-footer">
  <%= render partial: 'comments/template', locals: {commentable: @post,
new_comment: @comment} %>
</div>

In my _posts.html.erb
<% @post_items.each do |post| %>
<%= link_to image_tag(post.photo.url(:medium), style: 'height: 300px;
width: 500px;'), modal_post_path(post), data: {:toggle => 'modal',
:target => '#reusable_modal'}, lazy: true %>

   <div id="post-modal" class="modal fade"></div>

      <!-- Modal -->
      <div class="modal fade" id="reusable_modal" tabindex="-1"
role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
        <div class="modal-dialog" role="document">
          <div class="modal-content">

          </div>
        </div>
      </div>
<% end %>

<script>
$(document).on('page:change', function () {

      $(document).on('click', '#reusable_modal [data-dismiss="modal"]',
          function (e) {
            $(e.target).removeData('bs.modal');
            $('#reusable_modal').find('.modal-content').empty();
          });
      $(document).on('click', '[data-target="#reusable_modal"]',
function (e) {
        $("#reusable_modal").find(".modal-content").load($(this).attr("href"));
      });
    });
</script>

posts_controller.rb

def show
@post = Post.find(params[:id])
    @new_comment = Comment.build_from(@post, current_user.id, "")
    respond_to do |format|
      format.html
      format.js { render layout: false , template: 'posts/modal' }
      format.json {render json: @post }
    end
end

def modal
render layout: false
end