Asp.net mvc Knockout.js:无法绑定到custom@Html.EditorFor
我正在使用选择器构建一个自定义@Html.EditorFor(称为@Html.FullFieldEditor)。它确定要生成的输入类型(文本框、下拉列表、单选按钮、复选框等)。我一直在尝试将它连接到一个单选按钮列表中,因此:Asp.net mvc Knockout.js:无法绑定到custom@Html.EditorFor,asp.net-mvc,asp.net-mvc-3,knockout.js,knockout-2.0,Asp.net Mvc,Asp.net Mvc 3,Knockout.js,Knockout 2.0,我正在使用选择器构建一个自定义@Html.EditorFor(称为@Html.FullFieldEditor)。它确定要生成的输入类型(文本框、下拉列表、单选按钮、复选框等)。我一直在尝试将它连接到一个单选按钮列表中,因此: @Html.FullFieldEditor(m => m.MyModel.MyRadioButton, new Dictionary<string, object> { { "data_bind", "myRadioButton" } }) 但是没有运气
@Html.FullFieldEditor(m => m.MyModel.MyRadioButton, new Dictionary<string, object> { { "data_bind", "myRadioButton" } })
但是没有运气
我试图修复我的selector.cshtml
代码,但结果却弄得一团糟。下面是在我尝试实现knockout.js
之前为我工作的代码:
@{
var supportsMany = typeof (IEnumerable).IsAssignableFrom(ViewData.ModelMetadata.ModelType);
var selectorModel = (Selector)ViewData.ModelMetadata.AdditionalValues["SelectorModelMetadata"];
var fieldName = ViewData.TemplateInfo.GetFullHtmlFieldName("");
var validationClass = ViewData.ModelState.IsValidField(fieldName) ? "" : "input-validation-error";
// Loop through the items and make sure they are Selected if the value has been posted
if(Model != null)
{
foreach (var item in selectorModel.Items)
{
if (supportsMany)
{
var modelStateValue = GetModelStateValue<string[]>(Html, fieldName) ?? ((IEnumerable)Model).OfType<object>().Select(m => m.ToString());
item.Selected = modelStateValue.Contains(item.Value);
}
else
{
var modelStateValue = GetModelStateValue<string>(Html, fieldName);
if (modelStateValue != null)
{
item.Selected = modelStateValue.Equals(item.Value, StringComparison.OrdinalIgnoreCase);
}
else
{
Type modelType = Model.GetType();
if (modelType.IsEnum)
{
item.Selected = item.Value == Model.ToString();
}
}
}
}
}
}
@functions
{
public MvcHtmlString BuildInput(string fieldName,
SelectListItem item, string inputType, object htmlAttributes)
// UPDATE: Trying to do it above
{
var id = ViewData.TemplateInfo.GetFullHtmlFieldId(item.Value);
var wrapper = new TagBuilder("div");
wrapper.AddCssClass("selector-item");
var input = new TagBuilder("input");
input.MergeAttribute("type", inputType);
input.MergeAttribute("name", fieldName);
input.MergeAttribute("value", item.Value);
input.MergeAttribute("id", id);
input.MergeAttributes(new RouteValueDictionary(htmlAttributes));
// UPDATE: and trying above, but see below in the
// @foreach...@BuildInput section
input.MergeAttributes(Html.GetUnobtrusiveValidationAttributes(fieldName, ViewData.ModelMetadata));
if(item.Selected)
input.MergeAttribute("checked", "checked");
wrapper.InnerHtml += input.ToString(TagRenderMode.SelfClosing);
var label = new TagBuilder("label");
label.MergeAttribute("for", id);
label.InnerHtml = item.Text;
wrapper.InnerHtml += label;
return new MvcHtmlString(wrapper.ToString());
}
/// <summary>
/// Get the raw value from model state
/// </summary>
public static T GetModelStateValue<T>(HtmlHelper helper, string key)
{
ModelState modelState;
if (helper.ViewData.ModelState.TryGetValue(key, out modelState) && modelState.Value != null)
return (T)modelState.Value.ConvertTo(typeof(T), null);
return default(T);
}
}
@if (ViewData.ModelMetadata.IsReadOnly)
{
var readonlyText = selectorModel.Items.Where(i => i.Selected).ToDelimitedString(i => i.Text);
if (string.IsNullOrWhiteSpace(readonlyText))
{
readonlyText = selectorModel.OptionLabel ?? "Not Set";
}
@readonlyText
foreach (var item in selectorModel.Items.Where(i => i.Selected))
{
@Html.Hidden(fieldName, item.Value)
}
}
else
{
if (selectorModel.AllowMultipleSelection)
{
if (selectorModel.Items.Count() < selectorModel.BulkSelectionThreshold)
{
<div class="@validationClass">
@foreach (var item in selectorModel.Items)
{
@BuildInput(fieldName, item, "checkbox") // throwing error here if I leave this as is (needs 4 arguments)
//But if I do this:
//@BuildInput(fieldName, item, "checkbox", htmlAttributes) // I get does not exit in current context
}
</div>
}
else
{
@Html.ListBox("", selectorModel.Items)
}
}
else if (selectorModel.Items.Count() < selectorModel.BulkSelectionThreshold)
{
<div class="@validationClass">
@*@if (selectorModel.OptionLabel != null)
{
@BuildInput(fieldName, new SelectListItem { Text = selectorModel.OptionLabel, Value = "" }, "radio")
}*@
@foreach (var item in selectorModel.Items)
{
@BuildInput(fieldName, item, "radio")//same here
}
</div>
}
else
{
@Html.DropDownList("", selectorModel.Items, selectorModel.OptionLabel)
}
}
我想KO可以将上述问题最小化。同样,我在看20-30个输入,大多数有3个以上的选择(少数下拉列表中有10个选择)。这是越来越难以维持在1500线和增长
然后在我的视图中
我看到了这样的情况:
<div id="MyRadioButton_1">
@Helpers.StartingCost(MyModel.Choice1, "1")
</div>
<div id="MyRadioButton_2">
@Helpers.StartingCost(MyModel.Choice2, "2")
</div>
<div id="MyRadioButton_3">
@Helpers.StartingCost(MyModel.Choice2, "2")
</div>
更新3
这些选项不起作用。选项1甚至不会在
中显示数据绑定。选项2将不起作用,因为它只是检查字段是否是必需的(如果是,代码只显示“必需”的图像)
当我在您的“UPDATE2”(input.MergeAttributes(新的RouteValueDictionary(htmlAttributes));
之前尝试您的第一个建议时,输出如下:
<div class="field-input" data_bind="checked: MyRadioButton">
<div class="">
<div class="selector-item">
<input id="MyModel_MyRadioButton_Choice1" name="MyModel.MyRadioButton" type="radio" value="Choice1">
<label for="MyModel_MyRadioButton_Choice1">I am thinking about Choice 1.</label>
</div>
<!--Just showing one radio button for brevity-->
</div>
</div>
我认为,它应该表现为:
<div class="field-input">
<div class="">
<div class="selector-item">
<!-- "data-bind" should be showing up in the following INPUT, correct?-->
<input data-bind="checked: MyRadioButton" id="MyModel_MyRadioButton_Choice1" name="MyModel.MyRadioButton" type="radio" value="Choice1">
<label for="MyModel_MyRadioButton_Choice1">I am thinking about Choice 1.</label>
</div>
<!--Just showing one radio button for brevity-->
</div>
</div>
然后我在同一个文件的下面做了这个:
@foreach (var item in selectorModel.Items)
{
@BuildInput(fieldName, item, "radio", "test")
}
我的HTML输出如下:
<div class="selector-item">
<input data-bind="test" id="MyModel_MyRadioButton_Choice1" name="MyModel.MyRadioButton" type="radio" value="Choice1">
<label for="MyModel_MyRadioButton_Choice1">Choice 1</label>
</div>
选择1
这让我相信它是Selector.cshtml
文件,而不是HtmlFormHelper.cs
文件。
我将向所有50岁以上的人开放赏金。更新3
第一个好的下划线,我完全忘记了。您的代码看起来几乎正确,但实际上应该是:
@Html.FullFieldEditor(m => m.MyModel.MyRadioButton, new Dictionary<string, object> { { "data_bind", "checked:myRadioButton" } })
原始答案
我有一种感觉,尝试混合razor和knockout会发生什么,razor的东西会被渲染,然后当knockout被附加时,knockout视图模型中的值会覆盖razor视图中的任何内容
以下是我的建议,如果您试图重构以击倒:
创建一个只有html的视图(没有razor)
将您的淘汰绑定添加到它
使用将模型数据以json对象的形式传递给knockout。您可以通过多种方式实现这一点,但最简单的方法是将视图中的数据填充到javascript可以找到的
标记中
用于将json对象加载到viewmodel中
我不确定我是否理解你的困难。但是,如果您的困难在于数据绑定的位置和方式取决于用于渲染属性的输入字段
请看一看的特点。它通过预编译模板(即显示页面片段的razor帮助程序或PartialView)或完整视图,自动计算大部分绑定。
也就是说,您可以只写:@Html.TextBoxFor(m=>m.myProperty),并在客户端ViewModel的textbox和myProperty之间创建一个适当的数据绑定,以此类推,使用selects、radios等。您还可以避免使用helper,只需为您的文件指定“适当的名称”,并自动计算绑定。此外,您可以决定将服务器端ViewModel的哪一部分传输到客户端…您的客户端ViewModel将在post时自动传输回服务器端ViewModel内部 虽然我明白你的观点,但现在我关心的是如何在页面上操纵用户输入。我不是在保存它,而是在向导中将它传递到最后一个“确认”页面。我真的想最小化我用来显示/隐藏
元素的JS。我想KO可以减少线。现在我有一个单独的.js
文件,其中有1500行代码要显示/隐藏。参见上面的编辑示例。是的,在这种情况下,击倒将是完美的。问题是您需要开始将视图逻辑分离为多个视图模型,然后使用foreach和模板重新使用代码。你经历过吗?这对你会有很大帮助。是的,我就是从那里开始的。我只是无法将它连接到我的自定义编辑器for
。不幸的是,这只能将“数据绑定”
放在字段输入中,而不是放在它需要的
中。我试图在我的Selector.cshtml
中的TagBuilder
中使用你的代码(我在问题中发布的第一大块代码),但没有用。我将用我尝试过的代码(给我几分钟时间)来标记代码。你找到解决方案了吗?这并没有真正的帮助。在我自己的范例之上,我有一个范例(KO)需要处理,你的解决方案是检查第三个范例?它不是3d范例,而是一种基于名称约定自动生成“适当”KO数据绑定属性的方法。通过这种方式,应该可以更容易地将不使用ko的对象转换为使用ko的对象,而无需在已构建的局部视图中进行太多修改。另一方面,您应该使用ko模板加上“with”和“if”ko模板操作符来简化js。很明显,如果您的解决方案仅限于使用该编辑器,那么您可能不值得为其添加更多的工具。您是否找到了解决问题的方法?@JasonMore No。不确定我查看的是我的选择器.cshmtl
,而不是HtmlFormHelper.cs
。如果你看我的“更新4”,我能够把一个字符串放在正确的位置。只需要弄清楚如何传递数据绑定。我不是一个程序员,所以我不知道更细微的东西。我想我不知道你现在有什么问题,因为你说你的数据绑定在正确的位置。@JasonMore,但我传递的只是一个简单的字符串。我制作了我在“更新4”中提到的htmlAttributes
<div class="field-input">
<div class="">
<div class="selector-item">
<!-- "data-bind" should be showing up in the following INPUT, correct?-->
<input data-bind="checked: MyRadioButton" id="MyModel_MyRadioButton_Choice1" name="MyModel.MyRadioButton" type="radio" value="Choice1">
<label for="MyModel_MyRadioButton_Choice1">I am thinking about Choice 1.</label>
</div>
<!--Just showing one radio button for brevity-->
</div>
</div>
input.MergeAttribute("data-bind", htmlAttributes);
@foreach (var item in selectorModel.Items)
{
@BuildInput(fieldName, item, "radio", "test")
}
<div class="selector-item">
<input data-bind="test" id="MyModel_MyRadioButton_Choice1" name="MyModel.MyRadioButton" type="radio" value="Choice1">
<label for="MyModel_MyRadioButton_Choice1">Choice 1</label>
</div>
@Html.FullFieldEditor(m => m.MyModel.MyRadioButton, new Dictionary<string, object> { { "data_bind", "checked:myRadioButton" } })
@Html.FullFieldEditor(m => m.MyModel.MyTextBox, new Dictionary<string, object> { { "data_bind", "value:MyTextBox" } })
public static MvcHtmlString FullFieldEditor<T, TValue>(this HtmlHelper<T> html, Expression<Func<T, TValue>> expression)
{
return FullFieldEditor(html, expression, null);
}
public static MvcHtmlString FullFieldEditor<T, TValue>(this HtmlHelper<T> html, Expression<Func<T, TValue>> expression, object htmlAttributes)
{
var metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
if (!metadata.ShowForEdit)
{
return MvcHtmlString.Empty;
}
if (metadata.HideSurroundingHtml)
{
return html.EditorFor(expression);
}
var wrapper = new TagBuilder("div");
wrapper.AddCssClass("field-wrapper");
var table = new TagBuilder("table");
table.Attributes["border"] = "0";
//added this to even out table columns
table.Attributes["width"] = "100%";
var tbody = new TagBuilder("tbody");
var td1 = new TagBuilder("td");
td1.Attributes["width"] = "40%";
td1.Attributes["valign"] = "top";
var label = new TagBuilder("div");
label.AddCssClass("field-label");
label.AddCssClass("mylabelstyle");
label.InnerHtml += html.MyLabelFor(expression);
td1.InnerHtml = label.ToString();
var td2 = new TagBuilder("td");
td2.Attributes["width"] = "50%";
td2.Attributes["valign"] = "top";
var input = new TagBuilder("div");
input.AddCssClass("field-input");
// option1
input.InnerHtml += html.EditorFor(expression, htmlAttributes);
td2.InnerHtml = input.ToString();
var td3 = new TagBuilder("td");
td3.Attributes["width"] = "5%";
td3.Attributes["valign"] = "top";
if (metadata.IsRequired && !metadata.IsReadOnly)
{
// option2
td3.InnerHtml += html.RequiredFor(expression, htmlAttributes);
}
var td4 = new TagBuilder("td");
td4.Attributes["width"] = "5%";
td4.Attributes["valign"] = "middle";
if (!string.IsNullOrEmpty(metadata.Description))
{
td4.InnerHtml += html.TooltipFor(expression);
}
else
{
td4.InnerHtml += html.SpacerFor(expression);
}
td4.InnerHtml += html.ValidationMessageFor(expression);
var tr = new TagBuilder("tr");
tr.InnerHtml = td1.ToString() + td2.ToString() + td3.ToString() + td4.ToString();
tbody.InnerHtml = tr.ToString();
table.InnerHtml = tbody.ToString();
wrapper.InnerHtml = table.ToString();
return new MvcHtmlString(wrapper + Environment.NewLine);
}
input.MergeAttributes(new RouteValueDictionary(htmlAttributes));