opensoul.org

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

collectiveidea, html, plugin, rails, respond_to, and with_action July 16, 2007

17 Comments

  1. Joerg Battermann Joerg Battermann July 16, 2007

    Nice!

  2. Eric Anderson Eric Anderson July 16, 2007

    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:

    • allows the app to work without Javascript
    • is packaged up in a nice plugin
    • dirties up your actions (for example your “create” method above has to care about editing, approving, etc)

    My method:

    • Keeps the server-side actions clean and RESTful
    • Requires Javascript
    • In not packaged up in a nice plugin

    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:

    &lt;pre&gt; submit_tag 'Approve', :action =&gt; approval_path(@article) &lt;/pre&gt;

    which would generate the tag with the necessary JavaScript to update the action.

  3. Brandon Brandon July 16, 2007

    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).

  4. Eric Anderson Eric Anderson July 16, 2007

    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:

    
    &lt;%=submit_tag 'update', :name =&gt; '_method'%&gt;
    &lt;%=submit_tag 'destroy', :name =&gt; '_method'%&gt;
    &lt;/code&gt;
    
    That should produce tags that without JavaScript change the action based on what button is pressed (assuming a RESTful setup). Obviously this is fairly limited and flaky but if you did something along those lines so that the value of "commit" would determine the action (would require a Rails plugin). You could get the redirects out of your action (keeping them clean) while still not relying on JavaScript.
    
  5. Matt Aimonetti Matt Aimonetti July 16, 2007

    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

  6. Chris Chris July 16, 2007

    This is great stuff. Definitely bookmarked for future reference. :D

  7. nkryptic nkryptic July 16, 2007

    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?

  8. Brandon Brandon July 17, 2007

    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.

  9. Sam Nardoni Sam Nardoni July 17, 2007

    Perfect, just what I’m looking for.

  10. Matt Secoske Matt Secoske July 19, 2007

    Very elegant!

  11. Justin Justin July 29, 2007

    Awesome! I’m so glad I won’t be doing this with if/else anymore…

  12. John Nunemaker John Nunemaker July 30, 2007

    Just installed this and gave it a try. Super nice work.

  13. David David August 12, 2007

    Hi, I just installed this plugin but my code throws the following error:

    undefined method `with_action’ for #

    My guess is that I may not have installed the plugin correctly. How can I fix this?

  14. Brandon Brandon August 17, 2007

    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.

    script/plugin install http://source.collectiveidea.com/public/rails/plugins/with_action
    
  15. Jeremy Jeremy August 27, 2007

    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?

  16. Brandon Brandon August 27, 2007

    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.

  17. Paul Paul October 29, 2009

    Much thanks, this is a really great solution to a potentially messy problem and has saved me quite a few hours of hair-pulling work… Once again, the rails community provides :)

My name is Brandon Keepers. I like to build things, usually in Ruby or JavaScript. I work at GitHub and live in Holland, MI.

Popular Posts