datatablesをいじってみる(チェックボックスをつける、行や列の削除、合計値の表示、ファイル出力、PDF日本語化）

ブラウザ上での表の操作にはdatatablesが非常に使いやすいので、簡単に使い方を説明してみる。
完成版はこちらから。
https://github.com/legacyworld/datatables-test

# とりあえず作る
データとしてモバイルSuicaの利用履歴を使う。

```html:index.html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <link  href="https://cdn.datatables.net/1.10.21/css/jquery.dataTables.min.css" rel="stylesheet" >
    <link  href="https://cdn.datatables.net/select/1.3.1/css/select.dataTables.min.css" rel="stylesheet" >
    <script src="https://code.jquery.com/jquery-3.5.1.js"></script>
    <script src="https://cdn.datatables.net/1.10.21/js/jquery.dataTables.min.js"></script>
    <script src="/static/js/test.js"></script>
  </head>
  <body>
    <table id="ms_usage_table" class="table" style="width:100%">
      <thead>
        <tr id="table_header">
          <th>月</th>
          <th>日</th>
          <th>種別</th>
          <th>利用駅</th>
          <th>種別</th>
          <th>利用駅</th>
          <th>残額</th>
          <th>差額</th>
        </tr>
      </thead>
      <tbody id="ms_usage_list">
      </tbody>
    </table>
    <script>
      initializeDatatables()
    </script>
  </body>
</html>
```
```javascript:test.js
let initializeDatatables = function(){
    mobilesuica_data = [
        [02,06,"繰",'','','',"9253",''],
        [02,07,"VIEW","モバイル",'','',"9753",500],
        [02,07,"入","東京","出","品川","9573",-180],
        [02,07,"入","品川","出","東京","9393",-180],
        [02,08,"物販",'','','',"9453",-440]
    ]
    table = $('#ms_usage_table').DataTable({
        data: mobilesuica_data
    })
}
```
これをブラウザで読み込むとこのように表示される。
<img width="566" alt="35.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629456/c14f8a43-ba75-2dae-ca00-e0a0b88b3d61.png">


- ヘッダ部分はテーブルを作る前に書いておく
- .DataTableと.datatableがあるが、.DataTableの方が推奨
- **data: mobilesuica_data**で使用するデータを指定している

datatablesが何をしてくれているかというと、自動でこんな風に表を作ってくれているわけである。
<img width="670" alt="36.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629456/ef6c0902-bd30-2774-4bb7-c307cc314e0f.png">

# チェックボックスをつける
一番最初にするのはこれだと思う。
datatablesにも用意されているが、全選択のチェックボックスを用意できない。
というわけで、自作する。まずはindex.htmlに以下のように3行追加する。

```html:index.html
<!-- 省略 -->
      <thead>
        <tr id="table_header">
          <th><input type="checkbox" id="all_checks" ></th> <!-- これを追加 -->
          <th>月</th>
<!-- 省略 -->
    <script>
      initializeDatatables()
      $('#all_checks').click(all_select) // これを追加
      $('#ms_usage_list').click(ind_select) // これを追加
    </script>
<!-- 省略 -->
```
`<script>`に追加した部分は後ほど説明する。
**checkbox**を追加しただけでは、データの列の数と合わなくなってしまう（データにはcheckboxにあたる部分にデータが無い）
なのでやり方はダサいがダミーデータを追加する。
更にこのままではダミーデータが表示されてしまうので、表の一番左はチェックボックスになるように**columnDefs**という仕組みを使う。

```javascript:test.js
let initializeDatatables = function(){
    mobilesuica_data = [
        [0,02,06,"繰",'','','',"9253",''],
        [1,02,07,"VIEW","モバイル",'','',"9753",500],
        [2,02,07,"入","東京","出","品川","9573",-180],
        [3,02,07,"入","品川","出","東京","9393",-180],
        [4,02,08,"物販",'','','',"9453",-440]
    ]
    table = $('#ms_usage_table').DataTable({
        data: mobilesuica_data,
        'columnDefs': [
            {
                'targets': 0,
                'width': '10px',
                'searchable': false,
                'orderable': false,
                'render': function (data){
                    return '<input type="checkbox" name="o_chk" value="' + data + '">'
                }
            }
        ],
         'order': [[1, 'asc'],[2, 'asc']]
    })
}
```
columnDefsは非常に柔軟性が高いのでなんでも出来てしまう。

- targets
    - 対象の列番号を記載する。0開始
- width
    - チェックボックスなので固定幅にしている
- searchable
    - 検索の対象ではないのでfalse
- orderable
    - 並べ替えもしないのでflase
- render
    - ここの**return**をhtmlとして記述してくれる。引数を取ると元データ(mobilesuica_data)の値も使える。ここではvalueに入れているがあまり意味は無い

searchableやodereable等は指定しなければデフォルトではtrueになっている。
 
**order**は表を表示する際に予めどの列でソートしておくか、というパラメータ。
この例だとまず2列目（月）でソートして、その後3列目（日）でソートする。
このソートを入れておかないと、ordereable=falseとしても何故かチェックボックスにソートマークがついてしまう。
こんな感じで。
<img width="160" alt="14.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629456/f674f87b-d942-1c1e-d13f-4283cf065349.png">

さてこれでチェックボックスはついたが、全選択/全解除が全く効かない。
なので以下の2つの関数を追加する。

```javascript:test.js
let all_select = function(){
    // table.rows().every()の後でthisが変わるため全選択のチェックボックスの状態を保持
    var that = this
    // 見えている全ての行に対しての操作
    table.rows(':visible').every(function() {
        // 各行の1列目(eq(0))=チェックボックスを全選択のチェックボックスと同じ状態にする
        $(this.node()).find('td').eq(0).find('input').prop('checked',that.checked)
        if(that.checked == true){
            // 全選択のチェックボックスがついた状態
            $(this.nodes()).addClass('selected')
        }else{
            // 全選択のチェックボックスがついてない状態（＝全解除）
            $(this.nodes()).removeClass('selected')
        }
    })
}
  
let ind_select = function(){
    var that = this
    var count = 0
    table.rows(':visible').every(function() {
        if($(this.node()).find('td').eq(0).find('input').prop('checked')){
            // チェックがついている行数をカウントする
            count += 1
            $(this.node()).addClass('selected')
        }else{
            $(this.node()).removeClass('selected')
        }
    })

    if(count == table.rows(':visible').count()){
        // チェックの行数 == 全行数なら全選択のチェックボックスにチェックをつける
        $('#all_checks').prop('checked', true)
    }else{
        // チェックの行数 != 全行数なら全選択のチェックボックスにチェックを外す
        $('#all_checks').prop('checked', false)
    }
}
```
## all_select関数
### var that = this
これは`table.rows`のループの後では`this`の値が変わってしまうためコピーしている。
JQuery初心者にとってはこの辺がよくわからなかった。
一行目の**this**はconsole.log(this)で出力するとこう見える。
<img width="195" alt="15.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629456/3de40541-4c23-adf7-657c-d6c3293f5d00.png">
で、`table.rows`の後にconsole.log(this)するとこう見えます（5回ループするのでこれが５つ）。
<img width="344" alt="16.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629456/6a19a128-959b-4f1f-6150-1545c262d996.png">
これはDataTablesの形式で出力されているようだ。
なので、ここでの**this**の対象はこの表の一つの行だけである。

### rows().every()
`table.rows(':visible').every(function() {`はdatatablesで最もよく使うAPIである。
[※本家の説明はこちら](https://datatables.net/reference/api/rows().every())
1行ずつ操作するときに使う。`rows()`の()内ででいろいろ条件を設定すると絞り込める。
[※rowの選択に使える表記はこちら](https://datatables.net/reference/type/row-selector)
この例だと今見えている分だけ、という風にしてる。例えば検索とかでフィルタかけてる場合があるので、これがないとみえていないところまでチェックボックスがつく。

#### ``table.rows().every(function()`とした場合（＝見えていない行も操作）
Searchに**東京**と入れた状態
<img width="605" alt="19.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629456/7cca255d-3d15-1465-6c84-7fc2591b1680.png">
この状態で全選択をチェック
<img width="609" alt="20.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629456/75d4e4f3-295b-d6c0-ebac-f74e8e7e2b34.png">
更にこの状態でSearchの**東京**を消す
<img width="606" alt="21.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629456/0fb4d56a-80c6-caa7-3970-5c18fad30506.png">

という残念なことに。

#### ``table.rows(':visible').every(function()`とした場合（＝見えている行のみ操作）
2つ目までは同じですが、最後に**東京**をSearchから消した後にこうなる。
<img width="605" alt="22.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629456/fab90263-efbb-f1dc-cd30-55e8ea7d79b9.png">


きちんとなっている。全選択のチェックが残ったままなのがダメなところだが。

### `$(this.node()).find('td').eq(0).find('input').prop('checked',that.checked)`
最初`this.node()`と`$(this.node())`の違いが判らずかなり悩んだ。

`this.node()`と`$(this.node())`の違いは前者はdatatablesの形式、後者はJQueryで操作ができる。
node()はその行の`<tr>`を指す。行に対して何かをしたい場合は大体これで済む。
なので`$(this.node()).find('td').eq(0).find('input').prop('checked',that.checked)`の意味するところは、**「各行の`<tr>`にある最初(=`eq(0)`)の`<td>`のinputタグのチェックボックスの状態を全選択のチェックボックスと同じ状態(=that.checked)に設定する」**である。
JQuery初心者の筆者にはこれはハードルが高かった。
$を抜いた状態(`this.node().find('td').eq(0).find('input').prop('checked',that.checked)`)とすると全選択のチェックボックスを選んだときにコンソールに`TypeError: this.node(...).find is not a function`というエラーが出る。
<img width="307" alt="23.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629456/3f89f16e-132e-f6af-4201-c2f18521be29.png">

### if(that.checked == true)以降
各行のnode(=this.node())に'selected'をクラスとして追加すると、自動的にその行の色を変えてくれる。

## ind_select関数
これは各行のチェックボックスが押されるごとに、今表示されているすべての行について( `table.rows(':visible').every`)チェックボックスがついているかどうか調べて( `if($(this.node()).find('td').eq(0).find('input').prop('checked')`)、表示されている行数=チェックの数なら(`if(count == table.rows(':visible').count())`)全選択のチェックボックスをつける。

# チェックした行を消す
これは簡単。まず消すためのボタンと関数を作る。

```html:index.html
  <body>
    <button id="delete_row">選択した行を消す</button> <!-- <== 追加 -->
    <table id="ms_usage_table" class="table" style="width:100%">
<!-- 省略 -->
    <script>
      initializeDatatables()
      $('#all_checks').click(all_select)
      $('#ms_usage_list').click(ind_select)
      $('#delete_row').click(delete_row) // 追加
```
```javascript:test.js
let delete_row = function(){
    table.rows('.selected').remove().draw()
}
```
`table.rows('.selected')`で選択されているすべての行について`remove()`しているが、この時点では表示上は消えない。その後の`draw()`で消える。
消した後で元に戻すには以下のようにすればよい。

```javascript:test.js
table.clear().rows.add(mobilesuica_data).draw()
```
clear()で全て消して、add(mobilesuica_data)で再描画。
rows().remove()で消しても、元データは残っている。

# 合計の表示
フッター部分に**差額**の合計値を出すには以下のようにする。

```html:index.html
    <button id="calculate_sum">合計を表示</button> <!-- <== 追加 -->
<!-- 省略 -->
      </tbody>
<!-- <tfoot> 部分を追加 -->
      <tfoot>
        <tr>
          <th></th><th></th><th></th><th></th><th></th><th></th><th></th><th></th><th></th>
        </tr>
      </tfoot>
<!-- 省略 -->
      <script>
        $('#calculate_sum').click(calculate_sum) //  追加
```

```javascript:test.js
let calculate_sum = function(){
    var sum = table.column(8).data().reduce( function (a, b) {
        a = typeof a == 'number' ? a : 0
        b = typeof b == 'number' ? b : 0
        return a+b
    }, 0 )
    $(table.column(7).footer()).html('合計')
    $(table.column(8).footer()).html(sum)
}
```

まずフッターにもヘッダーと同じ数だけ列を作る。
`calculate_sum`では右端（9列目）の合計値を求めて、フッターに書き込んでいる。
`table.column(8).data()`は9列目のデータを指定、`reduce()`はjavascriptのreduceと同じ。
`a = typeof a == 'number' ? a : 0`では数値が入っていない場合は0にしている。
`$(table.column(7).footer()).html('合計')`では8列目のフッターに合計を追加している。
以下が結果。
<img width="572" alt="25.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629456/270a8d1c-8ea5-d534-a6f1-2b5442d6176d.png">

# 列を消す（隠す）、表示する
これは一番簡単。
隠す時(左側の利用駅を消してみる）

```javascript
table.column(4).visible(false)
```
<img width="578" alt="26.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629456/4d47c632-9854-98e8-d272-bd45988c3047.png">

再度表示するには`visible(true)`とするだけ。

# 出力(HTML,PDF,CSV,メモリ,印刷)
まずヘッダーファイルを変更。出力用のボタンを出すためのもの。
このページでまとまったjavascriptファイルを作ることが出来る。
https://datatables.net/download/index
datatables.min.jsが最初のものとは変わっている。この中にEXCELで出力したりするためのコードなどがまとまっている。

```html
  <head>
    <meta charset="UTF-8">
    <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/v/ju/jszip-2.5.0/dt-1.10.21/b-1.6.2/b-html5-1.6.2/b-print-1.6.2/datatables.min.css"/>
    <script src="https://code.jquery.com/jquery-3.5.1.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdfmake/0.1.36/pdfmake.min.js"></script>
    <script src="https://cdn.datatables.net/v/ju/jszip-2.5.0/dt-1.10.21/b-1.6.2/b-html5-1.6.2/b-print-1.6.2/datatables.min.js"></script>
    <script src="/static/js/vfs_fonts.js"></script>
    <script src="/static/js/test.js"></script>
  </head>
```

```javascript
let initializeDatatables = function(){
    mobilesuica_data = [
        [0,02,06,"繰",'','','',"9253",''],
        [1,02,07,"VIEW","モバイル",'','',"9753",500],
        [2,02,07,"入","東京","出","品川","9573",-180],
        [3,02,07,"入","品川","出","東京","9393",-180],
        [4,02,08,"物販",'','','',"9453",-440]
    ]
    // PDF出力の日本語対応のため
    pdfMake.fonts = {
        GenShin: {
          normal: 'GenShinGothic-Normal-Sub.ttf',
          bold: 'GenShinGothic-Normal-Sub.ttf',
          italics: 'GenShinGothic-Normal-Sub.ttf',
          bolditalics: 'GenShinGothic-Normal-Sub.ttf'
        }
    }
    table = $('#ms_usage_table').DataTable({
        data: mobilesuica_data,
        // ボタン配置
        dom: 'lBfrtip',
        buttons: [
         {
             extend: 'excelHtml5',
             text: 'EXCELファイルに出力',
             footer: true,
             filename: 'mobilesuica.xlsx',
             // チェックボックスの列は出力しない
             exportOptions: {
               columns: ':visible:not(:eq(0))' 
             }
         },
         {
             extend: 'csvHtml5',
             text: 'CSVファイルに出力',
             bom:true,
             footer: true,
             filename: 'mobilesuica.csv',
             exportOptions: {
               columns: ':visible:not(:eq(0))' 
             }
         },
         {
             extend: 'pdfHtml5',
             text: 'PDFファイルに出力',
             footer: true,
             filename: 'mobilesuica.pdf',
             customize: function ( doc ) {
               doc.defaultStyle.font= 'GenShin';
             },
             exportOptions: {
               columns: ':visible:not(:eq(0))' 
               }
         },
         {
             extend: 'copyHtml5',
             text: 'クリップボードにコピー',
             footer: true,
             filename: 'mobilesuica.html',
             exportOptions: {
               columns: ':visible:not(:eq(0))' 
             }
         },
         {
             extend: 'print',
             text: '印刷',
             footer: true,
             exportOptions: {
               columns: ':visible:not(:eq(0))' 
             },
         }
       ],
        'columnDefs': [
            {
                'targets': 0,
                'width': '10px',
                'searchable': false,
                'orderable': false,
                'render': function (data){
                    return '<input type="checkbox" name="o_chk" value="' + data + '">'
                }
            }
        ],
        // 2行目でまずソートしてから3行目で更にソート
        'order': [[1, 'asc'],[2, 'asc']]
    })
}
```

## PDFの日本語対応問題
ここで一つ問題が発生する。日本語を含むPDFへの出力が出来ないのだ。
ただ既に解決をしてくれている偉大な先人たちがいる。
[※JavascriptでPDFを作成する](https://qiita.com/shuhei_sakiyama/items/ef68d49b7199d50a853b)

pdfMakerのvfs_fonts.jsを以下のものと変更する。
https://github.com/naoa/pdfmake/tree/master/build
こちらをダウンロードしてローカルから読み込むようにする。

表の初期化の前に`pdfMake.fonts`という部分があるが、これが上記の日本語対応を行う部分である。

## ボタン配置
`dom: 'lBfrtip',`を書かないとファイル出力用のボタンが配置されない。
https://datatables.net/reference/option/dom
こちらに説明がある。
これらを変更するとボタンの場所や検索ボックスの場所が変わったりする。`lBftrip`が一番しっくりくる。

## ボタンの設定
`button [`以降で各ボタンの設定をしている。
下記でextend以外は記載しなければ何らかのデフォルト値が存在する。

- extend
    - これがそのボタンの役割
- text
    - ボタンの表示テキスト
- footer
    - フッターも含めるかどうか
- filename
    - 出力するファイル名
- exportOptions
    - チェックボックスは出力の必要が無いのでその設定
    - 参考にしたのはこちら https://stackoverflow.com/questions/36953426/exclude-column-from-export-in-datatables-buttons
    - `columns: ':visible:not(:eq(0))' `1行目(=チェックボックス）を出力しないようにしている

## 出来上がりはこちら
<img width="575" alt="27.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629456/26325724-9418-7f11-733e-cb7a97d85794.png">

出力されるPDFはこんな感じ
<img width="233" alt="28.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629456/1f388d7b-1a5f-061f-076f-982a88bf4a4c.png">

# その他細かな設定等

## 日本語化
下記を追加

```javascript
language: {
      url: "https://cdn.datatables.net/plug-ins/9dcbecd42ad/i18n/Japanese.json"
      },
```
こうなる。
<img width="570" alt="29.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629456/4cb1d0a5-732c-6453-a6d2-a54ff1817e3a.png">

Show entriesやSearchが日本語になっている。

## 表示件数のリスト
これで制御できる

```javascript
lengthMenu: [[10,25,50,100,-1],[10,25,50,100,"全"]],
```
左側の配列`[10,25,50,100,-1]`が設定。`-1`が全部を意味する。
右側の配列`[10,25,50,100,"全"]`が実際にプルダウンに表示される。
<img width="227" alt="32.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/629456/2dfe5bd1-be2d-c8e2-e0aa-b56d22b2b935.png">