ruby on rails f.selecionar opções com atributos personalizados

115

Eu tenho uma declaração de seleção de formulário, como esta:

= f.select :country_id, @countries.map{ |c| [c.name, c.id] }

O que resulta neste código:

...
<option value="1">Andorra</option>
<option value="2">Argentina</option>
...

Mas eu quero adicionar um atributo HTML personalizado às minhas opções, como este:

...
<option value="1" currency_code="XXX">Andorra</option>
<option value="2" currency_code="YYY">Argentina</option>
...
el_quick
fonte
2
O Rails não fornece essa funcionalidade, você terá que criar um helper para criar essa marcação. Além disso, tenha em mente que o exemplo que você mencionou não é um HTML válido.
Augusto
Eu sei, meu exemplo não é html válido ... Acho que tenho que mudar minha maneira de obter os resultados que desejo, thk!
el_quick

Respostas:

356

Rails PODEM adicionar atributos personalizados para selecionar opções, usando o helper options_for_select existente. Você quase acertou no código da sua pergunta. Usando atributos de dados html5:

<%= f.select :country_id, options_for_select(
    @countries.map{ |c| [c.name, c.id, {'data-currency_code'=>c.currency_code}] }) %>

Adicionando uma seleção inicial:

<%= f.select :country_id, options_for_select(
    @countries.map{ |c| [c.name, c.id, {'data-currency_code'=>c.currency_code}] }, 
    selected_key = f.object.country_id) %>

Se precisar de opções agrupadas, você pode usar o auxiliar grouped_options_for_select, como este (se @continents for uma matriz de objetos de continente, cada um tendo um método de países):

<%= f.select :country_id, grouped_options_for_select(
    @continents.map{ |group| [group.name, group.countries.
    map{ |c| [c.name, c.id, {'data-currency_code'=>c.currency_code}] } ] }, 
    selected_key = f.object.country_id) %>

O crédito deve ir para paul @ pogodan, que postou sobre como encontrar isso não na documentação, mas lendo a fonte do rails. https://web.archive.org/web/20130128223827/http://www.pogodan.com/blog/2011/02/24/custom-html-attributes-in-options-for-select

Anatortoise House
fonte
6

Você pode fazer isso da seguinte maneira:

= f.select :country_id, @countries.map{ |c| [c.name, c.id, { 'data-currency-code' => c.currency_code} ] }
Nikhil Gupte
fonte
Correto, mas já mencionado na resposta da Anatortoise House.
Kelvin
A resposta aceita não ilustra os atributos personalizados usando ruby. Este sim, portanto, sinto que é melhor, pois é a primeira resposta que mostra como fazê-lo via ruby.
Nikhil Gupte
5

Isso não é possível diretamente com Rails, e você terá que criar seu próprio helper para criar os atributos personalizados. Dito isso, provavelmente existem duas maneiras diferentes de realizar o que você deseja:

(1) Usando um nome de atributo personalizado em HTML5. Em HTML5, você pode ter nomes de atributos personalizados , mas eles devem ser pré-pendurados com 'data-'. Esses atributos personalizados não serão enviados com seu formulário, mas podem ser usados ​​para acessar seus elementos em Javascript. Se você quiser fazer isso, recomendo criar um auxiliar que gere opções como estas:

<option value="1" data-currecy-code="XXX">Andorra</option>

(2) Uso de valores com divisão personalizada para enviar dados adicionais. Se você realmente deseja enviar o código da moeda, recomendo criar sua caixa de seleção como esta:

= f.select :country_id, @countries.map{ |c| [c.name, "#{c.id}:#{c.currency_code}"] }

Isso deve gerar um HTML parecido com este:

<option value="1:XXX">Andorra</option>
<option value="2:YYY">Argentina</option>

Que você pode analisar em seu controlador:

@id, @currency_code = params[:country_id].split(':')
Pan Thomakos
fonte
3
Ótima resposta. Optei pela abordagem número 1 e escrevi em um blog como fiz o ajudante, caso isso ajude mais alguém. redguava.com.au/2011/03/…
Joel Friedlaender
Concorde com outros comentadores - veja a resposta de Anatortoise, abaixo!
Jacob,
23
>>>>>>>>>>>>>> RESPOSTA ERRADA ... MANTENHA ROLAGEM <<<<<<<<<<<<<<<
stevenspiel
1
Isso é diretamente possível no Rails. Veja: stackoverflow.com/a/9076805/380607
Magne
Pan, exclua esta resposta enganosa.
vemv de
4

O hash de atributos extras só é suportado no Rails 3.

Se você está no Rails 2.x , e deseja substituiroptions_for_select

Basicamente, apenas copiei o código do Rails 3. Você precisa substituir estes 3 métodos:

def options_for_select(container, selected = nil)
    return container if String === container
    container = container.to_a if Hash === container
    selected, disabled = extract_selected_and_disabled(selected)

    options_for_select = container.inject([]) do |options, element|
      html_attributes = option_html_attributes(element)
      text, value = option_text_and_value(element)
      selected_attribute = ' selected="selected"' if option_value_selected?(value, selected)
      disabled_attribute = ' disabled="disabled"' if disabled && option_value_selected?(value, disabled)
      options << %(<option value="#{html_escape(value.to_s)}"#{selected_attribute}#{disabled_attribute}#{html_attributes}>#{html_escape(text.to_s)}</option>)
    end

    options_for_select.join("\n").html_safe
end

def option_text_and_value(option)
  # Options are [text, value] pairs or strings used for both.
  case
  when Array === option
    option = option.reject { |e| Hash === e }
    [option.first, option.last]
  when !option.is_a?(String) && option.respond_to?(:first) && option.respond_to?(:last)
    [option.first, option.last]
  else
    [option, option]
  end
end

def option_html_attributes(element)
  return "" unless Array === element
  html_attributes = []
  element.select { |e| Hash === e }.reduce({}, :merge).each do |k, v|
    html_attributes << " #{k}=\"#{ERB::Util.html_escape(v.to_s)}\""
  end
  html_attributes.join
end

Meio confuso, mas é uma opção. Coloco esse código em um módulo auxiliar chamado, o RailsOverridesqual incluo em ApplicationHelper. Você também pode fazer um plugin / gem se preferir.

Uma pegadinha é que, para tirar proveito desses métodos, você deve sempre invocar options_for_selectdiretamente. Atalhos como

select("post", "person_id", Person.all.collect {|p| [ p.name, p.id, {"data-stuff"=>"html5"} ] })

produzirá os resultados antigos. Em vez disso, deveria ser:

select("post", "person_id", options_for_select(Person.all.collect {|p| [ p.name, p.id, {"data-stuff"=>"html5"} ] }))

Novamente, não é uma ótima solução, mas pode valer a pena chegar ao sempre tão útil atributo de dados.

mastaBlasta
fonte