miso_soup3 Blog

主に ASP.NET 関連について書いています。

ASP.NET MVC で動的なドロップダウンリストを実装する

ASP.NET MVC で、動的なドロップダウンリストを実装する方法です。

f:id:miso_soup3:20130718140144p:plain

都道府県を選択すると、市町村のドロップダウンリストの項目の値が変わるという、
よくあるパターンです。

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);
    }

(コードの例では、都道府県と市町村のクラス、StateCity を使用しています。)

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 の中身は何れも以下の様に構成されます。

f:id:miso_soup3:20130718140551p:plain

ビュー

ビューの 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 )を参照。
★3 スクリプトの読み込み

jQueryknockout.js の読み込みを忘れずに行います。

以上、ASP.NET MVC で動的なドロップダウンリストを実装する方法でした。

追記 1

POST時に、市町村の選択値を送信したい時は、@Html.NameFor を使うと便利です。
モデルバインディングが行われるよう、適切な name を設定できます。

f:id:miso_soup3:20130718141126p:plain

が、残念ながら NameFor は MVC 4 から使えるメソッドなので、
MVC 4 以外の場合は、「name="CityId"」のように文字列で指定する必要があります。
(それか codeplex からコードをひっぱってくるか。。。)
Html や Json を柔軟に操作できる ASP.NET MVC の特徴が生きています。

追記 2

ここで紹介した例は、都道府県のドロップダウンリストをサーバー側で生成しています。
もし、都道府県も knocjout.js の支配下におきたい場合(ページを表示させた後、Ajaxで都道府県を読み込む場合)は、コードの例を こちら に用意したのでご参照ください。