MVC 中的 KnockoutJS 嵌套数组





5.00/5 (8投票s)
如何在 ASP.NET MVC 中处理 KnockoutJS 数组(简单和嵌套)
引言
KnockoutJS 是一个非常实用的双向数据绑定库。Code Project 上有一些关于它的精彩文章,而 Knockout 网站本身也有一些 非常好的教程和 示例。如果您正在处理列表和数组,可能会发现我关于 在 KnockoutJS 中搜索、过滤和排序 列表的文章很有用。本文假定您熟悉 Knockout,并且需要了解如何在 Knockout 中使用数组,并将该数组数据传回 MVC 应用程序。
本文提供了一个关于在 KnockoutJS 中处理数组的简单分步指南,并演示了如何将 KnockoutJS JSON 数据从客户端浏览器使用映射的 ViewModel
对象保存到服务器。
安装
此示例使用一个简单的 MVC 项目,除了 KnockoutJS 和一些支持库外,没有其他依赖项。我们的示例将使用一个基本的销售人员模型,该销售人员有许多客户,每个客户都可以有许多订单。
服务器端代码
以下是我们用来表示“销售人员”的简单模型。
Sales person can sell in many regions
Sales person can sell to many customers, customers can have many orders
以下代码设置了服务器端的这个简单模型。
public class SalesPerson
{
public string FirstName { get; set; }
public string LastName { get; set; }
public List<region> Regions {get; set;}
public List<customer> Customers {get; set;}
public SalesPerson()
{
Regions = new List<region>();
Customers = new List<customer>();
}
}
public class Region
{
public int ID { get; set;}
public string SortOrder { get; set; }
public string Name { get; set; }
}
public class Customer
{
public int ID { get; set; }
public string SortOrder { get; set; }
public string Name { get; set; }
public List<order> Orders { get; set; }
public Customer()
{
Orders = new List<order>();
}
}
public class Order
{
public int ID { get; set; }
public string Date { get; set; }
public string Value { get; set; }
}
对于这个简单的示例,我们将大部分工作放在客户端完成,并在客户端设置数据 - 因此,我们将保持服务器端简单。我们将从主索引控制器返回一个简单的视图开始。
public ActionResult Index()
{
return View();
}
当我们完成客户端的工作后,我们将使用刚刚定义的模型将数据发送回控制器。这可以通过简单地声明一个接受与我们的模型类型相同的参数的控制器方法来实现。
public JsonResult SaveModel(SalesPerson SalesPerson)
{
// the JSON Knockout Model string sent in, maps directly to the "SalesPerson"
// model defined in SharedModel.cs
var s = SalesPerson; // we can work with the Data model here - save to
// database / update, etc.
return null;
}
顺带一提,如果我们想在服务器端获取一个预先填充的模型,我们可以使用 MVC 提供的标准模型传递工作流。
public ActionResult Index()
{
// create the model
SalesPerson aalesPersonModel = new SalesPerson
return View(salesPersonModel);
}
在 cshtml 视图中,我们将接收模型,将其序列化为 JSON,并将其渲染到一个客户端 JSON 变量中,然后加载到我们的 Knockout 模型中。
@Html.Raw(Json.Encode(Model))
客户端代码
我们在客户端做的第一件事是在我们的 MVC 项目中设置一个 JavaScript 文件来镜像我们的服务器端模型,并赋予它一些功能。
如果我们从模型树往回看,我们可以更清楚地看到事物的创建方式。
Customer
s 可以有许多 order
s,所以我们先讨论那个。
var Order = function {
var self = this;
self.ID = ko.observable();
self.Date = ko.observable();
self.Value = ko.observable();
});
}
上面的代码是一个非常基本的 Knockout 对象模型。它有三个字段:ID
、Date
和 Value
。为了使其更有用,我们需要对其进行一些扩展。我们将“扩展”以告诉可观察对象某个特定的字段/值是必需的,我们将允许模型接受一个“data”参数,我们可以将预先填充的模型传递给它,最后,我们将告诉模型,如果发送了“data”,则使用 Knockout Mapping 插件“解开”它。由于订单中没有子数组项,因此在 ko.mapping
函数“{}
”中没有传递“options”。
这是更新后的模型。
var Order = function (data) {
var self = this;
if (data != null) {
ko.mapping.fromJS(data, {}, self);
} else {
self.ID = ko.observable();
self.Date = ko.observable().extend({
required: true
});
self.Value = ko.observable().extend({
required: true
});
}
self.Value.extend({
required: {
message: '* Value needed'
}
});
}
接下来是客户模型,它遵循我们为订单讨论的相同模式。这里需要注意的额外一点是,我们告诉它 *当遇到一个名为“Orders
”的对象时,使用“orderMapping
”插件将其解开。*
var Customer = function (data) {
var self = this;
if (data != null) {
ko.mapping.fromJS(data, { Orders: orderMapping }, self);
} else {
self.ID = ko.observable();
self.SortOrder = ko.observable();
self.Name = ko.observable().extend({
required: true
});
self.Orders = ko.observable(); // array of Orders
self.OrdersTotal = ko.computed(function () {
return self.FirstName() + " " + self.LastName();
}, self);
}
“orderMapping
”简单地告诉 Knockout 如何使用“Order
”对象来解开在“Orders
”子数组中找到的任何数据。
var orderMapping = {
create: function (options) {
return new Order(options.data);
}
};
对于 customer
模型,我们将以不同的方式对其进行扩展,说明它是必需的,如果未提供值,则显示错误消息“* Name needed
”。
self.Name.extend({
required: {
message: '* Name needed'
}
});
最后,我们添加了一些操作方法来管理 Order
s 的 CRUD(创建、读取、更新、删除)。
Knockout 维护着一个内部的数组项索引,因此当你调用一个对数组项执行的操作时,它会在当前选定项的上下文中进行。这意味着我们不必担心将项的选定索引发送到 delete
/insert
/update
/etc。
此方法由每个现有 order
记录旁边的“x
”调用,调用时会从数组堆栈中删除选定项。
self.removeOrder = function (Order) {
self.Orders.remove(Order);
}
此方法负责将新项推送到数组。请特别注意,我们没有创建一个匿名对象,而是明确声明了所需的对象的类型。
self.addOrder = function () {
self.Orders.push(new Order({
ID: null,
Date: "",
Value: ""
}));
}
当我们向上移动到 Sales person 模型,并想创建一个 customer
时,它有一个 child
对象是一个数组(不像 order
对象那样独立)。因此,在创建新的 customer
对象时,我们还必须初始化将包含任何未来 customer
订单的数组。注意 order
s 被创建为一个空数组“[]
”。
self.addCustomer = function () {
self.Customers.push(new Customer({
ID: null,
Name: "",
Orders: []
}));
}
最后,对于初始化,我们有一个方法可以将内联 JSON 数据加载到我们声明的 Knockout ViewModel
中。注意这里的映射是如何工作的。该函数表示……加载名为 modelData
的对象,当遇到一个名为“regions
”的对象时,通过
// load data into model
self.loadInlineData = function () {
ko.mapping.fromJS(modeldata, { Regions: regionMapping, Customers: customerMapping }, self);
}
注意选项 - 它说从 modeldata
对象加载数据,当您进入一个名为 regions
的子对象时,使用 regionsmapping
方法来解开它。对于 customer
s 也是如此,使用 customermapping
。
下载的代码提供了更多细节。
标记
Knockout 的数据绑定简单而强大。通过向标记标签添加属性,我们绑定到数据模型,模型中的任何数据都会为我们在浏览器中呈现。
销售人员(顶层详情)标记
Sales person
First name:
<input data-bind="value:FirstName" />
Last name:
<input data-bind="value:LastName" />
地区标记
“foreach
”标签控件流运算符告诉 Knockout “对于每个数组项‘region
’,渲染此 div
容器的内容。”
还要注意数据绑定方法“$parent.removeRegion
”,它调用模型中一个简单的 delete
方法。
<div data-bind="foreach:Regions">
<div class="Regionbox">Region: <input data-bind="value:Name" />
<a data-bind="click: $parent.removeRegion" href="#">x</a></div>
</div>
客户标记
客户标记遵循与先前代码相同的模式。在这段代码中需要注意的是,有一个“for each
”数据绑定*嵌套在*另一个“for each
”中……我们因此说“为找到的每个 customer
记录渲染此标记,并为找到的每个 customer
记录,渲染您找到的每个 'customer.order
' 记录。”
此代码块中的另一个新概念是数据绑定“$index
”。此属性告诉 knockout 渲染当前项的“数组索引”。
<div data-bind="foreach:Customers">
<div class="Customerbox">
Customer:
<input data-bind="value:Name" /> <a href="#" data-bind="click: $parent.removeCustomer">x</a>
<span style="float:right">
Index: <span data-bind="text:$index"></span>
</span>
<a href="#" data-bind="click: addOrder">Order +</a>
<br />
<div data-bind="foreach:Orders">
---- Order date:
<input data-bind="value:Date" />Value:
<input data-bind="value:Value" /> <a href="#" data-bind="click: $parent.removeOrder">x</a>
<br />
</div> <!-- foreach Orders -->
</div>
</div> <!-- foreach Customer -->
排序插件
在我们继续进行此示例的数据交换部分之前,让我们再看一个处理 Knockout 数组和列表时非常有用的插件。它是 “Knockout Sortable”,由才华横溢的 Ryan Niemeyer 提供。
<div data-bind="sortable:Regions">
<div class="Regionbox">Region: <input data-bind="value:Name" /> <a data-bind="click: $parent.removeRegion" href="#">x</a></div>
</div>
只需将“for each
”数组 data-bin
属性替换为“sortable
”,我们的数组对象就能神奇地实现拖放排序。请看下面的动画 GIF 示例。
将数据发送到 MVC 服务器
将 datamodel
从客户端发送到服务器是通过简单的 Ajax 调用实现的。从 Knockout 序列化数据的关键是使用“ToJSON
”方法。在我们的例子中,由于我们有嵌套的数组对象,我们将通过映射方法传递它。
self.saveDataToServer = function (){
var DataToSend = ko.mapping.toJSON(self);
$.ajax({
type: 'post',
url: '/home/SaveModel',
contentType: 'application/json',
data: DataToSend,
success: function (dataReceived) {
alert('sent: ' + dataReceived)
},
fail: function (dataReceived) {
alert('fail: ' + dataReceived)
}
});
};
由于我们在服务器和客户端都有结构上映射好的模型,因此 JSON 会被 MVC 服务器解析,并直接作为服务器端数据模型可用。
public JsonResult SaveModel(SalesPerson SalesPerson)
{
// the JSON Knockout Model string sent in, maps directly
// to the "SalesPerson" model defined in SharedModel.cs
var s = SalesPerson; // we can work with the Data
// model here - save to database / update, etc.
return null;
}
就是这样 - 下载附件代码以查看更多细节并进行实验。希望它对某些人有用!
历史
- 2015 年 4 月 18 日 - 版本 1 发布