0

我有一个包含各种输入的表格。我有一堆可选参数,它们有一些选择。我想允许用户通过以下方式选择这些可选参数:

首先,用户单击表单底部的“添加组件”按钮,按钮上方会出现两个新的下拉菜单。第一个下拉列表有一个可以选择的类型列表,第二个将被禁用。当用户在第一个下拉列表中选择一个有效选项时,我想用一些特定于指定类型的值填充第二个下拉列表。用户应该能够继续添加新组件(下拉列表对),直到添加了所有所需的可选组件。理想情况下,在填写完所有字段并添加所需组件之前,不会发布表单。

我的问题是:我该 如何设计,以便在提交表单并且出现错误时,动态添加的字段(组件)将保留在页面上并显示正确的值?

我计划让 Add Component 按钮成为检索局部视图的 Ajax.ActionLink:

<div id="divComponentHolder"></div>
<%= Ajax.ActionLink("Add a Component", "GetComponentSelector", new AjaxOptions { UpdateTargetId = "divComponentHolder", InsertionMode = InsertionMode.InsertAfter}) %>

这个局部视图看起来像这样:

   <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MVCAndWebFormsTest.Models.ComponentSelectorModel>" %>
   <%= Html.Encode("Type:")%>
   <%= Html.DropDownList("ComponentType", Model.ComponentTypes, "", new {onchange = "updateCompValues(this);"}) %>
   <%= Html.Encode("File/Folder:")%>
   <div id="selectdiv">
       <% Html.RenderPartial("ComponentValueSelector", Model.ComponentValues); %>
   </div> 
   <br/>
   <script type="text/javascript" language="javascript">
        function updateCompValues(obj) {
            $.ajax({
                url: <% Url.Action("GetCompValues") %>,
                async: true,
                type: 'POST',
                data: { type: obj.value },
                dataType: 'text',
                success: function(data) { $("#selectdiv").html(data); },
                error: function() {
                    console.log('Erreur');
                }
            });
        }
   </script>

并且 ComponentValueSelector 部分将非常简单:

    <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<MVCAndWebFormsTest.Controllers.ViewModels.ComponentValueModel>" %>
    <%= Html.DropDownList("CompValue", Model.SelectList) %>
4

2 回答 2

2

看看 MVC 中的提交列表,这里有一些有用的站点:

这对于提交您正在构建的动态 DOM 很有用。

另一种方法不是进行 ajax 调用来呈现局部视图,您总是可以使用 jquery 直接将元素添加到 DOM。例如,使用 jquery clone ( $('element').clone(); ) 方法来复制您的列表框,然后执行一些正则表达式来更改输入框的 ID,以便它们具有唯一的 ID/名称。

当您将这些“选择”的列表传递给您的控制器时,您必须将它们设置回您的模型并让您的视图遍历它们以显示添加的正确数量的选择。

这是一个简单的例子。这可能不是您自己的最佳实施方式,或者其他人可能有更好的想法。

看法

<% for (int i = 0; i < in Model.Results.Count; i++) { %>
    //render better HTML but you should get the point!
    <%= Html.Hidden("choices[" + i + "].ID", i) %>
    <%= Html.DropDownList("choices[" + i + "].Choice1", ...) %>
    <%= Html.DropDownList("choices[" + i + "].Choice2", ...) %>
<% } %>

- add button

jQuery

$('#addButton').click(function()
{
    //say if your choice drop downs were in a table then take the last
    //row and clone it
    var row = $('table tr:last').clone(true);
    var newId = //work out the new id from how many rows in the table

    //make sure to update the id and name parameters of inputs 
    //of the cloned row
    row.find(':input')
    .attr('id', function()
    {
       return $(this).attr('id').replace(/\[[\d+]\]/g, '[' + newlId + ']');
       //this replaces the cloned [id] with a new id
    })
    .attr('name', function()
    {
       return $(this).attr('name').replace(/\[[\d+]\]/g, '[' + newId + ']');
    });

    row.find(':hidden').val(newId); //update the value of the hidden input

    //alert(row.html()); //debug to check your cloned html is correct!
    //TODO: setup ajax call for 1st drop down list to render 2nd drop down

    $('table tr:last').after(row);//add the row

    return false;
});

控制器

public ActionResult YourMethod(IList<YourObject> choices, any other parameters)
{
   YourViewModel model = new YourViewModel();
   model.Results = choices; //where Results is IList<YourObject>

   return View(model);
}
于 2010-02-01T20:19:22.247 回答
0

根据 David Liddle 的建议,我发现了一种更优雅的不同设计。它使用更多的 jQuery 和更少的局部视图和 Ajax 请求。

我没有添加一堆 DropDownList,而是决定使用一个表格、一对下拉列表和一个“添加”按钮。当用户在第一个下拉列表中选择 Type 选项时,ajax 仍用于检索部分视图以填充第二个 Value 下拉列表。一旦选择了值选项,用户然后单击添加按钮。

使用 jQuery,两个隐藏的输入被添加到页面中。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 部分视图

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

索引视图模型

public class IndexViewModel {
    public List<Component> comps { get; set; }
    public SelectList ComponentTypes { get; set; }

    public IndexViewModel(SelectList types) {
        comps = new List<Component>();
        ComponentTypes = types;
    }
}
于 2010-02-02T18:23:03.687 回答