使用 Knockout 和 JQuery 实现客户端 Grid 显示、编辑、分页、调整大小、过滤和排序
使用 Knockout 和 JQuery 实现客户端 Grid 显示、编辑、分页、调整大小、过滤和排序
引言
随着范式从 Web Form(代码隐藏)转向客户端脚本,大多数应用程序都基于 JavaScript 构建的客户端框架进行开发。Knockout 和 JQuery 是流行的客户端脚本之一。在每个应用程序(尤其是业务应用程序)中,我们都需要以表格结构显示数据,并包含不同的过滤、分页、排序、编辑、调整大小等功能。为了涵盖这些提到的功能,我将在本文中使用 Knockouts 框架,并借助 JQuery 来讲解所有要点。
完成本文后,读者应该能够理解 Knockout、JQuery、事件、函数、选择器等。
可以从以下 URL 下载 Knockout 和 JQuery 脚本:
以下是我们即将讨论的核心功能:
- 绑定(显示)
- 编辑
- 分页
- 调整大小
- 过滤
- 排序
绑定(显示)
在 Knockout 中,我们通过将绑定(结果集合)应用于表格的主体并进行迭代来渲染表格/Grid。在每次迭代中,HTML 内容(标签)会与动态数据一起渲染,这些数据绑定在 </TD>
(HTML 标签)中。
首先,我们需要创建可观察对象来保存和绑定数据。
var self = this;//
self.items = ko.observableArray();
self.itemsTemp = ko.observableArray();
现在,我们需要从服务器获取数据。为了实现这一点,我们将进行一次 Ajax 调用。请求成功后,结果将同时存储在 self.items
和 self.items.Temp
中。将相同的数据存储在两个可观察对象中有原因,这将在本文后面的(过滤阶段)进行讨论。
self.loadData = function (from, count) {
$.ajax({
type: "POST",
url: KoGrid.aspx/GetEmployeeList',
contentType: "application/json;",
data: "{'from':" + from + ",'count':" + count + "}",
dataType: "json",
success: function (results) {
self.items(results.d.employeeuserList);
self.itemsTemp(results.d.employeeuserList);
},
error: function (err) { alert(err.status + " - " + err.statusText); }
});
}
代码隐藏的 Web 方法将从数据库获取数据。为了避免复杂性并专注于 Grid,假数据将从 Web 方法生成。
[WebMethod]
public static RetunObject GetEmployeeList(int from, int count){
List<employeeuser> empList = new List<employeeuser>(){
new employeeuser {userId=1, name = "A", price = 5.5, sales = 2 },
new employeeuser {userId=2, name = "AB", price = 6.5, sales = 32 },
new employeeuser {userId=3, name = "Al", price = 7.5, sales = 42 },
new employeeuser {userId=4, name = "FAD", price = 8.5, sales = 52 },
new employeeuser {userId=5, name = "Az", price = 9.5, sales = 62 },
new employeeuser {userId=6, name = "VAF", price = 1.1, sales = 72 },
new employeeuser {userId=7, name = "AG", price = 2.5, sales = 83 },
new employeeuser {userId=8, name = "VAH", price = 3.2, sales = 92 },
new employeeuser {userId=9, name = "AI", price = 4.5, sales = 102 },
new employeeuser {userId=10, name = "AJ", price = 44.5, sales = 112 },
new employeeuser {userId=11, name = "A", price = 5.5, sales = 2 },
new employeeuser {userId=12, name = "VAB", price = 6.3, sales = 32 },
new employeeuser {userId=13, name = "Al", price = 7.5, sales = 45 },
new employeeuser {userId=14, name = "AD", price = 8.5, sales = 52 },
new employeeuser {userId=15, name = "RAz", price = 9.4, sales = 62 },
new employeeuser {userId=16, name = "AF", price = 1.5, sales = 78 },
new employeeuser {userId=17, name = "AG", price = 2.5, sales = 82 },
new employeeuser {userId=18, name = "FVAH", price = 3.5, sales = 92 },
new employeeuser {userId=19, name = "AI", price = 4.5, sales = 102 },
new employeeuser {userId=20, name = "AJ", price = 44.6, sales = 102 },
new employeeuser {userId=21, name = "FA", price = 5.5, sales = 2 },
new employeeuser {userId=22, name = "AB", price = 6.5, sales = 32 },
new employeeuser {userId=23, name = "RAl", price = 7.5, sales = 12 },
new employeeuser {userId=24, name = "AD", price = 8.7, sales = 59 },
new employeeuser {userId=25, name = "Az", price = 9.5, sales = 62 },
new employeeuser {userId=26, name = "AF", price = 1.5, sales = 73 },
new employeeuser {userId=27, name = "VAG", price = 2.5, sales = 82 },
new employeeuser {userId=28, name = "AH", price = 3.5, sales = 92 },
new employeeuser {userId=29, name = "AI", price = 4.5, sales = 107 },
new employeeuser {userId=30, name = "AJ", price = 44.8, sales = 112 }
};
return new RetunObject() { employeeuserList = empList.Skip(from).Take(count).ToList(),
TotalCount = empList.Count() };
}
public class employeeuser {
public int userId { get; set; }
public string name { get; set; }
public int sales { get; set; }
public double price { get; set; }
}
public class RetunObject{
public int TotalCount { get; set; }
public List<employeeuser> employeeuserList { get; set; }
}
成功将数据从服务器检索到可观察数组后,下面的表格将通过迭代 itemsTemp
可观察对象中的元素来动态渲染。
<table id="tblHeading" border="1" style="width: 100%;">
<thead>
<tr>
<td>
<a class="cursor bold " >UserId</a>
</td>
<td>
<a class="cursor bold" >Name</a>
</td>
<td>
<a class="cursor bold" >Price</a>
</td>
<td>
<a class="cursor bold" >Sales</a>
</td>
<td>
</td>
</tr>
</thead>
<tbody data-bind="foreach: itemsTemp">
<tr>
<td id="userId" data-bind="text: userId"></td>
<td data-bind="text: name"></td>
<td data-bind="text: price"></td>
<td data-bind="text: sales"></td>
<td>
<button value: userId">Edit</button>
<button value: userId">Delete</button>
</td>
</tr>
</tbody>
</table>
在页面加载时调用该函数来加载和渲染 Grid。
self.formLoad = function () {
self.loadData(0, 5);
}
self.formLoad();
在将模型视图与视图绑定后,输出将是下面的表格。
$(document).ready(function () {
ko.applyBindings(new PagedGridModel());
});
输出如下
编辑
成功创建 Grid 后,现在我们来讨论 Grid 的编辑功能。简单来说,我们需要将点击事件与编辑和删除按钮上的相关函数进行绑定。
<button data-bind="click: $root.EditClick, value: userId">Edit</button>
<button data-bind="click: $root.DeleteClick, value: userId">Delete</button>
分别在点击编辑和删除按钮时将调用以下函数。这里,我们仅显示带有行 ID 的警告。您可以根据自己的需求使用它。
self.EditClick = function (a) {
alert("UserId " + a.userId + " edit");
}
self.DeleteClick = function (a) {
alert("UserId " + a.userId + " delete");
}
点击编辑或删除按钮后,输出如下。
分页
Grid 的分页是当有大量记录需要显示时使用的功能之一。我们可以通过添加几行代码在 Grid 中实现分页。
首先,在 ViewModel 中添加分页可观察数组。
self.itemsPaging = ko.observableArray();
self.pageSize = ko.observable();
self.pageSize
是 Grid 的大小,在加载时将作为默认值填充,或者当用户更改 Grid 大小值时进行更新。这将在调整大小阶段进一步讨论。
现在,创建分页函数,它将在 self.itemsPaging
数组中获取分页范围值。
self.paging = function (totalRecord) {
self.itemsPaging([]);
var filtercount = self.pageSize();
var totalIteration = Math.ceil(totalRecord / filtercount);
if (totalIteration > 1) {
for (var i = 1; i <= totalIteration; i++) {
self.itemsPaging.push(i);
}
}
}
现在,下面的 HTML 将进行迭代。在每次迭代中,一个带有页码的 span
将作为显示值与点击事件(“moveToPage
”)进行绑定。
<div data-bind="foreach: itemsPaging" style="font-size: xx-large; width: 100%;">
<span style="background-color: lightgrey">
<a href="#" style="margin: 15px;" data-bind="text: $data,
click: $root.moveToPage "></a>
</span>
</div>
分页实现后的输出如下。
分页的完整功能还剩一件事。点击页码时,相应地从服务器或本地获取数据。为了实现这一点,我们将“moveToPage
”函数绑定到每个数字。在 moveToPage
中,我们获取要访问的页面数量,并根据页面大小和页码计算数量。之后,我们简单地调用“loadData
”函数,该函数将从服务器获取数据。通过一次性从服务器获取所有数据并将其保存在临时可观察数组中,可以实现相同的功能。
self.moveToPage = function (PageNumber) {
var count = self.pageSize();
var from = (PageNumber - 1) * count;
self.loadData(from, count);
}
调整大小
Grid 调整大小提供了限制每页记录数的 [功能]。分页功能与调整大小直接相关,因为当我们更改页面的大小(记录数)时,Grid 下方的分页数字也会更新。实现调整大小功能也只需要写几行代码。
在表单上添加下拉列表(Select
)以及一些用于调整大小的选项。
<b>Page Size: </b>
<select id="Scount" data-bind="value: pageSize, event: { change: pageSizeChange }"
class="ddlWidth260 ddl">
<option value="5">5</option>
<option value="10">10</option>
<option value="20">20</option>
<option value="50">50</option>
</select>
现在我们需要将下拉列表(Select)的值绑定到可观察对象,以便获取当前大小值。
在这里,我们将使用之前提到的“pageSize
”可观察对象。之后,在 change
事件上将 Select 与“PageSizeChange
”函数进行绑定。在 pageSizeChange
函数中,我们只需调用从零(0)到页面大小的记录,分页也会相应重置。
self.pageSizeChange = function () {
self.loadData(0, self.pageSize());
}
过滤
Grid 的另一个非常重要的功能是过滤记录。同样,我们可以通过在 Grid 页眉上渲染文本框(input)并将其与相关事件“过滤”进行绑定来实现过滤记录。
首先,我们需要添加输入框,为其分配“filter”类,以及“Keyup
”事件和列名属性,以便它可以识别需要过滤哪个列,并将其应用于我们希望显示过滤功能的*所有* </TD>
(HTML 标签)中。我们可以通过在表格中添加以下 HTML 来实现此目的。
<thead>
<tr>
<td>
<a class="cursor bold " data-bind="click:
$root.sortGrid.bind(this, 'userId')">UserId</a><br />
<input type="text" class="width100 filter" columnname="userId"
data-bind="event: { keyup: $root. filtering.bind(this, 'userId') }" />
</td>
<td>
<a class="cursor bold" data-bind="click:
$root.sortGrid.bind(this, 'name')">Name</a><br />
<input type="text" class="width100 filter" columnname="name"
data-bind="event: { keyup: $root. filtering.bind(this, 'name') }" />
</td>
<td>
<a class="cursor bold" data-bind="click:
$root.sortGrid.bind(this, 'price')">Price</a><br />
<input type="text" class="width100 filter" columnname="price"
data-bind="event: { keyup: $root. filtering.bind(this, 'price') }" />
</td>
<td>
<a class="cursor bold" data-bind="click:
$root.sortGrid.bind(this, 'sales')">Sales</a><br />
<input type="text" class="width100 filter" columnname="sales"
data-bind="event: { keyup: $root. filtering.bind(this, 'sales') }" />
</td>
<td>
</td>
</tr>
</thead>
然后创建一个名为“filtering”的函数,在该函数中我们将根据用户输入过滤数据。在此函数中,我们首先获取所有具有“filter
”类的输入。如前所述,相同的数据(结果集合)将存储在两个可观察对象中。之所以这样做,是为了保存用户过滤的数据。如果我们只将数据存储在一个可观察数组中,并且用户进行了一些过滤,它将过滤结果,但如果用户删除了所有过滤器,我们将没有完整的数据,因为我们已经过滤了它。这就是将数据存储在两个可观察对象中的原因,以便如果用户在 Grid 上应用过滤然后重置 Grid,来自第二个可观察对象(self.items()
)的数据将存储到 self.itemsTemp
。通过这样做,我们可以避免不必要的服务器调用。
现在,所有获取到的输入都将被迭代以对 Grid 应用相关的过滤,最后将结果数据重新绑定到表格。
self.itemsTemp(self.items());
这是过滤前的 Grid 输出结果。
应用过滤后的 Grid 输出结果。
重置/移除过滤后的 Grid 输出结果。
排序
Grid 的另一个重要功能是排序。在 Knockout 或任何其他客户端框架中,我们可以通过将事件与页眉锚文本绑定并将列名传递进去,以便识别要对数据进行排序的列,从而非常轻松地实现排序。
<thead>
<tr>
<td>
<a class="cursor bold " data-bind="click:
$root.sortGrid.bind(this, 'userId')">UserId</a><br />
<input type="text" class="width100 filter" columnname="userId"
data-bind="event: { keyup: $root.filtering.bind(this, 'userId') }" />
</td>
<td>
<a class="cursor bold" data-bind="click:
$root.sortGrid.bind(this, 'name')">Name</a><br />
<input type="text" class="width100 filter" columnname="name"
data-bind="event: { keyup: $root.filtering.bind(this, 'name') }" />
</td>
<td>
<a class="cursor bold" data-bind="click:
$root.sortGrid.bind(this, 'price')">Price</a><br />
<input type="text" class="width100 filter" columnname="price"
data-bind="event: { keyup: $root.filtering.bind(this, 'price') }" />
</td>
<td>
<a class="cursor bold" data-bind="click:
$root.sortGrid.bind(this, 'sales')">Sales</a><br />
<input type="text" class="width100 filter" columnname="sales"
data-bind="event: { keyup: $root.filtering.bind(this, 'sales') }" />
</td>
<td>
</td>
</tr>
</thead>
最后,创建绑定事件,该事件将根据该列对记录进行排序。
self.sortGrid = function (data, obj) {
self.itemsTemp.sort(function (a, b) {
var x = a['' + data];
var y = b['' + data];
if (x < y) { return -1; }
if (x > y) { return 1; }
return 0;
});
}
这是在价格 列
上排序的 Grid。