Knockout.js:如何排序table?

Knockout.js: How to sort table?

我用这个例子:

http://stevescodingblog.co.uk/real-time-system-resource-monitor-with-signalr-mvc-knockout-and-webapi/#comment-3293

Table 显示可观察数组。该数组由客户端通过 signalr 更新。一切正常。 我想添加按选定列排序。

<div id="computerInfo">

<h2>Real-time System Resource Monitor</h2>
<h5 data-bind="visible: connected">Connected to message hub</h5>

<table border="0" class="table table-striped">
    <tr>
        <th>Machine</th>
        <th>CPU %</th>
        <th>Memory Available (Mb)</th>
        <th>Total Memory (Mb)</th>
        <th>Mem Available %</th>
    </tr>

    <!-- ko foreach: machines -->
    <tr data-bind="css: { highCpu: cpu() > 90 || memPercent()<30 }">
        <td data-bind="text: machineName"></td>
        <td data-bind="text: cpu"></td>
        <td data-bind="text: memUsage"></td>
        <td data-bind="text: memTotal"></td>
        <td data-bind="text: memPercent"></td>
    </tr>
    <!-- /ko -->

</table>

$(function () {

    // The view model that is bound to our view
    var ViewModel = function () {
        var self = this;

        // Whether we're connected or not
        self.connected = ko.observable(false);

        // Collection of machines that are connected
        self.machines = ko.observableArray();

        self.headers = [
                            { title: 'Machine Name', sortKey: 'keyMachineName' },
                            { title: 'CPU', sortKey: 'keyCpu' },
                            { title: 'Mem Usage', sortKey: 'keyMemUsage' },
                            { title: 'Mem Total', sortKey: 'keyMemTotal' },
                            { title: 'Mem Percent', sortKey: 'keyMemPercent' }
        ];
        self.sort = function (header, event) {
            var sortKey = header.sortKey;
            //implementation of sort
        };
        self.sort = function (header, event) {
            var sortKey = header.sortKey;
            switch (sortKey) {
                case 'keyMachineName':
                    self.machines.sort(function (a, b) {
                        var n = a.machineName < b.machineName ? -1 : a.machineName > b.machineName ? 1 : 0;
                        alert(n);
                        return n;//1;
                    });
                    break;
                case 'keyCpu':
                    self.machines.sort(function (a, b) {
                        var n = a.cpu < b.cpu ? -1 : a.cpu > b.cpu ? 1 : 0;
                        alert(n);
                        return n;
                    });
                    break;
                case 'keyMemUsage':
                    self.machines.sort(function (a, b) {
                        var n = a.memUsage < b.memUsage ? -1 : a.memUsage > b.memUsage ? 1 : 0;
                        alert(n);
                        return n;
                    });
                    break;
            }
        };
    };

    // Instantiate the viewmodel..
    var vm = new ViewModel();

    // .. and bind it to the view
    //ko.applyBindings(vm, $("#computerInfo")[0]);

    // Get a reference to our hub
    var hub = $.connection.cpuInfo;

    // Add a handler to receive updates from the server
    hub.client.cpuInfoMessage = function (machineName, cpu, memUsage, memTotal) {

        var machine = {
            machineName: machineName,
            cpu: cpu.toFixed(0),
            memUsage: (memUsage / 1024).toFixed(2),
            memTotal: (memTotal / 1024).toFixed(2),
            memPercent: ((memUsage / memTotal) * 100).toFixed(1) + "%"
        };

        var machineModel = ko.mapping.fromJS(machine);

        // Check if we already have it:
        var match = ko.utils.arrayFirst(vm.machines(), function (item) {
            return item.machineName() == machineName;
        });

        if (!match) {
            vm.machines.push(machineModel);
        } else {
            var index = vm.machines.indexOf(match);
            vm.machines.replace(vm.machines()[index], machineModel);
        }

       // vm.machines.sort();


        //ko.applyBindings(vm, $("#computerInfo")[0]);

        //$("#infoTable").tablesorter({sortList: [0,0]});
    };

    ///


    ko.applyBindings(vm, $("#computerInfo")[0]);
    ///
    // Start the connectio
    $.connection.hub.start().done(function () {
        vm.connected(true);
    });
});

问题是: 排序函数总是 returns 0,即值总是相同的:

var n = a.machineName < b.machineName

当我进行测试时 return 1 然后排序工作正常。

当您使用

创建机器时
var machineModel = ko.mapping.fromJS(machine);

您创建了一个具有可观察属性的视图模型。在这种情况下,在您的排序方法中,您需要将它们视为可观察对象,因此您必须使用括号来使用它们。例如:

case 'keyMachineName':
    self.machines.sort(function (a, b) {
        var n = a.machineName() < b.machineName() ? -1 : a.machineName() > b.machineName() ? 1 : 0;
        alert(n);
        return n;//1;
    });
break;

jQuery tablesorter 插件不适用于 knockout。干脆算了。

当 DOM 节点在其不知情的情况下四处移动时,Knockout 无法处理,但这正是 jQuery Tablesorter 所做的。

但是 Knockout 已经做好了为你排序 table 的准备。

