所以,标题有点做作,但这本质上是我想要完成的:
<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>
这里的想法是:
我希望表单
Item
是部分的,并且我想对 Razor 的 foreach 和为 Knockout 提供模板使用相同的部分。(我不想重复 HTML,也不想为 Knockout 创建一个特殊版本。)Razor foreach 用于在 JavaScript 被禁用或不支持的情况下进行后备。表单应该仍然是可编辑和可提交的(因为模型绑定器将正确处理 POST 数据),无论是否使用 JavaScript。
这当然意味着字段名称需要遵循
Items[0].SomeField
. 但是,部分将被强类型化为 oneItem
(而不是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 属性。它似乎工作得很好,但欢迎任何批评或改进建议。
真的,我现在唯一挣扎的是在 Razor foreach 中将正确的前缀放入部分中。通常,您会使用 afor
而不是解决此问题foreach
,然后传递索引项而不是通用项,即:
@for (var i = 0; i < Model.Items.Count(); i++)
{
@Html.TextBoxFor(m => m.Items[i].SomeField)
}
但是,这在使用部分时不起作用,因为一旦进入部分,您仍然只是在处理一个Item
脱离上下文的单个 。
所以,最紧迫的问题是我怎样才能仍然使用部分强类型 to Item
,但仍然让它知道存在的任何索引?然后,除此之外,如何最好地减少重复或 HTML 和逻辑(增加代码重用)。我的其余想法是否走在正确的轨道上。有什么我可以改进或做得更好的吗?
编辑
我应该提一下,理想情况下,我想保留部分强类型并实际利用它。换句话说,我希望能够做到@Html.TextBoxFor(m => m.SomeField)
而不是像@Html.TextBox("Items[" + iterator + "].SomeField")
. 如果我必须走那条路,那是一回事,但我希望有更好的方法。
我想我也可以按照以下方式做一些事情:
@Html.TextBox(prefix + "[" + iterator + "]." + Html.NameFor(m => m.SomeField))
然后传prefix
和iterator
入偏。从某种意义上说,这更好,因为我不必处理硬编码的值,但这是很多额外的逻辑,特别是对于具有一堆字段的部分。再次,希望有更好的方法。