はじめに
C#の.NET FrameworkのテンプレートエンジンRazorにはinput要素を作るヘルパーが用意されており、簡単にテキストボックスやチェックボックスが作ることができるようになっています。
さて、ラジオボタンを作るRadioButtonForというヘルパーがあるのですが、これがなかなか使いづらく、動的に要素を作るような場面だと途端に実装の難易度が上がってしまいます。また、チェックされているかどうかを属性で与えるのですが、これもなかなか扱いづらく、結局ラジオボタンを敬遠する……ということになりがちです。
調べたところ、Mvc3futuresというライブラリを入れるとそこそこいい感じに扱えるらしいとか、まあ何かしらの解決策は見つかるのですが、せっかくなので自分でほしいものを作ってみました。
ちょっと異色のRadioButtonHelper - miso_soup3 Blog
https://miso-soup3.hateblo.jp/entry/20120808/1344442219
RadioButtonListItemとRadioButtonListItemAccessor
まずは、以下のようなクラスを作ります。RadioButtonListItemは完全にSelectListItemのパクリです。というかそもそもSelectListItemがあるのにRadioButtonListItemがないのがおかしいんですよ。RadioButtonListItemAccessorはあとで使い方を紹介します。
public class RadioButtonListItem
{
public string Value { get; set; }
public string Text { get; set; }
public bool IsChecked { get; set; }
}
public class RadioButtonListItemAccessor
{
public MvcHtmlString Radio { get; set; }
public string Text { get; set; }
}
RadioButtonListForとCreateRadioButtonListItemCollection
では、次にHtmlHelperの拡張メソッドを作ります。
public static IEnumerable<RadioButtonListItemAccessor> RadioButtonListFor<TModel, TValue>(this HtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression, IEnumerable<RadioButtonListItem> items, IDictionary<string, object> htmlAttributes = null)
{
return items.Select(item => {
var htmlAttributesOfThis = htmlAttributes ?? new Dictionary<string, object>();
if (item.IsChecked)
{
htmlAttributesOfThis["checked"] = item.IsChecked;
}
return new RadioButtonListItemAccessor()
{
Radio = InputExtensions.RadioButtonFor(html, expression, item.Value, htmlAttributesOfThis),
Text = item.Text
};
});
}
渡されたRadioButtonListItemのコレクションから動的にラジオボタンを作ります。
注意するべきなのは、DropDownListForがただちにドロップダウンリストを作るのに対し、こちらはRadioButtonListItemではなくRadioButtonListItemAccessorのイテレータを返すことです。
これは、たとえば次のようにラジオボタンとラベルを別々に扱うことにより柔軟なスタイルやレイアウトを可能にするためです。
<input type="radio" for="myradio"><label id="myradio" class="mystyle">hoge</label>
<input type="radio" for="myradio2"><label id="myradio2" class="mystyle2">fuga</label>
Razor上では次のように使います。DropDownListForと違いループで回す必要があるのが少し面倒くさいですが、それは上述のような理由によるためです。
@foreach (var pref in Html.RadioButtonListFor(model => Model.GenderId, Model.GenderList))
{
<label class="radio-inline">
@pref.Radio
@pred.Text
</label>
}
RadioButtonListを作るのが面倒くさい
これで一件落着と思いきや、RadioButtonListを作るのが面倒くさいのです。ただ単にマスターデータを表示するだけでいいならこれで十分なのですが、今モデルが手元にありそのモデルの編集画面で該当する項目を選択した状態で画面を表示したいとなるとまた面倒です。マスターデータとラジオボタンで表示するプロパティ名(上述で言うところの「GenderId」)が分かっていれば、それらを適当なメソッドに突っ込むだけでいい感じのRadioButtonListを作ってもらえるようにしましょう。
public static class HtmlHelper {
public static ICollection<RadioButtonListItem> CreateRadioButtonListItemCollection<T>(IEnumerable<T> list, string checkedValue, string valueProp, string textProp)
{
return list.Select(item => new RadioButtonListItem()
{
Value = item.GetType().GetProperty(valueProp).GetValue(item).ToString(),
Text = item.GetType().GetProperty(textProp).GetValue(item).ToString(),
IsChecked = item.GetType().GetProperty(valueProp).GetValue(item).ToString() == checkedValue
}).ToList();
}
}
マスターデータの方もモデルとなっていると思うので、そのモデルのリストとvalueとして扱うプロパティ名(具体的にはIDやコードなどが入る)とラベルとして扱うプロパティ名(Genderの例でいえば「女性」とか「男性県」とか「その他」とか)を渡します。そしてチェックを入れる値も渡します。
ビューモデルの方では次のようにするとよいです。
// Genderクラスはこんな感じ
public class Gender {
public int Id { get; set; }
public string Name { get; set; }
}
public class UsserViewModel {
public int GenderId { get; set; }
ICollection<RadioButtoListItem> GenderRadios { get; set; }
// コンストラクタなどで
GenderRadios = HtmlHelper.CreateRadioButtonListItemCollection(genders, GenderId, "Id", "Name");
}
SelectListItemのリストも作りたい
CreateRadioButtonnListItemCollectionはとても便利です。SelectListItem版も作っておきましょう。
public static ICollection<SelectListItem> CreateSelectListItemCollection<T>(IEnumerable<T> list, string selectedValue, string valueProp, string textProp)
{
return list.Select(item => new SelectListItem()
{
Value = item.GetType().GetProperty(valueProp).GetValue(item).ToString(),
Text = item.GetType().GetProperty(textProp).GetValue(item).ToString(),
Selected = item.GetType().GetProperty(valueProp).GetValue(item).ToString() == selectedValue
}).ToList();
}}
以上
これで残念な子と呼ばれるRadioButtonForを扱いやすくすることができました。以上です。