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

在 C# MVC 中使用 KnockoutJS 进行搜索、筛选和排序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (27投票s)

2014 年 9 月 24 日

CPOL

10分钟阅读

viewsIcon

89344

downloadIcon

1831

关于如何使用 KnockoutJS 实现搜索、筛选和排序的旋风式教程

引言

KnockoutJS 是客户端数据绑定的绝佳伴侣,尤其是在与 ASP.NET MVC 结合使用时。然而,有时您需要做一些事情,虽然在 JSFiddle 等网站上有很多示例,但却没有解释代码是如何工作的,文档也不够深入,让人摸不着头脑,不知道为什么事情会这样运作。我最近试图为 Knockout 中的可观察数组实现一个可靠的“筛选和搜索”组合解决方案。数组中的数据通过 Ajax 从服务器发送到浏览器。本文将介绍我使用的方法,并清楚地解释以下内容

  • 如何设置一个简单的可观察数组
  • 如何使用 Ajax 从客户端浏览器到服务器获取 JSON 数据列表并填充 Knockout 可观察数组
  • 如何使用 Knockout Mapping 插件将数据从服务器链接到客户端
  • 如何使用 Knockout 实现组合筛选和搜索机制

这是成品截图!

实现此目标的方法有很多种,这是我的方法——它简单且有效——无需多言。

设置 - 服务器端

我们需要做的第一件事是设置一些示例数据来使用。让我们先处理 MVC C# 部分。

Data Model

我们创建一个数据模型 MVC 服务器端来生成一些随机数据发送到服务器。为此,构造一个具有一些基本成员的简单类。

public class ClientModel
{
    public string ClientName { get; set; }
    public string Address { get; set; }
    public string Phone { get; set; }
    public bool Active { get; set; }
    public int Status { get; set; }
}

然后我们将添加一个生成随机数据列表的方法

public List<clientmodel> GetClients()
{
    List<clientmodel> rslt = new List<clientmodel>();
    Random rnd = new Random();
    for (int i = 0; i < 50; i++)
    {
        int r = rnd.Next(1, 100);
        int z = rnd.Next(1, 5000);
        ClientModel cm = new ClientModel();
        cm.ClientName = i.ToString() + " Name ";
        cm.Address = i.ToString() + " Address " + z.ToString();
        cm.Phone = "Phone " + r.ToString();
        int x = rnd.Next(0, 2);
        if (x == 0)
            cm.Active = true;
        else cm.Active = false;
        cm.Status = rnd.Next(0, 4);
        rslt.Add(cm);
    }
    return rslt;        
}

然后我们创建一个包装类来承载我们创建的数据并将其发送回浏览器。

public class ClientList 
{
public List<clientmodel> Clients {get; set;}
 
public ClientList()
{
    Clients = new List<clientmodel>();
}
 
}

这是一个陷阱!……当你在 Knockout 中做任何复杂的模型,并且在服务器/客户端之间来回发送数据时,重要的是要让事情*精确地*对齐,尤其是在需要包装/解包子可观察数组时。在这个例子中,我确保我将我的数据列表称为“Clients”,这样,至关重要的是,当我在客户端使用映射解包它时,它能正确匹配我客户端数据的可观察数组。你将在文章后面看到这个双向舞蹈的另一半。

控制器 (Controller)

下一步是创建一个使用模型生成随机数据的控制器。我们创建包装列表类,用随机数据填充它,然后使用超级棒的 Newtonsoft JSON 工具将列表序列化以供浏览器使用。

注意!!要使用 Newtonsoft,请从 NuGet 获取并记住引用它。

using Newtonsoft.Json;

public string ClientAjaxLoader()
{
    ClientModel cm = new ClientModel();
    ClientList cl = new ClientList();
    cl.Clients.AddRange(cm.GetClients());
    var jsonData = JsonConvert.SerializeObject(cl);
    return jsonData;
}

设置 - 客户端

由于这是一个 MVC 应用程序,为了本文的简洁性,我们将进入自动生成的 index.cshtml,删除其中的所有内容并添加我们需要的骨架代码。
网上有很多 Knockout 教程涵盖基础知识——所以这里只是简单解释一下我是如何在客户端设置的。

步骤 1

