David Liddle からのアドバイスに基づいて、もう少しエレガントな別のデザインを見つけました。より多くの jQuery を使用し、部分ビューと Ajax リクエストを減らします。
たくさんの DropDownList を追加する代わりに、テーブル、ドロップダウンのペア、および [追加] ボタンを使用することにしました。ユーザーが最初のドロップダウンで [タイプ] オプションを選択すると、2 番目の [値] ドロップダウンを設定するための部分ビューを取得するために、引き続き ajax が使用されます。値オプションを選択したら、ユーザーは [追加] ボタンをクリックします。
jQuery を使用して、2 つの非表示の入力がページに追加されます。David からのリンクの命名規則は、これらの要素 (comps[0].Type および comps[0].Value) の名前付けに使用されます。また、追加された内容をユーザーに視覚的にフィードバックするために、同じタイプと値を持つ新しい行がテーブルに追加されます。
また、Type プロパティと Value プロパティだけを持つ Component クラスを定義し、モデルに List を追加しました。ビューで、このリストを繰り返し処理し、モデル内のすべてのコンポーネントをテーブルに非表示の入力として追加します。
インデックスビュー
<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;
}
}