Razorビューで住所のカスケードドロップダウンリストを実装する方法に興味があります。個人用サイトエンティティにはSuburbIdプロパティがあります。郊外にはCityIdがあり、市にはProvinceIdがあります。サイトビューで郊外、市、県のすべてのドロップダウンを表示したいと思います。郊外のドロップダウンには、最初に「最初に都市を選択」と表示され、市のドロップダウンには「最初に都市を選択」と表示されます。州を選択すると、その州の都市などが入力されます。
どうすればこれを達成できますか?どこから始めますか?
例で説明しましょう。常にモデルから始めます:
public class MyViewModel
{
public string SelectedProvinceId { get; set; }
public string SelectedCityId { get; set; }
public string SelectedSuburbId { get; set; }
public IEnumerable<Province> Provinces { get; set; }
}
public class Province
{
public string Id { get; set; }
public string Name { get; set; }
}
次にコントローラー:
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new MyViewModel
{
// TODO: Fetch those from your repository
Provinces = Enumerable.Range(1, 10).Select(x => new Province
{
Id = (x + 1).ToString(),
Name = "Province " + x
})
};
return View(model);
}
public ActionResult Suburbs(int cityId)
{
// TODO: Fetch the suburbs from your repository based on the cityId
var suburbs = Enumerable.Range(1, 5).Select(x => new
{
Id = x,
Name = "suburb " + x
});
return Json(suburbs, JsonRequestBehavior.AllowGet);
}
public ActionResult Cities(int provinceId)
{
// TODO: Fetch the cities from your repository based on the provinceId
var cities = Enumerable.Range(1, 5).Select(x => new
{
Id = x,
Name = "city " + x
});
return Json(cities, JsonRequestBehavior.AllowGet);
}
}
そして最後にビュー:
@model SomeNs.Models.MyViewModel
@{
ViewBag.Title = "Home Page";
}
<script type="text/javascript" src="/scripts/jquery-1.4.4.js"></script>
<script type="text/javascript">
$(function () {
$('#SelectedProvinceId').change(function () {
var selectedProvinceId = $(this).val();
$.getJSON('@Url.Action("Cities")', { provinceId: selectedProvinceId }, function (cities) {
var citiesSelect = $('#SelectedCityId');
citiesSelect.empty();
$.each(cities, function (index, city) {
citiesSelect.append(
$('<option/>')
.attr('value', city.Id)
.text(city.Name)
);
});
});
});
$('#SelectedCityId').change(function () {
var selectedCityId = $(this).val();
$.getJSON('@Url.Action("Suburbs")', { cityId: selectedCityId }, function (suburbs) {
var suburbsSelect = $('#SelectedSuburbId');
suburbsSelect.empty();
$.each(suburbs, function (index, suburb) {
suburbsSelect.append(
$('<option/>')
.attr('value', suburb.Id)
.text(suburb.Name)
);
});
});
});
});
</script>
<div>
Province:
@Html.DropDownListFor(x => x.SelectedProvinceId, new SelectList(Model.Provinces, "Id", "Name"))
</div>
<div>
City:
@Html.DropDownListFor(x => x.SelectedCityId, Enumerable.Empty<SelectListItem>())
</div>
<div>
Suburb:
@Html.DropDownListFor(x => x.SelectedSuburbId, Enumerable.Empty<SelectListItem>())
</div>
改善として、jqueryプラグインを記述して一部の重複を回避することにより、javascriptコードを短縮できます。
更新:
そして、プラグインについて話すと、次のようなものがあります。
(function ($) {
$.fn.cascade = function (options) {
var defaults = { };
var opts = $.extend(defaults, options);
return this.each(function () {
$(this).change(function () {
var selectedValue = $(this).val();
var params = { };
params[opts.paramName] = selectedValue;
$.getJSON(opts.url, params, function (items) {
opts.childSelect.empty();
$.each(items, function (index, item) {
opts.childSelect.append(
$('<option/>')
.attr('value', item.Id)
.text(item.Name)
);
});
});
});
});
};
})(jQuery);
そして、単にそれを配線します:
$(function () {
$('#SelectedProvinceId').cascade({
url: '@Url.Action("Cities")',
paramName: 'provinceId',
childSelect: $('#SelectedCityId')
});
$('#SelectedCityId').cascade({
url: '@Url.Action("Suburbs")',
paramName: 'cityId',
childSelect: $('#SelectedSuburbId')
});
});
Darin ソリューションへのリードをありがとう。ポイントに到達するのに大いに役立ちました。しかし、「xxviktor」が言及したように、私は循環参照を得ました。エラー。それを取り除くために、私はこのようにしました。
public string GetCounties(int countryID)
{
List<County> objCounties = new List<County>();
var objResp = _mastRepo.GetCounties(countryID, ref objCounties);
var objRetC = from c in objCounties
select new SelectListItem
{
Text = c.Name,
Value = c.ID.ToString()
};
return new JavaScriptSerializer().Serialize(objRetC);
}
自動カスケードを実現するために、jQuery拡張機能をこのように少し拡張しました。
$('#ddlCountry').cascade({
url: '@Url.Action("GetCounties")',
paramName: 'countryID',
childSelect: $('#ddlState'),
childCascade: true
});
そして、実際のJSはこのパラメーターを以下のように使用しています(JSON要求内)。
// trigger child change
if (opts.childCascade) {
opts.childSelect.change();
}
これが同様の問題を抱えている人の助けになることを願っています。
このソリューションはEF 4.0では直接機能しないことに注意してください。 「シリアル化中に循環参照が検出されました...」エラーが発生します。ここに可能な解決策があります http://blogs.telerik.com/atanaskorchev/posts/10-01-25/resolving_circular_references_when_binding_the_mvc_grid.aspx 、私は使用しました二つ目。
MVCに組み込まれた検証とバインディングをサポートするカスケードドロップダウンリストを実装するには、ここの他の回答で行われていることとは少し違うことをする必要があります。
モデルに検証がある場合、これはそれをサポートします。検証付きのモデルからの抜粋:
_[Required]
[DisplayFormat(ConvertEmptyStringToNull = false)]
public Guid cityId { get; set; }
_
コントローラーでgetメソッドを追加して、ビューが後で関連データを取得できるようにする必要があります。
_[AcceptVerbs(HttpVerbs.Get)]
public JsonResult GetData(Guid id)
{
var cityList = (from s in db.City where s.stateId == id select new { cityId = s.cityId, name = s.name });
//simply grabbing all of the cities that are in the selected state
return Json(cityList.ToList(), JsonRequestBehavior.AllowGet);
}
_
さて、先ほど述べたビューに:
ビューには、次のような2つのドロップダウンがあります。
_<div class="editor-label">
@Html.LabelFor(model => model.stateId, "State")
</div>
<div class="editor-field">
@Html.DropDownList("stateId", String.Empty)
@Html.ValidationMessageFor(model => model.stateId)
</div>
<div class="editor-label">
@Html.LabelFor(model => model.cityId, "City")
</div>
<div class="editor-field">
@*<select id="cityId"></select>*@
@Html.DropDownList("cityId", String.Empty)
@Html.ValidationMessageFor(model => model.cityId)
</div>
_
ドロップダウンのコンテンツはコントローラーによってバインドされ、自動的に入力されます。注:私の経験では、このバインディングを削除し、Javaスクリプトを使用してドロップダウンにデータを入力します。検証が失われます。さらに、ここでバインドしている方法は検証に適していますそれを変更します。
次に、jQueryプラグインに進みます。
_(function ($) {
$.fn.cascade = function (secondaryDropDown, actionUrl, stringValueToCompare) {
primaryDropDown = this; //This doesn't necessarily need to be global
globalOptions = new Array(); //This doesn't necessarily need to be global
for (var i = 0; i < secondaryDropDown.options.length; i++) {
globalOptions.Push(secondaryDropDown.options[i]);
}
$(primaryDropDown).change(function () {
if ($(primaryDropDown).val() != "") {
$(secondaryDropDown).prop('disabled', false); //Enable the second dropdown if we have an acceptable value
$.ajax({
url: actionUrl,
type: 'GET',
cache: false,
data: { id: $(primaryDropDown).val() },
success: function (result) {
$(secondaryDropDown).empty() //Empty the dropdown so we can re-populate it
var dynamicData = new Array();
for (count = 0; count < result.length; count++) {
dynamicData.Push(result[count][stringValueToCompare]);
}
//allow the empty option so the second dropdown will not look odd when empty
dynamicData.Push(globalOptions[0].value);
for (var i = 0; i < dynamicData.length; i++) {
for (var j = 0; j < globalOptions.length; j++) {
if (dynamicData[i] == globalOptions[j].value) {
$(secondaryDropDown).append(globalOptions[j]);
break;
}
}
}
},
dataType: 'json',
error: function () { console.log("Error retrieving cascading dropdown data from " + actionUrl); }
});
}
else {
$(secondaryDropDown).prop('disabled', true);
}
secondaryDropDown.selectedindex = 0; //this prevents a previous selection from sticking
});
$(primaryDropDown).change();
};
} (jQuery));
_
作成した上記のjQueryを、ビューの_<script>...</script>
_タグにコピーするか、必要に応じて別のスクリプトファイルにコピーできます(ただし、クロスブラウザーにするためにこれを更新しましたが、使用しているシナリオは不要になりましたが、動作するはずです)。
これらの同じスクリプトタグで(個別のファイルではなく)、次のjavascriptを使用してプラグインを呼び出すことができます。
_$(document).ready(function () {
var primaryDropDown = document.getElementById('stateId');
var secondaryDropdown = document.getElementById('cityId');
var actionUrl = '@Url.Action("GetData")'
$(primaryDropDown).cascade(secondaryDropdown, actionUrl);
});
_
$(document).ready
パーツを追加することを忘れないでください。ドロップダウンをカスケードする前に、ページを完全にロードする必要があります。
<script src="~/Scripts/jquery-1.10.2.min.js"></script>
<script type="text/javascript">
$(document).ready(function () {
//Dropdownlist Selectedchange event
$("#country").change(function () {
$("#State").empty();
$.ajax({
type: 'POST',
url: '@Url.Action("State")', // we are calling json method
dataType: 'json',
data: { id: $("#country").val() },
// here we are get value of selected country and passing same value
success: function (states) {
// states contains the JSON formatted list
// of states passed from the controller
$.each(states, function (i, state) {
$("#State").append('<option value="' + state.Value + '">' +
state.Text + '</option>');
// here we are adding option for States
});
},
error: function (ex) {
alert('Failed to retrieve states.' + ex);
}
});
return false;
})
});
</script>
<div>
@Html.DropDownList("country", ViewBag.country as List<SelectListItem>, "CountryName", new { style = "width: 200px;" })
</div>
<div>
</div>
<div>
@Html.DropDownList("State", ViewBag.country as List<SelectListItem>)
</div>
コントローラーから値を取得しています
public async Task<ActionResult> Country()
{
Country co = new Country();
List<SelectListItem> li = new List<SelectListItem>();
li.Add(new SelectListItem { Text = "Select", Value = "0" });
li.Add(new SelectListItem { Text = "India", Value = "1" });
li.Add(new SelectListItem { Text = "Nepal", Value = "2" });
li.Add(new SelectListItem { Text = "USA", Value = "3" });
li.Add(new SelectListItem { Text = "Kenya", Value = "4" }); ;
ViewBag.country= li;
return View();
}
public JsonResult state(string id)
{
List<SelectListItem> states = new List<SelectListItem>();
states.Add(new SelectListItem { Text = "--Select State--", Value = "0" });
switch (id)
{
case "1":
states.Add(new SelectListItem { Text = "MP", Value = "1" });
states.Add(new SelectListItem { Text = "UP", Value = "2" });
break;
case "3":
states.Add(new SelectListItem { Text = "USA1", Value = "3" });
states.Add(new SelectListItem { Text = "USA2", Value = "4" });
break;
}
return Json(new SelectList(states, "Value", "Text", JsonRequestBehavior.AllowGet));
}