17
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

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

とりあえず作る

データとしてモバイルSuicaの利用履歴を使う。

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>
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
    })
}

これをブラウザで読み込むとこのように表示される。
35.PNG

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

datatablesが何をしてくれているかというと、自動でこんな風に表を作ってくれているわけである。
36.PNG

チェックボックスをつける

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

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という仕組みを使う。

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としても何故かチェックボックスにソートマークがついてしまう。
こんな感じで。
14.PNG

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

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)で出力するとこう見える。
15.PNG
で、table.rowsの後にconsole.log(this)するとこう見えます(5回ループするのでこれが5つ)。
16.PNG
これはDataTablesの形式で出力されているようだ。
なので、ここでのthisの対象はこの表の一つの行だけである。

rows().every()

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

``table.rows().every(function()`とした場合(=見えていない行も操作)

Searchに東京と入れた状態
19.PNG
この状態で全選択をチェック
20.PNG
更にこの状態でSearchの東京を消す
21.PNG

という残念なことに。

``table.rows(':visible').every(function()`とした場合(=見えている行のみ操作)

2つ目までは同じですが、最後に東京をSearchから消した後にこうなる。
22.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というエラーが出る。
23.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()))全選択のチェックボックスをつける。

チェックした行を消す

これは簡単。まず消すためのボタンと関数を作る。

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) // 追加
test.js
let delete_row = function(){
    table.rows('.selected').remove().draw()
}

table.rows('.selected')で選択されているすべての行についてremove()しているが、この時点では表示上は消えない。その後のdraw()で消える。
消した後で元に戻すには以下のようにすればよい。

test.js
table.clear().rows.add(mobilesuica_data).draw()

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

合計の表示

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

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) //  追加
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列目のフッターに合計を追加している。
以下が結果。
25.PNG

列を消す(隠す)、表示する

これは一番簡単。
隠す時(左側の利用駅を消してみる)

table.column(4).visible(false)
26.PNG

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

出力(HTML,PDF,CSV,メモリ,印刷)

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

  <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>
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を作成する

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

出来上がりはこちら

27.PNG

出力されるPDFはこんな感じ
28.PNG

その他細かな設定等

日本語化

下記を追加

language: {
      url: "https://cdn.datatables.net/plug-ins/9dcbecd42ad/i18n/Japanese.json"
      },

こうなる。
29.PNG

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

表示件数のリスト

これで制御できる

lengthMenu: [[10,25,50,100,-1],[10,25,50,100,""]],

左側の配列[10,25,50,100,-1]が設定。-1が全部を意味する。
右側の配列[10,25,50,100,"全"]が実際にプルダウンに表示される。
32.PNG

17
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
22

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?