I\'m attempting to get over the hurdle of dynamic form fields in Rails -- this appears to be something the framework doesn\'t handle very gracefully. I\'m also using jQuery
Based on your answer to my comment I think handling deletion unobtrusively is a good place to start. I'll use Product with scaffolding as an example, but the code will be generic so it should be easy to use in your application.
First add a new option to your route:
map.resources :products, :member => { :delete => :get }
And now add a delete view to your Product views:
<% title "Delete Product" %>
<% form_for @product, :html => { :method => :delete } do |f| %>
Are you sure you want to delete this Product?
<%= submit_tag "Delete" %>
or <%= link_to "cancel", products_path %>
<% end %>
This view will only be seen by users with JavaScript disabled.
In the Products controller you'll need to add the delete action.
def delete
Product.find(params[:id])
end
Now go to your index view and change the Destroy link to this:
<%= link_to "Delete", delete_product_path(product), :class => 'delete' %>
If you run the app at this point and view the list of products you'll be able to delete a product, but we can do better for JavaScript enabled users. The class added to the delete link will be used in our JavaScript.
This will be a rather large chunk of JavaScript, but it's important to focus on the code that deals with making the ajax call - the code in the ajaxSend handler and the 'a.delete' click handler.
(function() {
var originalRemoveMethod = jQuery.fn.remove;
jQuery.fn.remove = function() {
if(this.hasClass("infomenu") || this.hasClass("pop")) {
$(".selected").removeClass("selected");
}
originalRemoveMethod.apply(this, arguments);
}
})();
function isPost(requestType) {
return requestType.toLowerCase() == 'post';
}
$(document).ajaxSend(function(event, xhr, settings) {
if (isPost(settings.type)) {
settings.data = (settings.data ? settings.data + "&" : "") + "authenticity_token=" + encodeURIComponent( AUTH_TOKEN );
}
xhr.setRequestHeader("Accept", "text/javascript, application/javascript");
});
function closePop(fn) {
var arglength = arguments.length;
if($(".pop").length == 0) { return false; }
$(".pop").slideFadeToggle(function() {
if(arglength) { fn.call(); }
$(this).remove();
});
return true;
}
$('a.delete').live('click', function(event) {
if(event.button != 0) { return true; }
var link = $(this);
link.addClass("selected").parent().append("Are you sure?
or Cancel
");
$(".delpop").slideFadeToggle();
$(".delpop input").click(function() {
$(".pop").slideFadeToggle(function() {
$.post(link.attr('href').substring(0, link.attr('href').indexOf('/delete')), { _method: "delete" },
function(response) {
link.parents("tr").fadeOut(function() {
$(this).remove();
});
});
$(this).remove();
});
});
return false;
});
$(".close").live('click', function() {
return !closePop();
});
$.fn.slideFadeToggle = function(easing, callback) {
return this.animate({opacity: 'toggle', height: 'toggle'}, "fast", easing, callback);
};
Here's the CSS you'll need too:
.pop {
background-color:#FFFFFF;
border:1px solid #999999;
cursor:default;
display: none;
position:absolute;
text-align:left;
z-index:500;
padding: 25px 25px 20px;
margin: 0;
-webkit-border-radius: 8px;
-moz-border-radius: 8px;
}
a.selected {
background-color:#1F75CC;
color:white;
z-index:100;
}
We need to send along the auth token when we make POST, PUT or DELETE. Add this line under your existing JS tag (probably in your layout):
<%= javascript_tag "var AUTH_TOKEN = #{form_authenticity_token.inspect};" if protect_against_forgery? -%>
Almost done. Open up your Application controller and add these filters:
before_filter :correct_safari_and_ie_accept_headers
after_filter :set_xhr_flash
And the corresponding methods:
protected
def set_xhr_flash
flash.discard if request.xhr?
end
def correct_safari_and_ie_accept_headers
ajax_request_types = ['text/javascript', 'application/json', 'text/xml']
request.accepts.sort!{ |x, y| ajax_request_types.include?(y.to_s) ? 1 : -1 } if request.xhr?
end
We need to discard flash messages if it's an ajax call - otherwise you'll see flash messages from the "past" on your next regular http request. The second filter is also required for webkit and IE browsers - I add these 2 filters to all of my Rails projects.
All that's left is the destroy action:
def destroy
@product.destroy
flash[:notice] = "Successfully destroyed product."
respond_to do |format|
format.html { redirect_to redirect_to products_url }
format.js { render :nothing => true }
end
end
And there you have it. Unobtrusive deleting with Rails. It seems like a lot of work all typed out, but it's really not that bad once you get going. You might be interested in this Railscast too.