2016 年 12 月 23 日星期五

使用絕對定位資料進行排序

在 DataTables 中資訊的排序可能是我在這個部落格中最常涵蓋的主題(例如 枚舉日期,未來還會有更多),但那是因為它是一個如此豐富的主題!DataTables 被廣泛應用於從太空望遠鏡遙測到業餘足球聯賽的各種應用中,因此它需要能夠處理許多不同的數據類型。DataTables 核心僅附帶一組基本的數據排序函數(字串、數字、貨幣、百分比、ISO8601 日期),因此,對於超出此範圍的任何內容,DataTables 提供了 排序外掛程式 API,您可以使用它來定義自己的排序方法。

通常,使用排序方法時,您會進行線性排序,即降序排序與升序排序完全相反,並且所有內容都按簡單的比較(數值或字元碼點)進行排序。這篇文章將會有所不同:我將介紹一個非線性的可配置排序外掛程式,您可以使用它將特定的數據片段保留在列的頂部或底部,無論排序方向和該項目的值如何。

讓我們完全倒過來寫這篇文章!我們先從一個完成的範例開始,然後描述 API,如果您只想使用它而不需要任何實作細節,最後,如果您對更多技術方面感興趣(當然您會感興趣 - 這是一個開發人員部落格!),我將討論實作。

範例

一個範例勝過千言萬語,所以讓我們考慮以下表格

名稱 職位 辦公室 開始日期 薪資
Tiger Nixon 系統架構師 愛丁堡 2011/04/25 $320,800
Garrett Winters 會計師 東京 2011/07/25 $170,750
Ashton Cox 初級技術作家 舊金山 2009/01/12 $86,000
未知 資深 Javascript 開發人員 愛丁堡 2012/03/29 待定
Airi Satou 會計師 東京 2008/11/28 $162,700
Brielle Williamson 整合專家 紐約 2012/12/02 $372,000
未知 銷售助理 舊金山 2012/08/06 待定
Rhona Davidson 整合專家 東京 2010/10/14 $327,900
Colleen Hurst Javascript 開發人員 舊金山 2009/09/15 不適用
Sonya Frost 軟體工程師 愛丁堡 2008/12/13 $103,600

在這種情況下,我們有兩列可以使用絕對排序

  • 名稱 列,其中有 「未知」 的項目應該顯示在表格的頂部。
  • 薪資 列,其中 「待定」 應顯示在頂部,而 「不適用」 應顯示在底部。

如果您通過點擊這些列的標頭來改變應用於表格的排序,您將能夠看到這些項目保持在原位,無論列的排序如何。如果排序應用於其他列之一,則行將正常排序。

為什麼

好吧,它有效,但為什麼要這樣做呢?主要是為了突出顯示某些數據 - 例如需要填寫的遺失數據(使用 Editor - 這只是這篇文章中的唯一銷售宣傳!)。將一些數據降級到默默無聞也可能很有用,例如上面顯示的 「不適用」 薪資。

如何使用

首先要做的是在您的頁面上包含外掛程式的來源。它可以在 DataTables CDN 上找到

JS

為了允許每個不同的數據集有所不同,需要配置外掛程式以告知它將哪些內容保留在表格的頂部和/或底部。這是通過兩個函數完成的

  • DataTable.absoluteOrder 用於排序字串
  • DataTable.absoluteOrderNumber 用於排序數字。

這兩個方法都接受一個單一參數,該參數可以是

  • string - 應該保留在表格頂部的一列的值。
  • object - 具有以下屬性的物件
    • value - 要保留在表格頂部或底部的值
    • position - 此數據在排序順序中應保持的位置 - topbottom
  • array - 字串或物件的陣列,與上述描述匹配,以允許在排序中特殊處理多個值。

該函數將為 DataTables 構建並附加一個排序外掛程式,該外掛程式將執行所需的排序。但是,一個重點是這個外掛程式執行自動類型檢測。相反,它將傳回一個值,該值應分配給應執行此類排序的列的 columns.type 選項。這是一個重點 - 此部落格中宣布的大多數其他外掛程式都會執行自動類型檢測,但由於其動態性質以及您可能不希望將其應用於恰好與所使用值匹配的所有列,因此沒有可靠且有效的方式來檢測符合此外掛程式的列。

