UPDATE: Here is a Rails 5 Adaptation of this answer using CoffeeScript: https://gist.github.com/1456815
jQuery is more popular for good reasons that I won't go into. Plenty on that elsewhere.
Assuming you want to use jQuery, the setup is pretty straightforward:
# add the following to your Gemfile under `group :development`
gem 'jquery-rails'
# run the following
$ bundle
# then let the generator install
$ rails g jquery:install
And then update your Application.rb javascript expansion defaults:
# JavaScript files you want as :defaults (application.js is always included).
config.action_view.javascript_expansions[:defaults] = %w( jquery rails )
And make sure your layout file (e.g. application.html.erb) is including those files:
<%= javascript_include_tag :defaults %>
For your modal, there are a lot of opinions on how to do it. Personally I prefer to roll my own modal windows based on the needs of the application. It's only a few lines of jQuery-laced JS to build modals that work really well with Rails.js:
# in a view, perhaps a _modal.html.erb partial that is included into
# your layout (probably at the bottom).
<div id="modal-container"></div>
<div id="modal">
<a href="#" class="close">close</a>
</div>
Here are some styles (scss style) for the modal that I use:
#modal-container {
display: none;
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background: rgba(0,0,0,0.4);
}
#modal {
display: none;
position: absolute;
width: 600px;
left: 50%;
margin-left: (-600px - 40px) / 2;
padding: 20px;
background: #fff;
border: 5px solid #eee;
& > .close {
position: absolute;
right: 5px;
top: 5px;
color: #666;
&:hover, &:active {
color: #000;
}
}
}
That gets the styles/views out of the way. You might want to customize those styles to fit your application better, but they're pretty generic so they should work to start.
To get it integrated into Rails, there are 2 parts. First, you need to get the browser to send AJAX requests for content and show the modal on return. Second, you need Rails to respond with an appropriate response.
Sending AJAX and handling the responses entails using :remote => true
on the links/forms you want to be sent remotely.
<%= link_to 'New Post', new_post_path, :remote => true %>
That'll create a data attribute on the link that Rails.js will pickup, allowing it to be submitted remotely automatically. It expects a javascript return, and it'll execute that response when it gets it. You can add a quick format.js
to each actions respond_to
block, and create an accompanying new.js.erb
file that contains the JS needed to actually fill in and show the modal. That's okay, but we can DRY it up a bit more than that by returning the template without a layout and moving the modal showing/hiding responsibilities into application.js:
# in your app controller, you'll want to set the layout to nil for XHR (ajax) requests
class ApplicationController < ActionController::Base
layout Proc.new { |controller| controller.request.xhr? ? nil : 'application' }
end
The javascript to make the whole thing work:
# in application.js
$(function(){
var $modal = $('#modal'),
$modal_close = $modal.find('.close'),
$modal_container = $('#modal-container');
# This bit can be confusing. Since Rails.js sends an accept header asking for
# javascript, but we want it to return HTML, we need to override this instead.
$('a[data-remote]').live('ajax:beforeSend', function(e, xhr, settings){
xhr.setRequestHeader('accept', '*/*;q=0.5, text/html, ' + settings.accepts.html);
});
# Handle modal links with the data-remote attribute
$('a[data-remote]').live('ajax:success', function(xhr, data, status){
$modal
.html(data)
.prepend($modal_close)
.css('top', $(window).scrollTop() + 40)
.show();
$modal_container.show();
});
# Hide close button click
$('.close', '#modal').live('click', function(){
$modal_container.hide();
$modal.hide();
return false;
});
});
I've got some logic in there to position the modal window 40px from the current scroll position of the browser.
With all of these pieces in place, when you click on a link/form with a remote attribute set, Rails.js will handle submitting the request, your application will know to return just the template for the action without the layout, perfect for displaying it in a modal window, and then our javascript above will hook into the ajax:success callback from Rails.js and display the modal with the template HTML returned from our application.
There's more than one way to skin a cat, and more than a dozen ways of building this functionality. This is definitely an area that stronger conventions have yet to be set by the Rails team. This is merely my way of handling this that I feel is fairly DRY, requires minimal effort, and is fairly flexible and future-proof. Perhaps one of the areas that someone could probably build on this is to use a modal/lightbox framework that covers more bases and is more feature-rich. And if someone does that, I'd love to see a write up!