65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 Knockout 和 JQuery 实现客户端 Grid 显示、编辑、分页、调整大小、过滤和排序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (14投票s)

2017 年 7 月 26 日

CPOL

5分钟阅读

viewsIcon

17750

downloadIcon

422

使用 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.itemsself.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%;">
                &nbsp;<span style="background-color: lightgrey">
                    <a href="#" style="margin: 15px;" data-bind="text: $data, 
                       click: $root.moveToPage "></a>
                </span>&nbsp;
   </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。

© . All rights reserved.