Предыстория: я работаю над своим первым приложением ASP.NET MVC 3 и хочу предоставить клиенту две похожие страницы - «Создать» и «Редактировать».
На обеих страницах есть два селектора DropDownList, которые позволяют клиенту сначала выбрать тип конуса или блюда, а затем выбрать подтип
на основе первого выбора (например, клиент выбирает «вафельный конус», а затем ему предоставляются подтипы, такие как «шоколадный соус»,
"обычный" и т. д.).
Я набираю событие 'change' в селекторе ConeDish
, чтобы вызвать действие Controller для возврата связанных подтипов. Примерно так:
View
<td>@Html.DropDownListFor(model => model.TypeID, new SelectList(Model.ConeDishTypes, "TypeID", "Name"), "-- Select Cone or Dish --", new { @id = "conedishselector" })
</td>
<td>@Html.DropDownListFor(model => model.SubtypeID, Enumerable.Empty<SelectListItem>(), "-- How Big? --", new { @id = "subtypeselector" })
</td>
Мой обработчик готовности документа для страницы Создать выглядит следующим образом:
<script type="text/javascript">
$(document).ready(function () {
$('#conedishselector').change(function () {
var selectedTypeID = $(this).val();
if (selectedTypeID != null) {
$.getJSON('@Url.Action("SubtypesForType")', { typeID: selectedTypeID }, function (subtypes) {
var subtypesSelect = $('#subtypeselector');
subtypesSelect.empty();
subtypesSelect.append($('<option/>', { value: 0, text: '-- How Big? --' }));
$.each(subtypes, function (index, subtype) {
subtypesSelect.append($('<option/>', {
value: subtype.SubtypeID,
text: subtype.Name
}));
});
});
}
});
});
</script>
Контроллер
И у меня есть действие контроллера, которое выглядит так:
public ActionResult SubtypesForType(int typeID)
{
using (var db = new IceCreamEntities())
{
IEnumerable<Subtypes> subtypes = db.Subtypes.Where(m => m.TypeID == typeID).ToList();
List<SubtypeIdentifierViewModel> subtypeVMs = new List<SubtypeIdentifierViewModel>();
foreach (var subtype in subtypes)
{
subtypeVMs.Add(new SubtypeIdentifierViewModel(subtype.SubtypeID, subtype.Name, subtype.Size));
}
return Json(subtypeVMs, JsonRequestBehavior.AllowGet);
}
}
По сути, это просто получение подтипов, для которых в действие передан соответствующий TypeID.
Я не думаю, что структура модели представления уместна для обсуждения, поэтому она не используется для экономии места.
Теперь этот бит для Create , кажется, работает довольно хорошо. Могут быть способы улучшить его (что я был бы рад услышать), но это достигает моей цели.
Но для страницы Edit у меня возникли трудности с отображением ранее выбранного подтипа в пользовательском интерфейсе. Мой текущий код скрипта выглядит так:
<script type="text/javascript">
$(document).ready(function () {
$('#conedishselector').change(function () {
var selectedTypeID = $(this).val();
if (selectedTypeID != null) {
$.getJSON('@Url.Action("SubtypesForType")', { typeID: selectedTypeID }, function (subtypes) {
var subtypesSelect = $('#subtypeselector');
subtypesSelect.empty();
subtypesSelect.append($('<option/>', { value: 0, text: '-- How Big? --' }));
$.each(subtypes, function (index, subtype) {
subtypesSelect.append($('<option/>', {
value: subtype.SubtypeID,
text: subtype.Name
}));
});
});
}
});
$('#conedishselector').trigger("change");
var theVal = $("#hiddensubtypeid").val();
$("#debug").val(theVal);
if (theVal != null && theVal != "") {
// alert here just to check
alert("I got here.");
$('#subtypeselector').val(theVal);
// I also tried this, but that didn't work without the alert either - didn't think it would though
// $("#subtypeselector option[value=" + val + "]").attr("selected", "selected");
}
});
</script>
По существу, поле hiddensubtypeid
на странице содержит ранее выбранный идентификатор для редактируемого здесь мороженого, и сценарий использует это значение для установки селектора, который был заполнен соответствующими подтипами, как это было сделано в Create
скрипт страницы.
И здесь я, возможно, не буду делать это «в соответствии с рекомендациями» или даже хорошим способом (то есть, используя это скрытое поле или вызывая изменение через триггер внутри обработчика готовности документа), но я не был уверен, как это сделать. сделать это иначе.
И при всем этом проблема, с которой я сталкиваюсь, заключается в том, что я считаю, что это проблема времени. Этот бит javascript непременно получит подтипы, связанные с конусом или тарелкой, которые были указаны ранее (этот бит не показан, но модель представления передала его)
и заполнить DropDownList выбранными типами, связанными с выбранным типом, но без этого отладочного предупреждения (которое я поместил только в код, потому что я не был уверен, что даже попал в условное выражение), установка подтипа не происходит.
По общему признанию, я мог подходить к этой проблеме неправильно, и я подозреваю, что мой очень синхронный мозг ожидает, что асинхронный мир будет работать так, как мой мозг смотрит на эту проблему, поэтому я буду признателен за любые советы по решению этой проблемы и улучшению моего дизайна. Я упустил часть кода вида и модели в основном потому, что не думаю, что он является центральным в обсуждении.
Немного больше информации из отдела "Это чувствуется хаки":
Если я попытаюсь использовать setTimeout
, чтобы отложить такие вещи:
$('#conedishselector').trigger("change");
$(function($) {
setTimeout(function() {
var theVal = $("#hiddensubtypeid").val();
$("#debug").val(theVal);
if (theVal != null && theVal != "") {
// alert here just to check
alert("I got here.");
$('#subtypeselector').val(theVal);
}
}, 200); // or 500, or 1000
});
Все будет работать, но я думаю, что это в лучшем случае решение проблемы с помощью клейкой ленты.
A Решение:
Так что я полагаю, что все это сводится к тому, что getJSON
является асинхронным вызовом ( Reference ), и это ясно из документов, так как он имеет успешный обратный вызов , который получает выполняется в какой-то момент в будущем , если запрос завершится успешно. Так что это значит? Это означает, что перебор функции готовности документа, вызов $('#yourfavoriteselector').trigger("change");
, а затем немедленный поворот и ожидание результатов getJSON
является ошибочной логикой. Задержка ожидания, как я делал выше с помощью setTimeout
, является хаком и приводит к неоптимальному «решению» проблемы. Он опирается (плохо, я мог бы добавить) на мысль, что обратный вызов успеха сработает после ожидания 500 мс или в любое другое указанное время.
Я перестроил функцию так:
<script type="text/javascript">
$(document).ready(function () {
$('#conedishselector').change(function () {
var selectedTypeID = $(this).val();
if (selectedTypeID != null) {
$.getJSON('@Url.Action("SubtypesForType")', { typeID: selectedTypeID }, function (subtypes) {
var subtypesSelect = $('#subtypeselector');
subtypesSelect.empty();
subtypesSelect.append($('<option/>', { value: 0, text: '-- How Big? --' }));
$.each(subtypes, function (index, subtype) {
subtypesSelect.append($('<option/>', {
value: subtype.SubtypeID,
text: subtype.Name
}));
});
var theVal = $("#hiddensubtypeid").val();
if (theVal != null && theVal != "") {
$('#subtypeselector').val(theVal);
}
});
}
});
$('#conedishselector').trigger("change");
});
</script>
и, как предположил Бэджер, сделал выбор как часть обратного вызова успеха, который обеспечил заполнение подтипов.
Преимущество такого способа также заключается в том, что пользователь может изменить выбор первого DropDownList на какое-то новое значение (скажем, хочет ли он посмотреть, какие другие конусы / блюда мы предлагаем) и вернуть его к исходному значению. снова. Когда это происходит, скрытое поле по-прежнему содержит исходный подтип, соответствующий исходному основному типу, и код обратного вызова соответственно сбрасывает подтип DropDownList. Таким образом, пользователя не раздражает необходимость заново выбирать размер блюда или тип вафельного рожка. Мне кажется, это лишь немного уважения к пользователю и приятный побочный эффект представленного решения.
Так что это «решение» и, возможно, не лучшее решение, но я хотел бы проиллюстрировать его для полноты.