113

I need to have multiple submit buttons.

I have a form which creates an instance of Contact_Call.

One button creates it as normal.

The other button creates it but needs to have a different :attribute value from the default, and it also needs to set the attribute on a different, but related model used in the controller.

How do I do that? I can't change the route, so is there a way to send a different variable that gets picked up by [:params]?

And if I do then, what do I do in the controller, set up a case statement?

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Satchel
  • 16,414
  • 23
  • 106
  • 192
  • possible duplicate of [Rails: Multi-submit buttons in one Form](http://stackoverflow.com/questions/3332449/rails-multi-submit-buttons-in-one-form) – Joshua Pinter Jun 22 '15 at 18:23
  • 4
    This one is older and has more votes. If anything the above should be closed as a duplicate of this... – Taryn East Jun 23 '15 at 04:32

7 Answers7

136

You can create multiple submit buttons and provide a different value to each:

<% form_for(something) do |f| %>
    ..
    <%= f.submit 'A' %>
    <%= f.submit 'B' %>
    ..
<% end %>

This will output:

<input type="submit" value="A" id=".." name="commit" />
<input type="submit" value="B" id=".." name="commit" />

Inside your controller, the submitted button's value will be identified by the parameter commit. Check the value to do the required processing:

def <controller action>
    if params[:commit] == 'A'
        # A was pressed 
    elsif params[:commit] == 'B'
        # B was pressed
    end
end

However, remember that this tightly couples your view to the controller which may not be very desirable.

Anurag
  • 140,337
  • 36
  • 221
  • 257
  • 1
    Now thats something new. Thanks @Anurag! – Shripad Krishna Jun 12 '10 at 02:38
  • 1
    so just putting the 'A' automatically create parameter name='commit'? – Satchel Jun 12 '10 at 06:00
  • is there a way as you said not to tightly couple the view to the controller? for example, for the submit buttons to change the URL? It *seems* that this isn't necessarily bad because a form submits variables which can change the behavior of hte controller,e ven if it user input, which the selection of the button is? – Satchel Jun 12 '10 at 06:07
  • 1
    You can't change a form action attribute without a messy js hack. – Ben Orozco Jun 12 '10 at 07:35
  • Changing the form action attribute on the fly is a more brittle solution. Using the commit attribute is less so. You could as an alternative wrap the second submit button inside a different form and pass a parameter that needs to be changed to the same action. But it is not much different than relying on the values of the 2 submit buttons. Without knowing more how you've setup this thing, the best solution so far would be the with 2 submit buttons. – Anurag Jun 12 '10 at 16:42
  • okay good...I'm fine with this, just wanted to know, will experiment with it hopefully today and let you know! – Satchel Jun 17 '10 at 00:46
  • Maybe sombody can help me with this issue http://stackoverflow.com/questions/18476292/rails-two-submit-buttons-but-only-one-remotetrue – John Smith Aug 27 '13 at 22:09
  • What about i18n issues? – carpamon Oct 18 '13 at 17:33
  • One should use `f.button` helper. It uses ` – urmurmur Jul 21 '15 at 19:58
  • I find that http://stackoverflow.com/a/3333426/1067145 is a cleaner approach. It's better to set the "out-of-band" 'name' of the submit than check the value itself. – Max Wallace Jun 07 '16 at 15:24
  • I think @patpit's solution below is a better approach! Use the `formaction` attribute on the submit button to overwrite the URL the form is submitted to – mrcasals May 18 '20 at 08:31
100

There is also another approach, using the formaction attribute on the submit button:

<% form_for(something) do |f| %>
    ...
    <%= f.submit "Create" %>
    <%= f.submit "Special Action", formaction: special_action_path %>
<% end %>

The code stays clean, as the standard create button doesn't need any change, you only insert a routing path for the special button:

formaction:
The URI of a program that processes the information submitted by the input element, if it is a submit button or image. If specified, it overrides the action attribute of the element's form owner. Source: MDN

patpir
  • 1,153
  • 2
  • 10
  • 9
  • 4
    this is supported across all browsers http://www.w3schools.com/tags/att_button_formaction.asp http://www.w3schools.com/tags/att_input_formaction.asp – Sumit Garg May 13 '16 at 18:02
  • 10
    I realize the question is old, but I advise readers that this concise solution deserves better consideration. – Jerome Nov 12 '16 at 09:22
  • 2
    I wish I had found this answer the first time I had this same question. I'm glad I decided to look a little deeper this time. Great solution. – rockusbacchus Aug 23 '17 at 21:44
  • 2
    I really like this solution. However, I had to add a hidden field with the CSRF token even though I was already using form helpers or Rails would not accept the token. I could not find a better workaround and am still not sure why exactly this happens or just adding the token again fixes it. – irruputuncu Jun 10 '20 at 12:35
  • I think this is the better solution because it respects the single responsibility principles and keeps things clear each button performs its own action keeping logic in controllers simple. – Khalil Gharbaoui Jul 09 '20 at 10:46
  • @irruputuncu, how did you solve this? I've added the `<% token_tag %>` within my form and I'm still receiving a token error – B-M Jan 27 '22 at 14:44
  • 1
    @B-M I could not find the exact code I wrote back then again, but I think I used this approach: https://stackoverflow.com/questions/8503447/rails-how-to-add-csrf-protection-to-forms-created-in-javascript - adding it like `<%= hidden_field_tag :authenticity_token, form_authenticity_token %>` – irruputuncu Jun 28 '22 at 07:31
33

You can alternatively recognized which button was pressed changing its attribute name.

<% form_for(something) do |f| %>
    ..
    <%= f.submit 'A', name: 'a_button' %>
    <%= f.submit 'B', name: 'b_button' %>
    ..
<% end %>

It's a little bit uncomfortable because you have to check for params keys presence instead of simply check params[:commit] value: you will receive params[:a_button] or params[:b_button] depending on which one was pressed.

idmean
  • 14,540
  • 9
  • 54
  • 83
masciugo
  • 1,113
  • 11
  • 19
  • 2
    Still doesn't decouple view from the controller. – slowpoison May 26 '13 at 08:39
  • 1
    Yes, if decouple means avoiding some logic in the action in order to routes to the final action you are right, they are still coupled. I just meant that if you use the name attribute in that logic your controller is independent from what's showed on the button. Thanks, edited – masciugo May 29 '13 at 08:36
  • 4
    This one seems to be better than the accepted one in i18n situations because "value" is displayed and if you're displaying Unicode characters it would get messy. – xji Mar 04 '15 at 06:11
  • 2
    However the parameters are not being let through. I'm using simple_form gem. Is there any correlation. – xji Mar 05 '15 at 14:52
  • 1
    This doesn't decouple the view from the controller, but at least it decouples the text displayed from the controller. much better IMO. – Mic Fok Mar 23 '15 at 19:19
  • 1
    similar problem to Xiang Ji. I'm using form_tag and the paramater is not coming through – ryan2johnson9 Dec 17 '15 at 04:08
13

Similar solution to one suggested by @vss123 without using any gems:

resources :plan do
  post :save, constraints: lambda {|req| req.params.key?(:propose)}, action: :propose
  post :save, constraints: lambda {|req| req.params.key?(:finalize)}, action: :finalize
end

Notice that I avoid using value and use input name instead since submit button value is often internationalized / translated. Also, I'd avoid using this too much since it will quickly clutter your routes file.

Tadas Sasnauskas
  • 2,183
  • 1
  • 22
  • 24
10

We solved using advanced constraints in rails.

The idea is to have the same path (and hence the same named route & action) but with constraints routing to different actions.

resources :plan do
  post :save, constraints: CommitParamRouting.new("Propose"), action: :propose
  post :save, constraints: CommitParamRouting.new("Finalize"), action: :finalize
end

CommitParamRouting is a simple class that has a method matches? which returns true if the commit param matches the given instance attr. value.

This available as a gem commit_param_matching.

siliconsenthil
  • 1,380
  • 1
  • 14
  • 25
3

An old question, but since I've been dealing with the same situation, I thought I'd post my solution. I'm using controller constants to avoid introducing a discrepancy between the controller logic and the view button.

class SearchController < ApplicationController
  SEARCH_TYPES = {
    :searchABC => "Search ABCs",
    :search123 => "Search 123s"
  }

  def search
    [...]
    if params[:commit] == SEARCH_TYPES[:searchABC]
      [...]
    elsif params[:commit] == SEARCH_TYPES[:search123]
      [...]
    else
      flash[:error] = "Search type not found!"]
      [...]
    end
  end
  [...]          
end

And then in the view:

<% form_for(something) do |f| %>
    [...]
    <%= f.submit SearchController::SEARCH_TYPES[:searchABC] %>
    <%= f.submit SearchController::SEARCH_TYPES[:search123] %>
    [...]
<% end %>

This way the text only lives in one place - as a constant in the controller. I haven't tried to figure out how to i18n this yet, however.

Draknor
  • 41
  • 3
  • What do you mean by "i18n"? – skrrgwasme Jul 24 '14 at 22:55
  • Was this preferable to using constraints in the route? Thanks! – Satchel Jul 26 '14 at 00:44
  • @Scott: i18n means 'internationalization' -- basically, how would you support multiple languages. I haven't really looked into it, so I'm not very familiar with how it works or how to implement it. – Draknor Aug 14 '14 at 15:44
  • @Angela - probably not :) And actually, after refactoring my code I simply created multiple forms, each with different actions, rather than a single monolithic form that contained a bunch of unrelated forms. – Draknor Aug 14 '14 at 15:44
1

I have a variable number of submit buttons on my form thanks to nested_form_fields, so just using the name wasn't enough for me. I ended up including a hidden input field in the form and using Javascript to populate it when one of the form submit buttons was pressed.

Shawn Walton
  • 1,714
  • 2
  • 14
  • 23