Friday, February 02, 2007

Two submit buttons with form_remote_tag

At the beginning stages of any new framework or language, you can get away with just posting stupid-programming tricks. This was the case with Ruby and Rails. It was fairly new about a year or two ago, but now, it's pretty much old hat, so I sometimes don't bother posting things that I figure out, since I figured it's old news.

Except, I'm still discovering little things here and there. It's not just with the new fancy RJS templates either. Old favorites like link_to_remote() still have unexplored corners (which we'll get to in a minute). Although this one wasn't that painful, I found it as a footnote in the Rails documentation, that others might have easily missed.

So the answer to the common question I was looking for was "How do I have two submit buttons in the same form?" Usually, we need this when there is a set of user-entered data that has more than one action associated with it. For example, in a blog editing page, you usually want to do two things: "save as draft" or "publish". And when you think about it, the file system on your desktop operates with the same metaphor. You select a bunch of files (the user-data), and right-click to select an action (move, copy, delete, etc).

So with a normal form submission, there is the solution of just naming the submit tags, and since the submit button will submit its value along with the form, you can tell which button was pressed:
<%= start_form_tag :action => :post %>
<%= submit_tag "Save", :name=>"save" %>
<%= submit_tag "Preview", :name=>"preview" %>
<%= end_form_tag %>
And then, in your controller, you simply check whether params["save"] or params["preview"] exists, and do the appropriate action, or call the appropriate callback.
def post
if params["save"]
save
render :action => :save
else
preview
render :action => :preview
end

def save
# do saving stuff
end

def preview
# do preview stuff
end
Cake, right? But what if you wanted to do this with a form submitted by XML_HTTP_REQUEST? This hack doesn't work with form_remote_tag, since it serializes the entire form, regardless of which buttons was pressed. I was all ready to buckle down and really learn some javascript, instead of the bits and pieces that I know.

But after combing through the documentation, apparently someone on the Rails team needed to do something similar before. If you look at the very end of the documentation for link_to_remote(), you'll find this:
:submit: Specifies the DOM element ID that‘s used as the parent of the form elements. By default this is the current form, but it could just as well be the ID of a table row or any other DOM element.
It ends up that you can use this to serialize anything containing form elements. But not only that, there are two other bonuses.

One, you can stylize the submit links to look far better than the ugly submit buttons. Two, you no longer need a dispacher method. Each link_to_remote "submit button" routes to its respective actions, which means you can have way more than two submit buttons, and not have a gigantic "if elsif" statement in a dispatcher method in your controller. Neat.
<div id="image_collection">
<% @images.each do |image| %>
<%= check_box "selected_images", image.id %>
<%= image_tag image.path %>
<% end %>
<%= link_to_remote "Group", :url => { :action => :group }, :submit => :image_collection %>
<%= link_to_remote "Delete", :url => { :action => :delete }, :submit => :image_collection %>
</div>
Notice that the parent tag doesn't have to be a form. It can be a div.

One downside to this is design related. Because the link_to_remote call can be anywhere on the page, unlike a submit button which has to be inside a form tag, one can put it anywhere. This added flexibility means that you have to be careful to put the newly minted submit button where it is obvious and intuitive that it submits the intended data. But if you're aware of that, have fun with your new and shiny multiple action form.

Update:
If you use :confirm in conjunction with :submit, make sure :confirm is in front of submit, as order seems to make a difference here.

10 comments:

  1. Anonymous6:19 AM

    That's very cool! If one were to implement this, how does it work for those that are coming to your page w/ JS disabled? Is there an easy to implement a 'backup' submit option? Also, you point out that that having the submit button anywhere is a potential design issue. I feel it's actually a huge design benefit. There are multiple times where we've wanted to have 'forms within forms' and now this appears to have solved that problem! I'm excited to try it out...

    Thanks!

    ReplyDelete
  2. I assume you'd need to detect whether javascript is avail or not, and then show a regular form with regular buttons. If javascript isn't on, you can't use form_remote_tag. If you look on the web for gracefully degrading javascript for rails, you'll probably find a few articles.

    Well, I think it's a design issue if you're not careful with it. Actions need to be associated with the items they're performed on. Users tend to make that association if the action buttons are grouped with the items they act upon.

    So if the user doesn't know what the button is going to take action on, it'll lead to confusion.

    ReplyDelete
  3. Anonymous8:16 AM

    I had another quick question! :) Is there a way to have multiple submit buttons that submit the form via a POST method vs a link_to/GET in this example?

    Thanks!

    ReplyDelete
  4. As a quickie reply about JS disabled, there's also the call respond_to() that you can work with. http://api.rubyonrails.com/classes/ActionController/MimeResponds/InstanceMethods.html#M000080

    Anyway, about it being a GET, I wrote up a post.

    http://webjazz.blogspot.com/2007/03/does-linktoremote-submit-using-get-or.html

    ReplyDelete
  5. Thanks! After lot of search I found this .. :submit is a good option when you have multiple forms

    ReplyDelete
  6. Anonymous5:33 AM

    This comment has been removed by a blog administrator.

    ReplyDelete
  7. Anonymous5:33 AM

    This comment has been removed by a blog administrator.

    ReplyDelete
  8. Daniel2:15 PM

    Thank You so much Wilhelm !!!

    ReplyDelete