I am trying to create some modal windows to appear whenever the user wants to edit the object. I haven't done this before, and I've looked for tutorials, but instead of getting some straight forward answers, all I got is more confused! There seem to be sooo many libraries one can use.
So I guess this question is in two parts. If I wanted to do this, are there any particular reasons why I would want to choose Jquery over prototype? Are there more options?
Second part is actually doing it, at this point i don't really care using which library.
I have the following, standard crud, edit and update actions. Now, what I would like is when the user clicks edit, instead of going to the page, pop up a window where he can edit the name of the ticket (this is the only attribute for now).
in the controller I have this:
def edit
@ticket = Ticket.find(params[:id])
end
def update
@ticket = Ticket.find(params[:id])
if @ticket.update_attributes(params[:ticket])
redirect_to tickets_url, :notice => "Successfully updated ticket."
else
render :action => 'edit'
end
end
Can anyone please help me with this? Also of course advice and links are more than welcome!
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With