Основываясь на советах Дэвида Лиддла, я нашел другой дизайн, который был немного более элегантным.Он использует больше jQuery и меньше частичных представлений и запросов Ajax.
Вместо того чтобы добавлять группу DropDownLists, я решил использовать таблицу, пару выпадающих списков и кнопку «Добавить».Когда пользователь выбирает параметр «Тип» в первом раскрывающемся списке, по-прежнему используется ajax для получения частичного представления для заполнения второго раскрывающегося списка «Значение».После выбора параметра «Значение» пользователь нажимает кнопку «Добавить».
Используя jQuery, на страницу добавляются два скрытых ввода.Соглашение об именах в ссылках от Дэвида используется для именования этих элементов (comps [0] .Type и comps [0] .Value).Кроме того, в таблицу добавляется новая строка с такими же типом и значением для визуальной обратной связи с пользователем, показывающей, что было добавлено.
Я также определил класс Component, который просто имеет свойства Type и Value, и добавил List в модель.В представлении я перебираю этот список и добавляю все компоненты модели в таблицу и в качестве скрытых входных данных.
IndexView
<table id="componentTable">
<tr>
<th>Type</th>
<th>Deploy From</th>
</tr>
<% foreach (Component comp in Model.comps) { %>
<tr>
<td><%= Html.Encode(comp.Type) %></td>
<td><%= Html.Encode(comp.Value) %></td>
</tr>
<% } %>
</table>
<div id="hiddenComponentFields">
<% var index = 0;%>
<% foreach (Component comp in Model.comps) { %>
<input type="hidden" name="comps[<%= Html.Encode(index) %>].Type" value="<%= Html.Encode(comp.Type) %>" />
<input type="hidden" name="comps[<%= Html.Encode(index) %>].Value" value="<%= Html.Encode(comp.value) %>" />
<% index++; %>
<% } %>
</div>
<%= Html.DropDownList("ComponentTypeDropDown", Model.ComponentTypes, "", new { onchange = "updateCompValues();"}) %>
<span id="CompValueContainer">
<% Html.RenderPartial("ComponentValueSelector", new ComponentValueModel()); %>
</span>
<span class="button" id="addComponentButton" onclick="AddComponentButtonClicked()">Add the File</span>
<span id="componentStatus"></span>
ComponentValueSelector PartialView
<%@ Control Language="C#" Inherits="ViewUserControl<ComponentValueModel>" %>
<% if(Model.SelectList == null) { %>
<select id="CompValue" name="CompValue" disabled="true">
<option></option>
</select>
<% } else { %>
<%= Html.DropDownList("CompValue", Model.SelectList, "") %>
<% } %>
jQuery
function updateCompValues() {
$.ajax({
url: '<%= Url.Action("GetComponentValues") %>',
async: true,
type: 'POST',
data: { type: $("#CompValue").value },
dataType: 'text',
success: function(data) { $("#CompValueContainer").html(data); enable($("#CompValue")) },
error: function() {
console.log('Erreur');
}
});
}
function AddComponentButtonClicked() {
UpdateCompStatus("info", "Updating...");
var type = $("#ComponentTypeDropDown").val();
var value = $("#CompValue").val();
if (type == "" || value == "") { // No values selected
UpdateCompStatus("warning", "* Please select both a type and a value");
return; // Don't add the component
}
AddComponent(type, value);
}
function AddComponent(type, setting_1) {
// Add hidden fields
var newIndex = GetLastCompsIndex() + 1;
var toAdd = '<input type="hidden" name="comps[' + newIndex + '].Type" value="' + type + '" />' +
'<input type="hidden" name="comps[' + newIndex + '].Setting_1" value="' + setting_1 + '" />';
$("#hiddenComponentFields").append(toAdd);
// Add to page
// Note: there will always be one row of headers so the selector should always work.
$('#componentTable tr:last').after('<tr><td>'+type+'</td><td>'+setting_1+'</td>remove</tr>');
}
function GetLastCompsIndex() {
// TODO
alert("GetLastCompsIndex unimplemented!");
// haven't figured this out yet but something like
// $("#hiddenComponentFields input:hidden" :last).useRegExToExtractIndexFromName(); :)
}
function UpdateCompStatus(level, message) {
var statusSpan = $("#componentStatus");
// TODO Change the class to reflect level (warning, info, error?, success?)
// statusSpan.addClassName(...)
statusSpan.html(message);
}
Контроллер
public ActionResult Index() {
SelectList compTypes = repos.GetAllComponentTypesAsSelectList();
return View(new IndexViewModel(compTypes));
}
[AcceptVerbs(HttpVerbs.Post)]
public ActionResult Index(Component[] comps, other params...) {
foreach(Component comp in comps) {
// Do something with comp.Type and comp.Value
}
return RedirectToAction(...);
}
public ActionResult GetComponentValues(string type) {
ComponentValueModel valueModel = new ComponentValueModel();
valueModel.SelectList = repos.GetAllComponentValuesForTypeAsSelectList(type);
return PartialView("ComponentValueSelector", valueModel);
}
IndexViewModel
public class IndexViewModel {
public List<Component> comps { get; set; }
public SelectList ComponentTypes { get; set; }
public IndexViewModel(SelectList types) {
comps = new List<Component>();
ComponentTypes = types;
}
}