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
posted by brandon | updated June 19th 07:19 PM
comments feed

16 comments

  1. Nice!

    Joerg Battermann Joerg Battermann
    July 16, 2007 at 08:49 AM
  2. 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:

    submit_tag 'Approve',
      :action => approval_path(@article)
    

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

    Eric Anderson Eric Anderson
    July 16, 2007 at 08:57 AM
  3. 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).

    Brandon Brandon
    July 16, 2007 at 09:22 AM
  4. 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:

    
    <%=submit_tag 'update', :name => '_method'%>
    <%=submit_tag 'destroy', :name => '_method'%>
    
    
    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.
    
    Eric Anderson Eric Anderson
    July 16, 2007 at 03:25 PM
  5. 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

    Matt Aimonetti Matt Aimonetti
    July 16, 2007 at 03:42 PM
  6. This is great stuff. Definitely bookmarked for future reference. :D

    Chris Chris
    July 16, 2007 at 04:18 PM
  7. 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?

    nkryptic nkryptic
    July 16, 2007 at 08:42 PM
  8. 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.

    Brandon Brandon
    July 17, 2007 at 12:58 AM
  9. Perfect, just what I’m looking for.

    Sam Nardoni Sam Nardoni
    July 17, 2007 at 08:34 AM
  10. Very elegant!

    Matt Secoske Matt Secoske
    July 19, 2007 at 08:31 AM
  11. Awesome! I’m so glad I won’t be doing this with if/else anymore…

    Justin Justin
    July 29, 2007 at 10:05 AM
  12. Just installed this and gave it a try. Super nice work.

    John Nunemaker John Nunemaker
    July 30, 2007 at 11:08 PM
  13. 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?

    David David
    August 12, 2007 at 06:20 PM
  14. 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
    
    Brandon Brandon
    August 17, 2007 at 11:07 AM
  15. 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?

    Jeremy Jeremy
    August 27, 2007 at 05:41 PM
  16. 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.

    Brandon Brandon
    August 27, 2007 at 10:17 PM

About

I'm Brandon Keepers, a web application developer that likes beautiful code, valid markup and adherence to standards. As a part of Collective Idea in Holland, Michigan, I practice Agile software development primarily using Ruby on Rails.

-86.103171 42.785037

Contact:

more ยป

Syndicate