我可以使用添加/删除功能在MVC 4视图中显示复杂对象列表吗?

时间:2013-04-06 13:57:58

标签: asp.net-mvc asp.net-mvc-4 partial-views nested mvc-editor-templates

我已经在这个问题上喋喋不休了好几天了,无法理解。我需要创建一个允许用户添加和删除复杂分层对象的视图。假的例子:

模型

public class ParentModel
{
    public int ParentId { get; set; }
    public string ParentText { get; set; }
    public IList<ChildModel> Children { get; set; } 
}
public class ChildModel
{
    public int ChildId { get; set; }
    public string ChildText { get; set; }
    public IList<GrandChildModel> GrandChildren { get; set; }
}
public class GrandChildModel
{
    public int GrandChildId { get; set; }
    public string GrandChildText { get; set; }
}

现在,假设我有一个填充ParentModel及其子属性的操作,一个简单的视图可能如下所示:

@model EditorTemplateCollectiosns.Models.ParentModel
@using (@Html.BeginForm())
{
    <div>
        @Html.TextBoxFor(m => m.ParentId)
        @Html.TextBoxFor(m => m.ParentText)<br/>
        Children:<br/>
        @for (var i = 0; i < Model.Children.Count; i++)
        {
            @Html.TextBoxFor(m => Model.Children[i].ChildId)
            @Html.TextBoxFor(m => Model.Children[i].ChildText)
            <div>
                Grandchildren:<br/>
                <table id="grandChildren">
                    <tbody>
                        @for (var j = 0; j < Model.Children[i].GrandChildren.Count; j++)
                        {
                            <tr>
                                <td>@Html.TextBoxFor(m => @Model.Children[i].GrandChildren[j].GrandChildId)</td>
                                <td>@Html.TextBoxFor(m => @Model.Children[i].GrandChildren[j].GrandChildText)</td>
                            </tr>
                        }
                    </tbody>
                </table>
            </div>
        }
    </div>
    <button type="button" id="addGrandchild">Add Grandchild</button>
    <input type="submit" value="submit"/>
}
<script type="text/javascript">
    $(document).ready(function() {
        $("#addGrandchild").click(function() {
            $("#grandChildren tbody:last").append('<tr><td><input type="text" name="grandChildId" /></td><td><input type="text" name="grandChildText" /></td></tr>');
        });
    });
</script>

此视图工作正常,除非我添加孙子并提交表单,在这种情况下它不会绑定到GrandChildModel列表。 POST的小提琴:

Post after adding grandchild

所以我尝试使用GrandChildModel的编辑器模板,看看它是否有效。这很好地用于简单地显示对象,但我不知道如何将孙子添加到列表中并将其显示在编辑器模板中。我还尝试嵌套加载了$.ajax次调用的部分视图,但这变成了一个后勤噩梦。

在实际应用程序中,层次结构比此示例更深,并且每个连续的子对象都需要具有此添加/删除功能。每次用户想要添加或删除对象时,如果没有表单的完整帖子,这是否可能?通过上面示例中的方法,是否可以让新添加的孙子绑定到POST上的IList?或者,是否有一种方法可以在客户端添加对象并将其显示在编辑器模板中绑定到POST上的IList?如果两种选择都不是正确的方法,那么是什么?

更新
我已经找到了可能有用的hacky,但我仍然想知道是否有更简单的方法来做到这一点。我将上面的视图代码更改为:

@model EditorTemplateCollectiosns.Models.ParentModel
@using (@Html.BeginForm())
{
    <div>
        @Html.TextBoxFor(m => m.ParentId)
        @Html.TextBoxFor(m => m.ParentText)<br/>
        Children:<br/>
        <button type="button" id="addChild" onclick="addChildRow()">Add Child</button>
        <table id="children">
            <tbody class="childTableBody">
                @for (var i = 0; i < Model.Children.Count; i++)
                {
                    <tr>
                        <td colspan="3">Child @i</td>
                    </tr>
                    <tr class="childInfoRow">
                        <td>
                            @Html.TextBoxFor(m => Model.Children[i].ChildId)
                        </td>
                        <td>
                            @Html.TextBoxFor(m => Model.Children[i].ChildText)                            
                        </td>
                        <td>
                            <button type="button" id="removeChild@(i)" onclick="removeChildRow(this)">Delete</button>
                        </td>
                    </tr>
                    <tr>
                        <td colspan="3">
                            Grandchildren:<br/>
                            <button type="button" id="addGrandchild@(i)" onclick="addGrandchildRow(this)">Add Grandchild</button>
                            <table id="grandChildren@(i)">
                                <tbody>
                                    @for (var j = 0; j < Model.Children[i].GrandChildren.Count; j++)
                                    {
                                        <tr>
                                            <td>@Html.TextBoxFor(m => @Model.Children[i].GrandChildren[j].GrandChildId)</td>
                                            <td>@Html.TextBoxFor(m => @Model.Children[i].GrandChildren[j].GrandChildText)</td>
                                            <td><button type="button" id="removeGrandchild@(i)_@(j)" onclick="removeGrandchildRow(this)">Delete</button></td>
                                        </tr>
                                    }
                                </tbody>
                            </table>
                        </td>
                    </tr>
                }
            </tbody>
        </table>
    </div>
    <input type="submit" value="submit"/>
}

然后,我添加了以下javascript。我不能使用类和jQuery单击事件处理程序作为重新绑定处理程序后,每次添加/删除似乎都不正常:

<script type="text/javascript">
    function addChildRow() {
        var newRowIndex = $("#children tr.childInfoRow").length;
        var newRow = '<tr><td colspan="3">Child ' + newRowIndex + '</td></tr><tr class="childInfoRow"><td><input type="text" name="Children[' + newRowIndex + '].ChildId" /></td>' +
            '<td><input type="text" name="Children[' + newRowIndex + '].ChildText" /></td>' +
            '<td><button type="button" id="removeChild' + newRowIndex + '" onclick="removeChildRow(this)">Delete</button></td></tr>' +
            '<td colspan="3">Grandchildren: <br/><button type="button" id="addGrandchild' + newRowIndex + '" onclick="addGrandchildRow(this)">Add Grandchild</button>' +
            '<table id="grandChildren' + newRowIndex + '"><tbody></tbody></table></td></tr>';
        $("#children tbody.childTableBody:last").append(newRow);
    }
    function addGrandchildRow(elem) {
        var childIndex = $(elem).attr('id').replace('addGrandchild', '');
        var newRowIndex = $("#grandChildren" + childIndex + " tr").length;
        var newRow = '<tr><td><input type="text" name="Children[' + childIndex + '].GrandChildren[' + newRowIndex + '].GrandChildId" /></td>' +
            '<td><input type="text" name="Children[' + childIndex + '].GrandChildren[' + newRowIndex + '].GrandChildText" /></td>' +
            '<td><button type="button" id="removeGrandchild' + childIndex + '_' + newRowIndex + '" onclick="removeGrandchildRow(this)">Delete</button></td></tr>';
        $("#grandChildren" + childIndex + " tbody:last").append(newRow);
    }
    function removeChildRow(elem) {
        $(elem).closest('tr').prev().remove();
        $(elem).closest('tr').next().remove();
        $(elem).closest('tr').remove();
    }
    function removeGrandchildRow(elem) {
        $(elem).closest('tr').remove();
    }
</script>

到目前为止,这是有效的,数组值似乎正确绑定。但是,我并不是在向两个表中添加行所需的大量内容的忠实粉丝。有没有人有更好的主意?

2 个答案:

答案 0 :(得分:0)

如果您只使用一个型号,这将更容易实现。 ChildModel和GrandChildModel只有一个HierarchichalPersonModel,而不是ParentModel。

这样你只需要一个视图和一个自定义编辑器模板。 Ajax会有点困难,但不是你所描述的后勤噩梦。

更新:添加了示例

人物模型:

public class Person
{
    public int Id { get; set; }
    public string Text { get; set; }
    public List<Person> Children { get; set; }
}

假存储库:

public static List<Person> PersonCollection { get; set; }

public static Person FindById(int id, List<Person> list = null)
{
    if (list == null)
        list = PersonCollection;

    foreach (var person in list)
    {
        if (person.Id == id)
            return person;

        if (person.Children != null)
            return FindById(id, person.Children);
    }

    return null;
}

答案 1 :(得分:0)

我最终在原始帖子的编辑中使用了解决方案。这是一个的javascript并且花了很长时间进行测试,但它确实有效并且是我能找到的唯一方法。如果有人对完整解决方案感兴趣,请在此处发表评论并发布。