範例用法

最簡單的用例是在列的頂部排序特定的數據片段

var nameType = DataTable.absoluteOrder( 'Unknown' );

$('#myTable').DataTable( {
    columnDefs: [
        { targets: 0, type: nameType }
    ]
} );

或者,如果您希望某個項目位於表格的底部

var nameType = DataTable.absoluteOrder( {
    value: 'Unknown', position: 'bottom'
} );

$('#myTable').DataTable( {
    columnDefs: [
        { targets: 0, type: nameType }
    ]
} );

範例程式碼

上面範例使用的程式碼結合了三種設定配置方式的所有選項;由於薪資列的數據位於列的頂部和底部,因此使用了一個陣列。如下所示

var nameType = DataTable.absoluteOrder( 'Unknown' );
var salaryType = DataTable.absoluteOrderNumber( [
    'TBC',
    { value: 'Not available', position: 'bottom' }
] );

$('#myTable').DataTable( {
    columnDefs: [
        {
            targets: 0,
            type: nameType
        },
        {
            targets: 5,
            type: salaryType
        }
    ]
} );

這就是全部!

它是如何運作的

為 DataTables 建立排序外掛程式非常容易;這不是這個軟體有趣的部分,但關鍵是我們要確切了解 Javascript 中排序的工作原理 - 特別是我們如何將自訂函數傳遞給 Array.prototype.sort() 來定義我們希望如何排序數據。一如既往,優秀的 MDN 有很好的描述

  • 如果 compareFunction(a, b) 小於 0,則將 a 排序到比 b 低的索引,即 a 排在前面。
  • 如果 compareFunction(a, b) 返回 0,則保持 a 和 b 相對彼此不變,但相對於所有不同的元素進行排序。
  • 如果 compareFunction(a, b) 大於 0,則將 b 排序到比 a 低的索引。

因此,我們需要做的是確定傳遞到排序函數的任何參數是否需要位於排序陣列的頂部或底部。這可以使用一個簡單的 for 迴圈來完成,但這是問題的 O(n) 解法,並且在排序比較函數中,我們希望事情盡可能快,因為它會執行很多次,尤其是對於較大的數據集。為了使其成為 O(1),我們可以準備一個物件,該物件包含要搜尋的值作為參數名稱,然後可以使用簡單的查找來檢查這些參數名稱。例如,假設我們有 var o = { "Unknown": true },要檢查值是否在物件中,我們可以簡單地使用 if ( o[value] ) {...}

因此,要建立所需的物件,我們可以使用

var alwaysTop = {};
var alwaysBottom = {};

for ( var i=0, ien=values.length ; i<ien ; i++ ) {
    var conf = values[i];

    if ( typeof conf === 'string' ) {
        alwaysTop[ conf ] = true;
    }
    else if ( conf.position === undefined || conf.position === 'top' ) {
        alwaysTop[ conf.value ] = true;
    }
    else {
        alwaysBottom[ conf.value ] = true;
    }
}

然後我們的排序函數可以簡單地包含

function ( a, b ) {
    if ( o.alwaysTop[ a ] || o.alwaysBottom[ b ] ) {
        return -1;
    }
    else if ( o.alwaysBottom[ a ] || o.alwaysTop[ b ] ) {
        return 1;
    }

    return ((a < b) ? -1 : ((a > b) ? 1 : 0));
};

外掛程式中的其他所有內容都只是封裝,以允許基於數字的排序、UMD 載入並保持程式碼大小相對較小。

改進空間

總有辦法讓事情變得更好,如果您對此外掛程式有任何建議或貢獻,請隨時發送拉取請求! 來源可在 GitHub 上找到。或者,如果您對自己的排序外掛程式有任何想法,我們將非常歡迎。

祝大家假期愉快!