Posted at

ラジオボタンが非選択のときにブラウザが値を送信してくれない事象をsimple_formで何とかする方法

More than 5 years have passed since last update.


困ったこと

ラジオボタンが非選択状態のとき、ブラウザはラジオボタン項目の値を送信してくれないのです(値がないから当然ですよねー)。

「非選択状態」という状態を保存したいとき、これだと保存されなくて困る。詰む。


解決方法


前提

rails4 + simple_form + twitter bootstrap3のマークアップです。


simple_form側の実装

こちらの記事で紹介されている方法をsimple_formで実装します。

radioボタンが非選択時、値を送信しないのに対応する


/app/inputs/collection_radio_buttons_input.rb

class CollectionRadioButtonsInput < SimpleForm::Inputs::CollectionRadioButtonsInput

# @override
def collection
if translate(:options).present?
translate(:options)
.to_a
.map { |elem| [elem.last, elem.first] }
.push(['未回答', '']) # <= 空の項目を常に追加するようにする
else
super
end
end

# @override
def apply_nested_boolean_collection_options!(options)
options[:item_wrapper_tag] = nil
end

# @override
def build_nested_boolean_style_item_tag(collection_builder)
hidden_class = collection_builder.value.blank? ? ' hidden' : ''
collection_builder.label(class: item_wrapper_class + hidden_class) {
collection_builder.radio_button + collection_builder.text
}
end

# @override
def item_wrapper_class
'radio-inline'
end
end


simple_formは基本的にオーバーライドして振る舞いを変えられるのが便利です。

collectionメソッドはラジオボタンの項目数が多すぎてもsimple_formでそこそこすっきり書く方法で紹介した、選択項目の値をconfig/locals/以下のファイルに書いてしまう方法を取り入れつつ、翻訳が存在する場合のみ空の項目を常に追加するようにしています(booleanのラジオボタンのときに誤判定する可能性もあるので、その辺考慮した方が良いです)。

こうやって空の項目を追加することで、「空白の値を持つラジオボタン」を作り出しているのです。

空白の値を持つラジオボタンは表示されないで欲しいので、build_nested_boolean_style_item_tagメソッドをオーバーライドして、空白の値の場合はhiddenクラスを追加するようにしています。hiddenにはdisplay: noneやらそんな感じのスタイルが定義されているので、表示上見えなくなるという寸法です。


ビュー側の実装

非選択状態のときは、内部的には空白の値を持つラジオボタンが選択されている状態でなければなりません。

そんな訳で、そういう風になるようにJavaScriptで制御してあげる必要があります。

今回は既に選択してあるラジオボタンをクリックすると、ラジオボタンが非選択状態になる、という振る舞いを実現します。

beforeRadioValue = null

$('input:radio').on 'mousedown', ->
beforeRadioValue = $("input:radio[name='#{$(this).attr('name')}']:checked").val()
.on 'click', ->
if $(this).val() == beforeRadioValue
# 既に選択してあるラジオボタンをクリックしたときに、空白の値を持つラジオボタンが選択されるようにする
$(this).closest('.form-group').find("input:radio[value='']").prop('checked', true)

これでサーバに空白の値が送信されるようになります。めでたしめでたし。