ASP.NET MVC で動的なドロップダウンリストを実装する
ASP.NET MVC で、動的なドロップダウンリストを実装する方法です。
都道府県を選択すると、市町村のドロップダウンリストの項目の値が変わるという、
よくあるパターンです。
knockout.js がオススメ
よく見かけるのが、選択した値に関係なく、全てのデータを取得して、
jQuery で表示する項目を制御したりする実装。
パフォーマンスも悪いですし、コードが複雑になりがちです。
一般的には、必要なデータのみ取得して、jQuery で制御すると思いますが、
knockout.js を利用すると、コード量が少なくなり、読みやすくなるのでオススメです。
ということで
ここでは、クライアント側は、knockout.js と jQuery を使った方法について書きます。
Jsonデータを返すサーバー側は、ASP.NET MVC の JsonResult を使用します。
(ASP.NET Web API で Json を返す方法も記載しておきます。)
全体像はこんな感じです。
ページの表示
最初に、都道府県と市町村のドロップダウンリストを配置したページを用意します。
ここは、ASP.NET MVC の基本的な処理になります。
コントローラ側で、都道府県の SelectList を用意し、
モデル(CreateAddressModel クラス)を用意してビューに渡します。
public class AddressController : Controller { [HttpGet] public ActionResult Create() { // 県のドロップダウンリストのソースを作成 // 県のデータを取得。(実際は恐らくデータベースから取得) IEnumerable<State> states = State.CreateDefaultsState(); // 県のデータより、ドロップダウンリストのソースオブジェクト SelectList を作成。 // DataValueは、Idプロパティを、DataTextFieldは、Nameプロパティを指定。 var stateSelectList = new SelectList(states, "Id", "Name"); var model = new CreateAddressModel(); model.StateSelectList = stateSelectList; return View(model); }
Json を返す API の用意
市町村の Json データを取得する API を用意します。
ASP.NET MVC の JsonResult の場合
任意のコントローラに、(コードの例では先ほどの AddressController に、)
JsonResult を返すアクションメソッドを用意します。
Json データの元となるモデルは、SelectList を使っています。
SelectListは、ドロップダウンリストを表すオブジェクトモデルなので、
Text や、Value 等の適切なプロパティを持っています。
なので、ビュー側での生成以外でも、Json を返す時にも使えます。
(不要なプロパティも持っているので気持ち悪い方は、自分で定義したモデルを使用した方が良いです)
/// <summary> /// 都道府県Idより、市町村リストのJsonを返します /// </summary> /// <param name="stateId">都道府県Id</param> /// <returns></returns> [HttpGet] public ActionResult GetCities(int stateId) { // 市町村のデータを取得。(実際は恐らくデータベースから取得) IEnumerable<State> states = State.CreateDefaultsState(); var cities = states.Where(state => state.Id == stateId) .SelectMany(state => state.Cities) .ToList(); var citySelectList = new SelectList(cities, "Id", "Name"); return Json(citySelectList, JsonRequestBehavior.AllowGet); }
ASP.NET Web API の場合
JsonResult ではなく、ASP.NET Web API で Json を返す場合は、
Apiコントローラを用意し、以下のメソッドを記述します。
JsonResult の場合とほぼ同じで、違うところは
継承するクラスと、メソッド名、メソッドの戻り値くらいです。
public class CitiesController : ApiController { public System.Web.Mvc.SelectList Get(int stateId) { IEnumerable<State> states = State.CreateDefaultsState(); var cities = states.Where(state => state.Id == stateId) .SelectMany(state => state.Cities) .ToList(); var citySelectList = new System.Web.Mvc.SelectList(cities, "Id", "Name"); return citySelectList; } }
(ASP.NET Web API のルーティングを追加するのを忘れずに)
以上、ASP.NET MVC の JsonResult、または ASP.NET Web API で
市町村のJson データを返す API の実装になります。
Json の中身は何れも以下の様に構成されます。
ビュー
ビューの Html と JavaScript のコードです。
@model DynamicSelectList.Models.CreateAddressModel @Html.LabelFor(m => m.StateId, "都道府県") @Html.DropDownListFor(m => m.StateId, Model.StateSelectList, "都道府県を選択して下さい。", new { id = "StateSelectList" }) @Html.LabelFor(m => m.CityId, "市町村") @* ★2 data-bind を指定 *@ <select data-bind="options: cities, optionsText: 'Text', optionsValue: 'Value', value: selectedCitity, optionsCaption: '市町村を選択して下さい。', enable: cities().length !== 0"> </select> @* ★3 jQuery と knockout.js の読み込み *@ <script src="~/Scripts/jquery-1.8.2.js"></script> <script src="~/Scripts/knockout-2.3.0.js"></script> <script type="text/javascript"> $(function () { // ★1ViewModel を定義 var createAddressViewModel = { cities: ko.observableArray([]), selectedCitity: ko.observable() }; ko.applyBindings(createAddressViewModel); $("#StateSelectList").on('change', function () { //都道府県ドロップダウンリストの値が変更されたら var stateId = $(this).val(); if (!stateId) { createAddressViewModel.cities([]); } $.ajax({ type: 'GET', url: '/api/cities',// ← URL は適宜変更 data: { stateId: stateId }, dataType: 'json', cache: false, success: function (data) { //市町村のデータをバインド createAddressViewModel.cities(data); //★4 市町村ドロップダウンリストの選択値を空("市町村を選択して下さい")にする。 createAddressViewModel.selectedCitity(''); } }); }); }); </script>
★1 ViewModel を定義
knockout.js で使うための ViewModel を宣言し2つのプロパティを持たせています。
- cities : 市町村データ
- selectedCity : 選択された市町村の値
この2つの値を、Html の data-bind 属性で指定することで、(コードの★2 data-bind を指定)
ViewModel の値が変わった時に、Html の要素の値も合わせて変更(バインド)されます。
selectedCity は、市町村ドロップダウンリストの選択値を制御するために、用意しました。(★4)
★2 data-bind の各値について
data-bind で指定している各値について、簡単に補足します。
- options : ドロップダウンリストの項目にバインドさせるデータリストを指定(ViewModel の cities)
- optionsText : ドロップダウンリストの項目名を表すプロパティを指定(市町村 Json データの Text プロパティ)
- optionsValue : ドロップダウンリストの値を表すプロパティを指定(市町村 Json データの Value プロパティ)
- value : 選択する項目の値を指定
- optionsCaption : ドロップダウンリストの先頭の項目名を指定
- enable : ドロップダウンリストが有効となる条件を、bool で指定(都道府県が選択され、市町村のデータ件数が0件でない場合有効)
- enable に指定した条件が false の時は、disabled 属性が追加されます。(The "enable" binding を参照。)
- enable ではなく visible で設定すると、都道府県を選択していない時は、市町村ドロップダウンリストを非表示にできます。(The "visible" binding )を参照。
追記 1
POST時に、市町村の選択値を送信したい時は、@Html.NameFor を使うと便利です。
モデルバインディングが行われるよう、適切な name を設定できます。
が、残念ながら NameFor は MVC 4 から使えるメソッドなので、
MVC 4 以外の場合は、「name="CityId"」のように文字列で指定する必要があります。
(それか codeplex からコードをひっぱってくるか。。。)
Html や Json を柔軟に操作できる ASP.NET MVC の特徴が生きています。
追記 2
ここで紹介した例は、都道府県のドロップダウンリストをサーバー側で生成しています。
もし、都道府県も knocjout.js の支配下におきたい場合(ページを表示させた後、Ajaxで都道府県を読み込む場合)は、コードの例を こちら に用意したのでご参照ください。