在编辑器模板呈现的列表中添加新项

时间:2014-04-14 19:24:49

标签: asp.net-mvc model-binding mvc-editor-templates

我有一个使用大量AJAX的ASP.NET MVC页面。在最基本的层面上,它包含一个问题列表,每个问题都有一个答案列表。我的观点模型(为了这个问题而大大简化):

查看模型:

public class QuestionViewModel
{
    ....
    public string QuestionText { get; set; }
    public List<AnswerViewModel> AnswerList { get; set; }
    public bool EditMode { get; set; }
    ....
}

public class AnswerViewModel
{
    ....
    public string Answer { get; set; }
    ....
}

_View.cshtml:

@model QuestionViewModel

@if(Model.EditMode)
{
    @Html.EditorFor(x => x)
}
else
{
...
}

QuestionViewModel.cshtml(编辑模板):

@model Project.Models.QuestionViewModel

....
@Html.EditorFor(x => x.AnswerList)
....

AnswerViewModel.cshtml(编辑模板):

@model KSUAdvising.Models.AnswerViewModel

....
@Html.TextBoxFor(x => x.Answer)
....

^^^这个EditorFor调用使我的答案列表很好(连同适当的索引,因此模型绑定器将适当地绑定回列表。即:

<input id="AnswerList_0__Answer" name="AnswerList[0].Answer" type="text" value="Yes">
<input id="AnswerList_1__Answer" name="AnswerList[1].Answer" type="text" value="No">

但是,在客户端,我需要在用户单击按钮时将项添加到此列表中。这需要在客户端进行,因此在用户保存问题之前不会对数据库进行任何更改。

我看到了解决这个问题的方向:

  1. 对服务器进行AJAX调用以呈现新的空白答案并将其添加到DOM。但是,由于这不是问题的上下文,它不会呈现正确的名称(索引),也不会被模型绑定器拾取。
  2. 渲染隐藏的HTML&#34;模板&#34;到客户端包括正确的输入名称,然后克隆此模板并修改名称索引以满足模型绑定器。这看起来有点hacky,很难正确渲染这个模板。
  3. 这似乎是一种常见的情况,但我很难找到一个好的解决方案。这种情况的推荐方法是什么?

2 个答案:

答案 0 :(得分:1)

我投票支持选项2。

呈现隐藏的HTML项目模板。然后在客户端按钮上单击,克隆模板并修改索引。

在cshtml页面中,在foreach之后,为模板添加一个新的隐藏div。给它一个索引占位符(我使用_ x0x_)。创建一个新的空项目,然后以与foreach中相同的方式呈现它。 (您也可以将项目呈现为部分视图,然后在foreach内外调用它。)

以下是示例cshtml页面:

@foreach (var role in roles)
{
    int roleIndex = roles.IndexOf(role);
    string rolePrefix = "CasePartyRoles[" + roleIndex + "].";
    <div id="CasePartyRoleIndex_@roleIndex" class="row brdr-bttm mrgn-bttm-sm">
        <div class="col-md-10 mrgn-bttm-sm">
            @Html.Hidden(rolePrefix + "SequenceNo", role.SequenceNo)
            @Html.Hidden(rolePrefix + "RowVersion", role.RowVersion)
            @Html.DropDownListFormGroupFor(modelItem => role.PartyRoleCode, (SelectList)ViewBag.PartyRoleSelectList, null, "col-md-3", "col-md-9", null, role.PartyRoleCode, rolePrefix + "PartyRoleCode")
            @Html.DropDownListFormGroupFor(modelItem => role.PartyStatusCode, (SelectList)ViewBag.PartyStatusSelectList, null, "col-md-3", "col-md-9", null, role.PartyStatusCode, rolePrefix + "PartyStatusCode")
            @Html.EditorFormGroupFor(modelItem => role.SubFileNo, "col-md-3", "col-md-9", null, null, rolePrefix + "SubFileNo")
            @Html.EditorFormGroupFor(modelItem => role.PartyRank, "col-md-3", "col-md-9", null, null, rolePrefix + "PartyRank")
        </div>
    </div>
}
<div id="CasePartyRoleTemplate" class="hidden">
    @*Template for new Role*@
    @{
        var newRole = new CasePartyRole();
        string newRolePrefix = "CasePartyRoles[_x0x_].";
    }
    <div id="CasePartyRoleIndex__x0x_" class="row brdr-bttm mrgn-bttm-sm">
        <div class="col-md-10 mrgn-bttm-sm">
            @Html.Hidden(newRolePrefix + "SequenceNo", newRole.SequenceNo)
            @Html.Hidden(newRolePrefix + "RowVersion", newRole.RowVersion)
            @Html.DropDownListFormGroupFor(modelItem => newRole.PartyRoleCode, (SelectList)ViewBag.PartyRoleSelectList, null, "col-md-3", "col-md-9", null, newRole.PartyRoleCode, newRolePrefix + "PartyRoleCode")
            @Html.DropDownListFormGroupFor(modelItem => newRole.PartyStatusCode, (SelectList)ViewBag.PartyStatusSelectList, null, "col-md-3", "col-md-9", null, newRole.PartyStatusCode, newRolePrefix + "PartyStatusCode")
            @Html.EditorFormGroupFor(modelItem => newRole.SubFileNo, "col-md-3", "col-md-9", null, null, newRolePrefix + "SubFileNo")
            @Html.EditorFormGroupFor(modelItem => newRole.PartyRank, "col-md-3", "col-md-9", null, null, newRolePrefix + "PartyRank")
        </div>
    </div>
</div>

这是一个基于模板添加项目的小jQuery函数:

function createItemFromTemplate(templateId, indexNo, insertBeforeId) {
    // Copy the template Element, replaces all the _x0x_ with the index no and add the new element
    $(insertBeforeId).before($.parseHTML($(templateId).clone().prop('outerHTML').replace(/_x0x_/g, indexNo)));
}

答案 1 :(得分:0)

这取决于...为了表现,我不会选择第一个选项。通过Ajax加载新项目的这种轻微延迟可能很烦人。

就个人而言,大多数时候我只会在客户端完全构建一个HTML元素。我知道这会使维护变得复杂,因为您必须记住在您对模型集合进行更改时更改客户端逻辑,但这是快速而直接的。

还有一个想法如何改善你的第二个选择。基本上,你不需要有一个真实的对象来构建它。

@Html.TextBoxFor(model => model.Items[int.MaxValue], new { style= "display:none" })

提交的输入

<input name="Items[2147483647]" id="Items_2147483647_" style="display: none;" type="text" value="">

请注意,在我的情况下,Items属性为null,但是如果你只是想构建一个模板,那么无关紧要xxxFor html helpers只是使用表达式来构建html。此外,在使用真实索引克隆模板时,替换int.MaxValue非常容易。使用int.MaxValue作为占位符是安全的,非常简单的是你的页面上会有很多项目。

希望它有所帮助!