Como faço para criar vários botões de envio para o mesmo formulário no Rails?

97

Eu preciso ter vários botões de envio.

Eu tenho um formulário que cria uma instância de Contact_Call.

Um botão o cria normalmente.

O outro botão o cria, mas precisa ter um valor: attribute diferente do padrão e também precisa definir o atributo em um modelo diferente, mas relacionado, usado no controlador.

Como faço isso? Não posso mudar a rota, então há uma maneira de enviar uma variável diferente que é selecionada por [: params]?

E se eu fizer isso, o que eu faço no controlador, configurar uma declaração de caso?

Timothy T.
fonte
possível duplicata do Rails: botões de envio múltiplo em um formulário
Joshua Pinter
3
Este é mais antigo e tem mais votos. Se qualquer coisa acima deve ser fechada como uma duplicata deste ...
Taryn East

Respostas:

129

Você pode criar vários botões de envio e fornecer um valor diferente para cada um:

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

Isso resultará em:

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

Dentro do seu controlador, o valor do botão enviado será identificado pelo parâmetro commit. Verifique o valor para fazer o processamento necessário:

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

No entanto, lembre-se de que isso une fortemente sua visão ao controlador, o que pode não ser muito desejável.

Anurag
fonte
1
Agora isso é algo novo. Obrigado @Anurag!
Shripad Krishna
1
então, basta colocar o 'A' para criar automaticamente o nome do parâmetro = 'commit'?
Timothy T.
há uma maneira, como você disse, de não acoplar firmemente a visualização ao controlador? por exemplo, para os botões de envio para alterar o URL? Ele parece que isso não é necessariamente ruim, porque um formulário envia variáveis que podem mudar o comportamento do controlador de hte, e ven se a entrada do usuário, que a seleção do botão é?
Timothy T.
1
Você não pode alterar um atributo de ação de formulário sem um hack bagunçado do js.
Ben Orozco
Alterar o atributo de ação do formulário instantaneamente é uma solução mais frágil. Usar o atributo commit é menos. Você poderia, como alternativa, envolver o segundo botão de envio em um formulário diferente e passar um parâmetro que precisa ser alterado para a mesma ação. Mas não é muito diferente de confiar nos valores dos 2 botões de envio. Sem saber mais como você configurou isso, a melhor solução até agora seria com 2 botões de envio.
Anurag de
74

Também há outra abordagem, usando o atributo formaction no botão de envio:

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

O código permanece limpo, já que o botão de criação padrão não precisa de nenhuma alteração, você apenas insere um caminho de roteamento para o botão especial:

formação:
O URI de um programa que processa as informações enviadas pelo elemento de entrada, se for um botão de envio ou imagem. Se especificado, ele substitui o atributo de ação do proprietário do formulário do elemento . Fonte: MDN

patpir
fonte
2
isso é compatível com todos os navegadores w3schools.com/tags/att_button_formaction.asp w3schools.com/tags/att_input_formaction.asp
Sumit Garg
8
Sei que a pergunta é antiga, mas aconselho os leitores que essa solução concisa merece uma consideração melhor.
Jerome
2
Eu gostaria de ter encontrado essa resposta na primeira vez que fiz a mesma pergunta. Estou feliz por ter decidido olhar um pouco mais a fundo desta vez. Ótima solução.
rockusbacchus
1
Eu realmente gosto dessa solução. Porém, eu tive que adicionar um campo oculto com o token CSRF mesmo que eu já estivesse usando auxiliares de formulário ou o Rails não aceitaria o token. Não consegui encontrar uma solução alternativa melhor e ainda não tenho certeza de por que exatamente isso acontece ou apenas adicionar o token novamente corrige o problema.
irruputuncu
Acho que esta é a melhor solução porque respeita os princípios de responsabilidade única e mantém as coisas claras, cada botão executa sua própria ação mantendo a lógica nos controladores simples.
Khalil Gharbaoui
29

Você pode, alternativamente, reconhecer qual botão foi pressionado alterando seu nome de atributo.

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

É um pouco desconfortável porque você tem que verificar a presença das teclas de parâmetro ao invés de simplesmente verificar o params[:commit]valor: você receberá params[:a_button]ou params[:b_button]dependendo de qual foi pressionada.

masciugo
fonte
2
Ainda não desacopla a visão do controlador.
slowpoison
1
Sim, se desacoplar significa evitar alguma lógica na ação a fim de direcionar para a ação final que você está certo, eles ainda estão acoplados. Eu apenas quis dizer que se você usar o atributo name nessa lógica, seu controlador é independente do que é mostrado no botão. Obrigado, editado
masciugo
4
Este parece ser melhor do que o aceito em situações i18n porque "valor" é exibido e se você estiver exibindo caracteres Unicode, ficaria confuso.
xji
2
No entanto, os parâmetros não estão sendo permitidos. Estou usando a gem simple_form. Existe alguma correlação.
xji
1
Isso não desacopla a visualização do controlador, mas pelo menos separa o texto exibido do controlador. muito melhor IMO.
Mic Fok
13

Solução semelhante a uma sugerida por @ vss123 sem usar nenhuma joia:

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

Observe que eu evito usar valor e uso o nome de entrada em vez disso, pois o valor do botão de envio é frequentemente internacionalizado / traduzido. Além disso, eu evitaria usar muito isso, pois isso bagunçaria rapidamente seu arquivo de rotas.

Tadas Sasnauskas
fonte
9

Resolvemos usando restrições avançadas em trilhos.

A ideia é ter o mesmo caminho (e, portanto, a mesma rota e ação nomeadas), mas com restrições de roteamento para ações diferentes.

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

CommitParamRoutingé uma classe simples que possui um método matches?que retorna verdadeiro se o parâmetro commit corresponder ao atributo de instância fornecido. valor.

Está disponível como uma gema commit_param_matching .

siliconsentil
fonte
3

Uma pergunta antiga, mas como estou lidando com a mesma situação, pensei em postar minha solução. Estou usando constantes do controlador para evitar a introdução de uma discrepância entre a lógica do controlador e o botão de visualização.

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

E então na vista:

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

Dessa forma, o texto vive apenas em um lugar - como uma constante no controlador. Ainda não tentei descobrir como resolver isso ainda.

Draknor
fonte
O que você quer dizer com "i18n"?
skrrgwasme
Isso era preferível a usar restrições na rota? Obrigado!
Timothy T.
@Scott: i18n significa 'internacionalização' - basicamente, como você suportaria vários idiomas. Eu realmente não olhei para isso, então não estou muito familiarizado com como funciona ou como implementá-lo.
Draknor de
@Angela - provavelmente não :) E, na verdade, depois de refatorar meu código, simplesmente criei vários formulários, cada um com ações diferentes, em vez de um único formulário monolítico que continha um monte de formulários não relacionados.
Draknor de
1

Eu tenho um número variável de botões de envio em meu formulário graças a nested_form_fields, então apenas usar o nome não foi suficiente para mim. Acabei incluindo um campo de entrada oculto no formulário e usando Javascript para preenchê-lo quando um dos botões de envio do formulário foi pressionado.

Shawn Walton
fonte