使用部分与Knockout模板集成的可编辑集合的Razor表单

时间:2013-04-25 00:06:39

标签: asp.net-mvc razor knockout.js

所以,标题有点人为,但这基本上就是我想要完成的事情:

<ul data-bind="template: { name: 'ItemFormTemplate', foreach: Items }">
    @foreach (var item in Model.Items)
    {
        Html.RenderPartial("_ItemForm", item);
    }
</ul>

<script type="text/html" id="ItemFormTemplate">
    @{ Html.RenderPartial("_ItemForm", new Item()); }
</script>

这里的想法是:

  1. 我希望Item的形式是部分的,我想对Razor的foreach和Knockout模板使用相同的部分。 (我不想重复HTML或只为Knockout创建一个特殊版本。)

  2. 如果JavaScript被禁用或不受支持,则Razor foreach用于后备。无论是否使用JavaScript,表单仍应可编辑且可提交(如在模型绑定器中将正确处理POST数据)。

  3. 这当然意味着字段名称需要遵循Items[0].SomeField之类的模式。部分将仅针对一个Item强类型(而不是IEnumerable<Item>)。可以理解,这对于Knockout模板来说实际上是不可能的,因为当前索引只能在客户端可用。我有一个我已经在使用的解决方法:

    ko.bindingHandlers.prefixAndIndex = {
        init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
            if (bindingContext.$index) {
                var prefix = ko.utils.unwrapObservable(valueAccessor());
                var $element = $(element);
                $element.find('input,textarea,select').each(function () {
                   var $this = $(this);
                   var newId = prefix + '_' + bindingContext.$index() + '__' + $this.attr('id');
                   var newName = prefix + '[' + bindingContext.$index() + '].' + $this.attr('name');
                   $('label[for=' + $this.attr('id') + ']').attr('for', newId);
                   $this.attr('id', newId);
                   $this.attr('name', newName);
                });
            }
        }
    };
    

    这只是绑定到模板的顶级元素(在本例中为<li>),并使用正确的前缀搜索并替换所有id和name属性。它似乎运作良好,但任何批评或改进建议都欢迎。

  4. 真的,我现在唯一正在努力的是在Razor foreach内部获得正确的前缀。通常,您使用for而不是foreach解决此问题,然后传递索引项而不是通用项,即:

    @for (var i = 0; i < Model.Items.Count(); i++)
    {
        @Html.TextBoxFor(m => m.Items[i].SomeField)
    }
    

    但是,当使用partial时,这不起作用,因为一旦你进入局部,你仍然只是处理单个Item,脱离了上下文。

    所以,最紧迫的问题是我如何仍然使用部分强类型Item,但仍然知道任何索引存在?然后,除此之外,如何最好地减少重复或HTML和逻辑(增加代码重用)。我的其余思想是否在正确的轨道上。有什么我可以改进或做得更好吗?

    修改

    我应该提到理想情况我想保持部分强类型并实际利用它。换句话说,我希望能够@Html.TextBoxFor(m => m.SomeField)而不是像@Html.TextBox("Items[" + iterator + "].SomeField")那样伪造它。如果我去那条路,这是一回事,但我希望有更好的方法。

    我想我也可以做一些事情:

    @Html.TextBox(prefix + "[" + iterator + "]." + Html.NameFor(m => m.SomeField))
    

    然后将prefixiterator传递给部分内容。从某种意义上讲,我不需要处理硬编码值,这是更好的,但它是一个额外逻辑的很多,特别是对于有一堆字段的部分。再次,希望有更好的方式。

2 个答案:

答案 0 :(得分:1)

你的问题指出在asp.net Mvc中如何处理前缀的分散缺乏知识,因为在10天内没有人能够提出一个“可接受的”解决方案,因为有几种方法可以以干净的方式传递前缀你的部分观点:

1 您可能会获得与

相同的效果
@Html.TextBoxFor(m => m.Items[i].SomeField)

使用DisplayFor或EditorFor调用局部视图:

@Html.DisplayFor(m => m.Items[i].SomeField)

您可以使用item数据类型的相同名称定义显示/编辑模板,或者只是将部分视图的名称传递给EditorFor / DisplayFor帮助程序。有关详细信息,请参阅here

2 您也可以使用前一个答案的RenderPartial的相同重载,将您要使用的完整前缀传递给ViewDataDictionary,方法是将其分配给属性: ViewDataDictionary.TemplateInfo.HtmlFieldPrefix

完成此操作后,您提供的前缀将自动添加到所有输入字段名称。

所以如果你写:

@Html.TextBoxFor(m => m.Property)

文本框的名称为:Prefix.Property,其中前缀是您传入的前缀

ViewDataDictionary.TemplateInfo.HtmlFieldPrefi

答案 1 :(得分:0)

  

所以,最紧迫的问题是我如何仍然使用偏爱   强类型到项目,但仍然知道任何索引   本?

我想你只想知道数组中的索引,你应该在ViewDataDictionary中传递它。

@for (var i = 0; i < Model.Items.Count(); i++){
  Html.RenderPartial("_ItemForm", Model.Items[i], new ViewDataDictionary(){
    { "index", i }
  });
}