2017-07-10

tobijibu

DataTablesでオリジナルのソートを定義する

DataTablesを使っていて、日付が入るカラムのソートを実装していたのですが、 少し悩んだ部分あったのでまとめておきます。

DataTablesには元々ソート機能があり、 基本的にはorderableオプションを指定するだけで対応できます。

今回やろうとしていたのは、特殊なパターンかもしれません。 同じ「日付」カラムの中に、「月」までのデータと「日」まであるデータが混在する状態で、 ソートを実装して欲しいという内容でした。 2017年5月というデータと2017年5月2日というデータが同じカラムに並んでいるイメージです。 ちなみに、「月」までのデータは全て月末の日付(30日、31日等)とします。

もちろん表示内容はそのままという前提なので、 「月」までのデータに月末日を付与して、2017年5月31日というような表示にすることはできません。 これが許されればすんなりと実装できたのですが・・・。

という流れでソートを実装することにしましょう。

テーブルデータ

今回利用するデータは以下を使います。

+----+--------+-----+------------+------------+
| id | animal | cnt | date1      | date2      |
+----+--------+-----+------------+------------+
|  1 | dog    |   1 | 2017-01-01 | 2017-08-31 |
|  2 | cat    |   2 | 2017-12-08 | 2017-07-31 |
|  3 | tiger  |   3 | 2017-08-01 | 2017-09-30 |
|  4 | lion   |   4 | 2017-05-01 | 2017-08-31 |
|  5 | wolf   |   5 | 2017-07-01 | 2017-07-31 |
+----+--------+-----+------------+------------+
date1date2がありますが、条件によって使うデータを判定します。

cnt3以下の場合はdate1を表示し、 4以上の場合はdate2を表示するとしましょう。

SQLを駆使しして、最終的には以下のようなデータが生成されたとします。

+--------+-----+---------------+
| animal | cnt | date          |
+--------+-----+---------------+
| dog    |   1 | 2017年1月1日   |
| cat    |   2 | 2017年12月8日  |
| tiger  |   3 | 2017年8月1日   |
| lion   |   4 | 2017年8月      |
| wolf   |   5 | 2017年7月      |
+--------+-----+---------------+

そのままJSONで表すとすれば、以下のような形式になります。 このJSONからデータの並び替えを実装したいと思います。

const tableData = [
  { animal : 'dog',   cnt : 1, date : '2017年1月1日',  },
  { animal : 'cat',   cnt : 2, date : '2017年12月8日', },
  { animal : 'tiger', cnt : 3, date : '2017年8月1日',  },
  { animal : 'lion',  cnt : 4, date : '2017年8月',     },
  { animal : 'wolf',  cnt : 5, date : '2017年7月',     },
];

カラム定義

データのカラムを定義します。

日付カラムでのソートを実現したいので、ordable : trueを指定します。

ordableだけでは文字列としてソートされます。 文字列としてソートしてしまうと、1月→12月→7月といった順序になってしまいます。 そこでorderDataTypeを指定して、オリジナルのソートルールを生成します。

orderDataTypeに指定する値は、後述するソートルールの名称を指定します。 今回はdom-dateとしていますが、どのような名前でも構いません。

columnDefs : [
  { 'title' : '動物',   'data' : 'animal', 'targets' :  0, },
  { 'title' : '匹数',   'data' : 'cnt',    'targets' :  1, },
  { 'title' : '日付',   'data' : 'date',   'targets' :  2,
    'orderable' : true,             // --> カラムのソート可否
    'orderDataType' : 'dom-date',   // --> ソート名称
  },
],
deferRender : false,

今回のようにデータが少ない場合は設定不要ですが、deferRenderを指定しています。 この設定はデータ件数が多いテーブルでソートする場合に有効です。

DataTablesではデータ件数が多い場合、自動的にページ送りを生成します。 この機能は大量のデータを読み込むのではなく、1ページ毎に分割してテーブルを生成してくれます。 大変便利な機能なのですが、ソート機能を使う上では邪魔になってしまいます。 全データをソートするのではなく、一部のデータのみをソートすることになり、本来表示させたい順序になりません。 deferRender : falseにすることで、全データを対象にすることができます。

一方、ソート対象のデータが膨大な場合、ソートに時間が掛かってしまうことが考えられます。 データの件数にもよりますが、DB側でソートした結果を改めて取得するなど工夫をした方が良いかもしれません。

ソートルール

orderDataTypeで指定したソートルールを定義します。 $.fn.dataTable.ext.order[]に先ほどorderDataTypeに指定した名前を入れます。

このルールはDataTablesが生成した結果の値を元にソートを行います。 つまり、テーブルのtdタグに含まれている文字列からデータを抜き取り、ソートを実装することになります。

$.fn.dataTable.ext.order['dom-date'] = function (settings, col){
  return this.api().column(col, {order:'index'}).nodes().map(function (td, i) {
    if (! $(td).html()) return 0;
    let date = $(td).html().split(/年|月|日/g);    // --> 値を年、月、日で分割
    if (! date[2]) {
      // 「日」が無ければ「月」に +1する。
      // 月末日を指定したいので、「日」(date[2])には0を入れて、1日の1日前を入れる。
      date[1] = parseInt(date[1]) + 1;
      date[2] = 0;
    }
    // 日付けを生成
    let time = new Date(date[0], date[1]-1, date[2]);
    return time.getTime();  // --> 日付けのマイクロ秒を生成
  });
}

※ここではcolumn()を使っていますが、rows()等も使うことが出来ますので、かなり細かく指定することができます。

完了

これで日付けでソートすることが出来るようになりました。

今回の例では日付けでしたが、他の内容でも同じようにオリジナルソートを実装することができます。 色々と試してみてください。

今回説明で利用したソースはこちらにあります。