以下示例功能

  • 完全可配置的列,即未在视图中进行硬编码
  • 单击
  • sortable table 列,包括已排序列的视觉反馈
  • 交替 ascending/descending 排序
  • 通过 Knockout extender
  • 格式化值
  • 在一个时间间隔内自动更新值(此用例中的可能场景)
  • 值更新时自动重新排序

ko.extenders.formatted = function (underlyingObservable, formatFunction) {
    underlyingObservable.formatted = ko.computed(function () {
        return formatFunction(underlyingObservable());
    });
};

function Machine(data) {
    var self = this,
        memoryFormat = function (val) { return Math.round(val / 1024, 0).toLocaleString(); };
        
    self.machineName = ko.observable().extend({formatted: function (val) { return val; }});
    self.cpu = ko.observable(0).extend({formatted: function (val) { return val.toFixed(1); }});
    self.memUsage = ko.observable(0).extend({formatted: memoryFormat});
    self.memTotal = ko.observable(0).extend({formatted: memoryFormat});
    self.memPercent = ko.computed(function () {
        return self.memUsage() / self.memTotal() * 100;
    }).extend({formatted: function (val) { return val.toFixed(1) + '%'; }});
    self.conditions = ko.computed(function () {
        return {
            highCpu: self.cpu() > 50,
            lowMem: self.memPercent() < 30
        };
    });
    
    self.update(data);
};
Machine.prototype.update = function (data) {
    ko.mapping.fromJS(data, {}, this);
};

function MachineList() {
    var self = this;

    self.connected = ko.observable(false);
    self.machines = ko.observableArray();
    self.headers = [
        {title: 'Machine Name', key: 'machineName', cssClass: ''},
        {title: 'CPU %', key: 'cpu', cssClass: 'right'},
        {title: 'Mem Usage (MB)', key: 'memUsage', cssClass: 'right'},
        {title: 'Mem Total (MB)', key: 'memTotal', cssClass: 'right'},
        {title: 'Mem Available %', key: 'memPercent', cssClass: 'right'}
    ];
    self.sortHeader = ko.observable(self.headers[0]);
    self.sortDirection = ko.observable(1);
    self.toggleSort = function (header) {
        if (header === self.sortHeader()) {
            self.sortDirection(self.sortDirection() * -1);
        } else {
            self.sortHeader(header);
            self.sortDirection(1);
        }
    };

    // use a computed to subscribe to both self.sortHeader() and self.sortDirection()
    self.sortMachines = ko.computed(function () {
        var sortHeader = self.sortHeader(),
            dir = self.sortDirection(),
            tempMachines = self.machines(),
            prop = sortHeader ? sortHeader.key : "";
        if (!prop) return;
        tempMachines.sort(function (a, b) {
            var va = ko.unwrap(a[prop]),
                vb = ko.unwrap(b[prop]);                
            return va < vb ? -dir : va > vb ? dir : 0;
        });
        self.machines.notifySubscribers();
    });
    self.insertMachine = function (data) {
        var machine = ko.utils.arrayFirst(vm.machines(), function (item) {
            return ko.unwrap(item.machineName) == ko.unwrap(data.machineName);
        });

        if (machine) machine.update(data);
        else vm.machines.push(new Machine(data));
        self.sortMachines();
    };
};

// -------------------------------------------------------------------------------

// connection mockup
$.connection = {
    cpuInfo: {client: {}},
    hub: {start: function() { return $.Deferred().resolve(); }}
};

var vm = new MachineList(),
    hub = $.connection.cpuInfo;

hub.client.cpuInfoMessage = function (data) { vm.insertMachine(data); };

// start the connection as soon as possible...
$.connection.hub.start().done(function () {
    vm.connected(true);
    $(function () {
        // ...but don't apply bindings before the document is ready
        ko.applyBindings(vm, $("#computerInfo")[0]);
    });

    // create some random sample data
    setInterval(function () {
        var GB = 1024 * 1024;
        ['A', 'B', 'C', 'D'].forEach(function (name) {
            hub.client.cpuInfoMessage({
                machineName: name,
                cpu: getRandomInt(0, 1000) / 10,
                memUsage: getRandomInt(5 * GB, 7 * GB),
                memTotal: 10 * GB
            });
        });
    }, 1000);
});

// -------------------------------------------------------------------------------

function getRandomInt(min, max) {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min + 1)) + min;
}
.table { border-collapse: collapse; }
.table th, .table td { border: 1px solid silver; padding: 0 2px; }
.sortable { cursor: pointer; }
.table-striped tbody tr:nth-child(odd) { background-color: #f7f7f7; }
.highCpu { color: red; }
.lowMem { color: red; }
.right { text-align: right; }
.sorted { background-color: #B5E0FF; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>

<div id="computerInfo">
  <h2>Real-time System Resource Monitor</h2>
  <h5 data-bind="visible: connected">Connected to message hub</h5>

  <table border="0" class="table table-striped">
    <thead>
      <tr data-bind="foreach: headers">
        <th class="sortable" data-bind="click: $root.toggleSort, text: title, css: {sorted: $data === $root.sortHeader()}"></th>
      </tr>
    </thead>
    <tbody data-bind="foreach: machines">
      <tr data-bind="foreach: $root.headers, css: conditions">
        <td data-bind="text: $parent[key].formatted, css: cssClass"></td>
      </tr>
    </tbody>
  </table>
</div>