Handling forms with multiple buttons
With an app that I’m working on, the client wants to have several buttons for doing different actions on every form: “Save”, “Save & Continue Editing”, “Save & Add Another”, and “Cancel”. HTML only allows you to have one action defined per form (instead of per button), and Rails pretty much assumes that if you submit a request to a specific action, you expect to execute it.
So, instead of littering my code with all kinds of if/else statements, I decided to wrap up the functionality into a little plugin that makes it a little cleaner.
with_action is a respond_to style helper for executing different blocks based on presence of certain request parameters.
def create
with_action do |a|
a.cancel { redirect_to articles_path }
a.any do
@article = Article.new(params[:article])
if @article.save
a.save { redirect_to article_path(@article) }
a.edit { redirect_to article_path(@article) }
a.approve do
@article.approve!
redirect_to article_path(@article)
end
else
render :action => 'new'
end
end
end
end
A block is invoked if a parameter with the same name exists and is not blank. Here is an example of the form that submits to this action:
<%= submit_tag 'Save', :name => 'save' %>
<%= submit_tag 'Save & Continue Editing', :name => 'edit' %>
<%= submit_tag 'Save & Approve', :name => 'approve' %>
<%= submit_tag 'Cancel', :name => 'cancel' %>
If an any block is present and no parameter that matches one of the other blocks, it is called by default, otherwise the first block will be called. The any block is the only one that can have nesting and be called multiple times.
I realize this could be considered trivial, but this looks a lot cleaner than the alternative, and more importantly, gave me a way to standardize on how I handle these actions. Let me know what you think.
http://github.com/collectiveidea/with_action








16 comments
Nice!
July 16, 2007 at 08:49 AM
I’ve always handled stuff like this by changing the action with JavaScript. Basically my submit button has an onclick callback that will update the action for the form to the right URL. I see the tradeoffs as:
Your method:
My method:
I can see both methods useful depending on the needs of the situation. Makes me now want to package my method up as a nice plugin. Maybe something like:
which would generate the tag with the necessary JavaScript to update the action.
July 16, 2007 at 08:57 AM
Eric,
That’s another option. But one of the ideals that I try to uphold with JavaScript is to make it fail gracefully. I don’t care if something doesn’t work without JavaScript, but it shouldn’t cause it to behave differently (i.e. Cancel button causes it to save the form).
July 16, 2007 at 09:22 AM
I can see you point. Not only does my method not work with JavaScript but it actually could do the wrong thing. Perhaps something along the lines of _method then. If you submit buttons look like:
July 16, 2007 at 03:25 PM
Good stuff Brandon. You solution might get a big confusing if you add too many any nested blocks but it’s still a very clean way of dealing with the issue you were facing.
Thanks for the plugin, I’ll keep it handy in case I have to do the same thing soon.
Matt
July 16, 2007 at 03:42 PM
This is great stuff. Definitely bookmarked for future reference. :D
July 16, 2007 at 04:18 PM
One thing I wonder is whether the “cancel” button is a bad idea. If your form contains a file upload field, clicking cancel will upload the file first, before you are redirected, no?
July 16, 2007 at 08:42 PM
nkryptic,
That’s a great point. You would hope that “most” of the time people would hit cancel before the select any file uploads.
Usually I just use a cancel link, but in this situation I decided it looked bad to have 3 buttons and then a cancel link. But maybe, to get uniformity, I’ll have to switch to using images for the inputs and then just an image as a link for the cancel button.
July 17, 2007 at 12:58 AM
Perfect, just what I’m looking for.
July 17, 2007 at 08:34 AM
Very elegant!
July 19, 2007 at 08:31 AM
Awesome! I’m so glad I won’t be doing this with if/else anymore…
July 29, 2007 at 10:05 AM
Just installed this and gave it a try. Super nice work.
July 30, 2007 at 11:08 PM
Hi, I just installed this plugin but my code throws the following error:
undefined method `with_action’ for #<locationscontroller:0x466728c>
My guess is that I may not have installed the plugin correctly. How can I fix this?
August 12, 2007 at 06:20 PM
David,
It does sound like it’s not installed right. I would just try removing the plugin and installing it again. Other than that, I don’t know what to tell you.
August 17, 2007 at 11:07 AM
I’ve noticed a problem in the past, when doing this kind of thing, where submitting a form via ajax causes it to send all the buttons’ information, so rails doesn’t know which button was pressed. Does your plugin deal with that?
August 27, 2007 at 05:41 PM
Jeremy,
My plugin isn’t doing any special handling for Ajax requests, but I believe Prototype 1.6 has had some improvements in the form serialization code, so it may not be an issue anymore.
August 27, 2007 at 10:17 PM