如何使用模型将列表从视图正确连接到控制器
How to properly connect a list from the view to the controller using a Model
我想要一个表单,允许用户填写一些字段,当控制器接收到这些字段时,这些字段应该被压缩成一个对象。我找不到解释这个的词,所以让我举个例子。
这个对象(AllocationInformation
)基本上有六个字段,是模型的一部分:
AllocationInformation
{
FundSource
Function
Location
Program
Subject
Object
}
现在模型中有一个 List<AllocationInformation>
。该视图将允许用户创建任意数量的分配对象。
所以表单中有六个字段,还有一个按钮,让用户可以根据需要多次添加相同的六个字段中的另一个。
视图还需要能够渲染这些对象(如果它们已经存在于模型中)。我可以完成所有这些工作,但它丑陋如罪,我希望找到更好的方法。
目前我的看法是这样的:
<button type="button" class="btn btn-primary" onclick="addAllocation()">Add Allocation</button>
<div class="row">
<div id="allocationsDiv" class="container">
<div class="row">
<div class="col-sm-1" id="fundSourceCol">FundSource</div>
<div class="col-sm-1" id="functionCol">Function</div>
<div class="col-sm-1" id="locationCol">Location</div>
<div class="col-sm-1" id="programCol">Program</div>
<div class="col-sm-1" id="subjectCol">Subject</div>
<div class="col-sm-1" id="objectCol">Object</div>
<div class="col-sm-1" id="removeCol">Remove?</div>
</div>
@if (Model.Allocations != null)
{
for (int i = 0; i < Model.Allocations.Count; i++)
{
<div>
@Html.TextBoxFor(m => m.Allocations[i], new { @class = "form-control input-xsmall" })
</div>
}
}
</div>
</div>
这是 addAllocation()
的 javascript:
function addAllocation()
{
var $row = $("<div class='row'></div>");
$("#allocationsDiv").append($row);
var fundSource = "<div class='col-sm-1'><input type='text' name='Allocation.FundSource' class='funding-source-item form-control' /></div>";
$row.append(fundSource);
var func = "<div class='col-sm-1'><input type='text' name='Allocation.Function' class='funding-source-item form-control col-sm-1' /></div>";
$row.append(func);
var location = "<div class='col-sm-1'><input type='text' name='Allocation.Location' class='funding-source-item form-control col-sm-1' /></div>";
$row.append(location);
var program = "<div class='col-sm-1'><input type='text' name='Allocation.Program' class='funding-source-item form-control col-sm-1' /></div>";
$row.append(program);
var subject = "<div class='col-sm-1'><input type='text' name='Allocation.Subject' class='funding-source-item form-control col-sm-1' /></div>";
$row.append(subject);
var object = "<div class='col-sm-1'><input type='text' name='Allocation.Object' class='funding-source-item form-control col-sm-1' /></div>";
$row.append(object);
var $remove = $("<div class='col-sm-1'><i class='glyphicon glyphicon-remove glyphicon-clickable col-sm-1 funding-source-item'></i></div>");
$remove.on('click', function () {
$row.remove()
});
$row.append($remove);
}
这就是问题所在。addAllocation()
函数需要知道已进行多少分配的当前索引,以便正确构建输入名称,因为它们应该采用 Allocation[i].FundSource
,这意味着我必须有一个全局 javascript 变量或类似的东西来跟踪它,这似乎是一个糟糕的主意。
我只是认为必须有一种我不知道的更好的方法。
这是实际的 AllocationInformation
class 以防有帮助:
public class AllocationInformation
{
[Key]
public int Id { get; set; }
[ForeignKey("CheckRequestModel")]
public int CheckRequestId { get; set; }
public CheckRequestModel CheckRequestModel { get; set; }
public string FundSource { get; set; }
public string Function { get; set; }
public string Location { get; set; }
public string Program { get; set; }
public string Subject { get; set; }
public string Object { get; set; }
public float Amount { get; set; }
public int VendorNumber { get; set; }
}
您的 Razor 实现看起来不错。如果它是所有现有对象,那应该正确地回发一个 List<AllocationInformation>
对象。
问题出在您的 JavaScript 实施上。首先,您当前的 JavaScript 代码甚至没有考虑索引。所有的字段名称都是Allocation.Foo
的形式。相反,它们需要采用 Allocations[N].Foo
的形式(还要注意复数形式)。
获取当前索引实际上是微不足道的。在您的 addAllocation
JavaScript 方法中,只需执行:
var currentIndex = $('#allocationsDiv .row').length - 1;
然后,使用 currentIndex
变量代替字段名称中的 N
。
对于它的价值,您可能需要考虑将 Knockout JS 之类的东西集成到您的项目中。它使处理渲染集合之类的事情变得微不足道。您可以创建一个通用 "row" 模板,然后绑定该模板以针对某些可观察数组的每个实例进行渲染。然后,要添加或删除行,您只需在数组中添加或删除项目,Knockout 会相应地更新 HTML。
编辑
值得一提的是,我为您提供的查找当前索引的方法仅在项目未被带外删除时才有效。例如,如果您从三个项目(索引 0、1 和 2)开始,删除第二个(索引 1),然后添加一个新项目,您实际上将再次以索引 0、2 和 2 结束.显然,这是行不通的。您可以尝试在删除一个项目后重新索引所有现有项目,这将需要选择每一行,并使用基于 for 循环的新索引更新名称参数。然而,这确实非常笨重。这是您应该使用 Knockout JS 之类的东西的另一个原因。所有这些逻辑都是开箱即用的。
更新:淘汰赛速成班
这是我通常使用 Knockout 代码为我的视图设置代码的方式:
外部 JS
var Namespace = Namespace || {};
Namespace.Application = (function () {
var _init = function (data) {
var viewModel = Namespace.ViewModel(data);
ko.applyBindings(viewModel);
_wireEvents(viewModel);
return viewModel;
};
var _wireEvents = function (viewModel) {
$('#AddAllocation').on('click', viewModel.AddAllocation);
$('#Allocations).on('click', '.remove', viewModel.RemoveAllocation);
// other event handlers here
};
return {
Init: _init
};
})();
Namespace.ViewModel = function (data) {
var self = {};
self.Allocations = ko.observableArray(
$.map(data.Allocations, Namespace.AllocationViewModel)
);
self.AddAllocation = function () {
self.Allocations.push(new Namespace.AllocationViewModel(Namespace.Allocation));
};
self.RemoveAllocation = function () {
var data = ko.dataFor(this);
self.Allocations.remove(data);
};
return self;
};
Namespace.AllocationViewModel = function (data) {
var self = {};
self.Id = ko.observable(data.Id);
self.CheckRequestId = ko.observable(data.CheckRequestId);
// etc.
return self;
};
页面 JS
<script>
var Namespace = Namespace || {};
Namespace.Allocation = @Html.Raw(Json.Encode(YourProject.Namespace.To.AllocationInformation));
$(document).ready(function () {
var data = @Html.Raw(Json.Encode(Model));
Namespace.Application.Init(data);
});
</script>
此处使用的 Namespace
变量是对您的应用程序使用命名空间的一般指示。污染 JavaScript 中的全局命名空间是一种不好的形式,因此您只需创建一些您的组织独有的变量,通常是公司名称之类的东西,然后将所有变量和函数放在该对象上。
Namespace.Application
对象是视图的核心功能。您可以随意命名;它不必被称为 Application
.
Namespace.Allocation
只是您拥有的 AllocationInformation
class 的 JavaScript 对象表示。存储它可以让您轻松添加基于此对象模板的新分配。
Namespace.ViewModel
是主要的Knockout视图模型,Namespace.AllocationViewModel
是单个分配特定的视图模型。主视图模型将以这些的可观察数组结束(通过 $.map
)。
在此示例代码中,我手动创建了所有可观察对象。实际上有一个用于 Knockout 的映射插件,它允许您执行如下操作:
Namespace.ViewModel = function (data) {
var model = ko.mapping.fromJS(data);
return model;
}
它会自动为 data
对象的任何成员创建可观察对象。然后,您只需要向视图模型添加您需要的其他内容,而不必手动拼出每个 属性。
我想要一个表单,允许用户填写一些字段,当控制器接收到这些字段时,这些字段应该被压缩成一个对象。我找不到解释这个的词,所以让我举个例子。
这个对象(AllocationInformation
)基本上有六个字段,是模型的一部分:
AllocationInformation
{
FundSource
Function
Location
Program
Subject
Object
}
现在模型中有一个 List<AllocationInformation>
。该视图将允许用户创建任意数量的分配对象。
所以表单中有六个字段,还有一个按钮,让用户可以根据需要多次添加相同的六个字段中的另一个。
视图还需要能够渲染这些对象(如果它们已经存在于模型中)。我可以完成所有这些工作,但它丑陋如罪,我希望找到更好的方法。
目前我的看法是这样的:
<button type="button" class="btn btn-primary" onclick="addAllocation()">Add Allocation</button>
<div class="row">
<div id="allocationsDiv" class="container">
<div class="row">
<div class="col-sm-1" id="fundSourceCol">FundSource</div>
<div class="col-sm-1" id="functionCol">Function</div>
<div class="col-sm-1" id="locationCol">Location</div>
<div class="col-sm-1" id="programCol">Program</div>
<div class="col-sm-1" id="subjectCol">Subject</div>
<div class="col-sm-1" id="objectCol">Object</div>
<div class="col-sm-1" id="removeCol">Remove?</div>
</div>
@if (Model.Allocations != null)
{
for (int i = 0; i < Model.Allocations.Count; i++)
{
<div>
@Html.TextBoxFor(m => m.Allocations[i], new { @class = "form-control input-xsmall" })
</div>
}
}
</div>
</div>
这是 addAllocation()
的 javascript:
function addAllocation()
{
var $row = $("<div class='row'></div>");
$("#allocationsDiv").append($row);
var fundSource = "<div class='col-sm-1'><input type='text' name='Allocation.FundSource' class='funding-source-item form-control' /></div>";
$row.append(fundSource);
var func = "<div class='col-sm-1'><input type='text' name='Allocation.Function' class='funding-source-item form-control col-sm-1' /></div>";
$row.append(func);
var location = "<div class='col-sm-1'><input type='text' name='Allocation.Location' class='funding-source-item form-control col-sm-1' /></div>";
$row.append(location);
var program = "<div class='col-sm-1'><input type='text' name='Allocation.Program' class='funding-source-item form-control col-sm-1' /></div>";
$row.append(program);
var subject = "<div class='col-sm-1'><input type='text' name='Allocation.Subject' class='funding-source-item form-control col-sm-1' /></div>";
$row.append(subject);
var object = "<div class='col-sm-1'><input type='text' name='Allocation.Object' class='funding-source-item form-control col-sm-1' /></div>";
$row.append(object);
var $remove = $("<div class='col-sm-1'><i class='glyphicon glyphicon-remove glyphicon-clickable col-sm-1 funding-source-item'></i></div>");
$remove.on('click', function () {
$row.remove()
});
$row.append($remove);
}
这就是问题所在。addAllocation()
函数需要知道已进行多少分配的当前索引,以便正确构建输入名称,因为它们应该采用 Allocation[i].FundSource
,这意味着我必须有一个全局 javascript 变量或类似的东西来跟踪它,这似乎是一个糟糕的主意。
我只是认为必须有一种我不知道的更好的方法。
这是实际的 AllocationInformation
class 以防有帮助:
public class AllocationInformation
{
[Key]
public int Id { get; set; }
[ForeignKey("CheckRequestModel")]
public int CheckRequestId { get; set; }
public CheckRequestModel CheckRequestModel { get; set; }
public string FundSource { get; set; }
public string Function { get; set; }
public string Location { get; set; }
public string Program { get; set; }
public string Subject { get; set; }
public string Object { get; set; }
public float Amount { get; set; }
public int VendorNumber { get; set; }
}
您的 Razor 实现看起来不错。如果它是所有现有对象,那应该正确地回发一个 List<AllocationInformation>
对象。
问题出在您的 JavaScript 实施上。首先,您当前的 JavaScript 代码甚至没有考虑索引。所有的字段名称都是Allocation.Foo
的形式。相反,它们需要采用 Allocations[N].Foo
的形式(还要注意复数形式)。
获取当前索引实际上是微不足道的。在您的 addAllocation
JavaScript 方法中,只需执行:
var currentIndex = $('#allocationsDiv .row').length - 1;
然后,使用 currentIndex
变量代替字段名称中的 N
。
对于它的价值,您可能需要考虑将 Knockout JS 之类的东西集成到您的项目中。它使处理渲染集合之类的事情变得微不足道。您可以创建一个通用 "row" 模板,然后绑定该模板以针对某些可观察数组的每个实例进行渲染。然后,要添加或删除行,您只需在数组中添加或删除项目,Knockout 会相应地更新 HTML。
编辑
值得一提的是,我为您提供的查找当前索引的方法仅在项目未被带外删除时才有效。例如,如果您从三个项目(索引 0、1 和 2)开始,删除第二个(索引 1),然后添加一个新项目,您实际上将再次以索引 0、2 和 2 结束.显然,这是行不通的。您可以尝试在删除一个项目后重新索引所有现有项目,这将需要选择每一行,并使用基于 for 循环的新索引更新名称参数。然而,这确实非常笨重。这是您应该使用 Knockout JS 之类的东西的另一个原因。所有这些逻辑都是开箱即用的。
更新:淘汰赛速成班
这是我通常使用 Knockout 代码为我的视图设置代码的方式:
外部 JS
var Namespace = Namespace || {};
Namespace.Application = (function () {
var _init = function (data) {
var viewModel = Namespace.ViewModel(data);
ko.applyBindings(viewModel);
_wireEvents(viewModel);
return viewModel;
};
var _wireEvents = function (viewModel) {
$('#AddAllocation').on('click', viewModel.AddAllocation);
$('#Allocations).on('click', '.remove', viewModel.RemoveAllocation);
// other event handlers here
};
return {
Init: _init
};
})();
Namespace.ViewModel = function (data) {
var self = {};
self.Allocations = ko.observableArray(
$.map(data.Allocations, Namespace.AllocationViewModel)
);
self.AddAllocation = function () {
self.Allocations.push(new Namespace.AllocationViewModel(Namespace.Allocation));
};
self.RemoveAllocation = function () {
var data = ko.dataFor(this);
self.Allocations.remove(data);
};
return self;
};
Namespace.AllocationViewModel = function (data) {
var self = {};
self.Id = ko.observable(data.Id);
self.CheckRequestId = ko.observable(data.CheckRequestId);
// etc.
return self;
};
页面 JS
<script>
var Namespace = Namespace || {};
Namespace.Allocation = @Html.Raw(Json.Encode(YourProject.Namespace.To.AllocationInformation));
$(document).ready(function () {
var data = @Html.Raw(Json.Encode(Model));
Namespace.Application.Init(data);
});
</script>
此处使用的 Namespace
变量是对您的应用程序使用命名空间的一般指示。污染 JavaScript 中的全局命名空间是一种不好的形式,因此您只需创建一些您的组织独有的变量,通常是公司名称之类的东西,然后将所有变量和函数放在该对象上。
Namespace.Application
对象是视图的核心功能。您可以随意命名;它不必被称为 Application
.
Namespace.Allocation
只是您拥有的 AllocationInformation
class 的 JavaScript 对象表示。存储它可以让您轻松添加基于此对象模板的新分配。
Namespace.ViewModel
是主要的Knockout视图模型,Namespace.AllocationViewModel
是单个分配特定的视图模型。主视图模型将以这些的可观察数组结束(通过 $.map
)。
在此示例代码中,我手动创建了所有可观察对象。实际上有一个用于 Knockout 的映射插件,它允许您执行如下操作:
Namespace.ViewModel = function (data) {
var model = ko.mapping.fromJS(data);
return model;
}
它会自动为 data
对象的任何成员创建可观察对象。然后,您只需要向视图模型添加您需要的其他内容,而不必手动拼出每个 属性。