创建主 Knockout ViewModel。在此阶段,它非常简单,包含一个名为“Clients”的可观察数组

var viewModel = function () {
    var self = this;
    self.Clients = ko.observableArray([]);
}

第二步

创建一个与来自服务器的 ClientModel 数据对应的 Model

 var ClientDetail = function (data) 
{ 
    var self = this; 
    if (data != null) 
    { 
        self.ClientName = ko.observable(data.ClientName); 
        self.Address = ko.observable(data.Address); 
        self.Phone = ko.observable(data.Phone); 
        self.Active = ko.observable(data.Active); 
        self.Status = ko.observable(data.Status); 
    } 
}

步骤 3

目前,ViewModel 必须手动将数据推送到其 Clients 数组。让我们改变这一点,以便可以通过传入数据作为参数来填充数组。

var viewModel = function (data) {
    if (data != null)
        {
        // DO SOMETHING
}
    var self = this;
    self.Clients = ko.observableArray([]);
}

做一些事情”部分,我们将引入“映射”。映射允许我们更准确地定义名为“Clients”的成员实际包含什么。

var viewModel = function (data) {
    if (data != null)
        {
        ko.mapping.fromJS(data, { Clients: clientMapping }, self);
        }
    var self = this;
    self.Clients = ko.observableArray([]);

我们调用 ko.mapping.fromJS,传入以下参数:

  1. 数据包(将从服务器接收)
  2. 指示它如何使用映射解包数据
  3. 把它放在哪里(它自己内部)

步骤 4

创建映射函数“ClientMapping”,它将“解包”服务器端生成的 JSON string

var clientMapping = {
    create: function (options) {
        return new ClientDetail(options.data);
    }
};

这是我们代码中第一次提到我们最初的“ClientDetail”模型以及它与 ViewModel 的关系。所以我们告诉客户端的是……你有一个数组。当数据传入时,获取该数据,并在该数据**内部**寻找一个名为 CLIENTS 的记录块,并解包在该块中找到的每条记录,将每条记录转换为模型类型“ClientDetail”。这是陷阱的一部分,而且很重要……请记住,从服务器传下来的数据必须用映射筛选器文本的名称标记,否则它将找不到它,并且您的数据将无法映射。再次强调——不要忘记!

MVC 服务器端

ClientList cl = new ClientList();

这包含一个名为“Clients”的主成员

public List<clientmodel> Clients {get; set;}</clientmodel>

客户端 ViewModel 有一个名为“Clients”的数组成员

self.Clients = ko.observableArray([]);

并通过 KO 映射过程“{ Clients: clientMapping }”将两者链接在一起

ko.mapping.fromJS(data, { Clients: clientMapping }, self);

使用 Ajax 填充 Knockout 可观察数组

为了将数据从服务器获取到客户端,我们将在 ViewModel 本身中设置一个函数,并通过点击链接来调用它。

self.getClientsFromServer = function () {
    $.ajax("/home/ClientAjaxLoader/", {
        type: "GET",
        cache: false,
    }).done(function (jsondata) {
        var jobj = $.parseJSON(jsondata);
        ko.mapping.fromJS(jobj, { Clients: clientMapping }, self);
        });
}

所以这非常简单,我们调用 URL“/home/ClientAjaxLoader”(它调用所使用的域,在我们的测试案例中是“localhost”)。在“done”回调中,我们获取接收到的数据“jsondata”,并使用“parseJSON”将其转换为 JavaScript 对象。然后我们调用 Knockout 映射工具来解包并将 JSON 数据映射到我们的客户端数组中

要显示数据,我们需要连接一些 HTML 控件并绑定 Knockout 数组。
创建一个简单的表格,并使用“for each”绑定用客户端详细信息数据填充它。

最后,添加一个链接来调用新方法

请注意,在这种情况下,数据绑定是一个 OnClick 绑定,它调用我们创建的从服务器获取数据的方法。
好的,我们正在顺利进行,这里是目前为止所有连接起来的截图,一旦我们点击“从服务器获取客户端”链接,数据就会流动。

(它不会赢得任何用户体验比赛,但它有效!)

搜索和筛选

好的,这就是我们参加这个派对的原因——比赛开始吧!

这个搜索和筛选方法基于 Knockout 的内置“observable”行为和双向绑定。简而言之,我们要做的是:

  • 创建 viewmodel 的一个新成员,它是一个简单的可观察量,充当我们的“搜索过滤器”。
  • 创建一个计算成员,它观察搜索过滤器,当它改变时,对于客户端数组中的每个记录,将数组中的值与搜索值进行比较,如果匹配,则在“匹配记录”数组中返回该客户端记录。
  • 在“for each”中连接新的计算成员以显示过滤结果。

让我们从简单开始,放置一个单一的搜索,以查找任何具有“ClientAddress”值*包含*我们搜索项值(换句话说,通配符搜索)的客户端记录。

步骤 1

创建一个新的可观察成员来保存我们的 search string

self.search_ClientAddress = ko.observable('');

第二步

创建一个新的计算成员,当我们的 search string 改变时,扫描客户端记录并筛选出我们想要的内容

self.filteredRecords = ko.computed(function () {
    return ko.utils.arrayFilter(self.Clients(), function (rec) {
            return (
                      (self.search_ClientAddress().length == 0 || 
                       rec.Address().toLowerCase().indexOf
                       (self.search_ClientAddress().toLowerCase()) > -1)
                   )        
    });
});

在此计算成员中,我们使用 KO.UTILS.arrayFilter 遍历 Clients 数组,对于每条记录,进行比较,并仅返回我们允许通过筛选的 Clients 数组中的记录。看看发生了什么……arrayFilter 的第一个参数是要搜索的数组,第二个参数是执行筛选的函数。在筛选函数中,我们传入一个名为“rec”的变量——它代表当时正在检查的单个数组记录对象。所以我们基本上是说“遍历我们的客户端列表,对于遇到的每条记录,测试当前正在检查的记录是否应该通过数组筛选器。”

在这种情况下,我们说,如果 search string 中有零数据(length == 0),则允许记录通过

self.search_ClientAddress().length == 0

或者

如果 search string 的值包含在使用“arrayfilter”扫描的客户端记录中

rec.Address().toLowerCase().indexOf(self.search_ClientAddress().toLowerCase()) > -1

正如我们很快就会看到的,arrayFilter 方法是一个极其强大的工具。

要检查它是否有效,最后要做的是放置一个 HTML 控件并将其绑定到新的可观察 search string 成员 (self.search_ClientAddress)。

地址 包含: <input data-bind=" value:=" valueupdate:="" />

在这里,我们绑定到新成员,并且关键地,使用“valueUpdate”触发器“afterKeyDown”向搜索可观察量发送消息,表明值已更新。这会触发计算成员执行其操作。
为了表明它正在工作,我们将更改原始显示表,从显示“Clients”数据数组变为显示“filteredRecords”返回的数组。
我们这里采取的方法是,我们原始的客户端数据数组保持不变,我们对筛选搜索结果中返回的数据集中的数据副本进行任何可视化/操作。

所以,在地址搜索框中输入一些值后,结果如下。“44”值在“Address”字段的任何部分找到,因此返回两条记录。

现在我们知道了搜索的基本原理,我们可以快速构建一个强大的组合搜索和筛选功能。我们将添加功能,允许用户根据 Client.Name 的第一部分、Client.Address(如前所述)进行搜索,并在该搜索中,只筛选显示 ActiveInactive 客户端记录,或状态值大于或等于 2 的记录。

步骤 1

添加更多搜索可观察量!

self.search_ClientName = ko.observable('');
self.search_ClientActive = ko.observable();
self.search_ClientStatus = ko.observable('');

第二步

添加 HTML 来设置这些值

步骤 3

一些 JQuery 的优点是绑定到这些链接上的 Click 事件

$(function () {
                $('#showAll').click(function () {
                    vm.search_ClientActive(null);
                    vm.search_ClientStatus(null);
                });
                $('#showActive').click(function () {
                    vm.search_ClientActive(true);
                });
                $('#showInActive').click(function () {
                    vm.search_ClientActive(false);
                });
                $('#showStatus').click(function () {
                    vm.search_ClientStatus(2); // set filter to show only status with value >= 2
                });
            });

(请注意,“显示所有”操作会将搜索可观察量的值重置为 null,从而触发列表显示所有记录)。

步骤 4

最后,我们将扩展我们的计算成员,以包含上面介绍的参数

self.filteredRecords = ko.computed(function () {
    return ko.utils.arrayFilter(self.Clients(), function (rec) {
            return (
                      (self.search_ClientName().length == 0 || ko.utils.stringStartsWith
                      (rec.ClientName().toLowerCase(), self.search_ClientName().toLowerCase()))
                        &&
                      (self.search_ClientAddress().length == 0 || rec.Address().toLowerCase().indexOf
                      (self.search_ClientAddress().toLowerCase()) > -1)
                        &&
                      (self.search_ClientActive() == null || rec.Active() == self.search_ClientActive())
                        &&
                      (self.search_ClientStatus() == null || rec.Status() >= self.search_ClientStatus())
                   )                
    });
});

所以,一切都配合得很好。

显示所有记录

只显示活动记录

显示活动记录,并根据 ClientNameClientAddress 进行搜索

最后一个筛选技巧,添加到混淆中,其中 Client.Status 值大于或等于 2

列表排序

能够应用筛选器很棒,更酷的是你可以在筛选结果中搜索——也许唯一缺失的部分是排序功能。所以这里有一个快速的补充来实现这一点

在服务器端,我将为我们的客户端模型添加一个“SortCode”字段,并随意填充一些数据,以便我们可以在其上进行排序。

cm.Status = rnd.Next(0, 4);

                switch (cm.Status) {

                    case 0:  
                            cm.SortCode = "AAA";
                            break;
                    case 1:  
                            cm.SortCode = "BBB";
                            break;
                    case 2: 
                            cm.SortCode = "CCC";
                            break;
                    case 3: 
                            cm.SortCode = "DDD";
                            break;
                    case 4: 
                            cm.SortCode = "EEE";
                            break;
                    default :
                            break;
                
                }

客户端,我们添加一个 KO observable,计算出的筛选数组可以监视它

self.isSortAsc = ko.observable(true);

我们添加一些标记/Jquery 来打开/关闭它

翻转排序代码

$('#flipSortCode').click(function () {
                   vm.isSortAsc(!vm.isSortAsc());
               });

接下来,我们将新的可观察量添加到计算函数中,最后,将 SORT 函数链式添加到计算成员中

self.filteredRecords = ko.computed(function () {            

            return ko.utils.arrayFilter(self.Clients(), function (rec) {
                return (
                          (self.search_ClientName().length == 0 ||
                                ko.utils.stringStartsWith(rec.ClientName().toLowerCase(), 
                                self.search_ClientName().toLowerCase()))
                            &&
                          (self.search_ClientAddress().length == 0 ||
                                rec.Address().toLowerCase().indexOf
                                (self.search_ClientAddress().toLowerCase()) > -1)
                            &&
                          (self.search_ClientActive() == null ||
                                rec.Active() == self.search_ClientActive())
                            &&
                          (self.search_ClientStatus() == null ||
                                rec.Status() >= self.search_ClientStatus())
                            &&
                          (self.isSortAsc() != null)
                       )
            }).sort(            
               function (a, b) {
                   if (self.isSortAsc() === true)
                       {
                       var x = a.SortCode().toLowerCase(), y = b.SortCode().toLowerCase();
                       return x < y ? -1 : x > y ? 1 : 0;
                       }
                   else {
                       var x = b.SortCode().toLowerCase(), y = a.SortCode().toLowerCase();
                       return x < y ? -1 : x > y ? 1 : 0;
                       }                  
                    }
                );
        });

因此,点击“翻转排序”现在可以根据需要对我们的列表进行升序/降序排序。

摘要

本文带您快速了解了如何使用非常强大的“Ko.Utils.arrayFilter”功能,通过 Knockout 实现简洁快速的搜索和筛选功能,并通过 Ajax 从 MVC 后端将数据服务器到 ViewModel。一如既往,技巧在于细节,所以不要忘记那些陷阱。如果您喜欢这篇文章,请投票并评论!

历史

  • 版本 1 - 2014年9月25日
  • 版本 1.1 - 2014年9月26日 - 修复 - 必须重新加载数据
  • 版本 1.2 - 2014年9月30日 - 文章中添加了“sort”并更新了示例代码
© . All rights reserved.