DataTablesをkintoneライクにする魔法のコード

## はじめに
kintoneのカスタマイズでテーブルを使いたいときに便利なのが[DataTables](https://datatables.net/)。
そのまま使用するとkintoneのデザインに溶け込まず、イケてない感じになる。
そこで今回はDataTablesをkintoneライクにする魔法のコードを作ってみた。

## CDN
参考：https://developer.cybozu.io/hc/ja/articles/202960194-Cybozu-CDN
DataTablesはCybozu CDNで公開されているので、こちらを使う。
DataTablesを使うためにはjQueryを読み込む必要があるのでこちらもCDNから読み込む。

#### JS
* https://js.cybozu.com/jquery/3.3.1/jquery.min.js
* https://js.cybozu.com/datatables/v1.10.19/js/jquery.dataTables.min.js

#### CSS
* https://js.cybozu.com/datatables/v1.10.19/css/jquery.dataTables.min.css

## 魔法のCSS
DataTablesのJS/CSSに加えて、下記のようなCSSを追加するとデザインがkintoneライクになる。

```css:kintone-datatables.css

table.dataTable {
    font-size: 14px;
    margin: 0;
}

table.dataTable.no-footer {
    border-bottom: 1px solid #e3e7e8;
}

table.dataTable thead tr th:first-child,
table.dataTable thead tr td:first-child {
    border-left: 1px solid #e3e7e8;
}

table.dataTable tbody tr th:first-child,
table.dataTable tbody tr td:first-child {
    border-left: 1px solid #e3e7e8;
}

table.dataTable tr.odd {
    background-color: #f5f5f5;
}

table.dataTable tr.even {
    background-color: #fff;
}

table.dataTable thead th,
table.dataTable thead td {
    text-align: left;
    white-space: normal;
    overflow: hidden;
    margin-right: -5px;
    border-top: 1px solid #e3e7e8;
    border-right: 1px solid #e3e7e8;
    border-bottom: 1px solid #e3e7e8;
    background-color: #fff;
    letter-spacing: 0.1em;
    font-weight: 400;
}

table.dataTable tbody th,
table.dataTable tbody td {
    white-space: normal;
    overflow: hidden;
    border-right: 1px solid #e3e7e8;
}

table.dataTable tbody tr.selected {
    background-color: #e1f2f7;
}

.dataTables_wrapper.no-footer .dataTables_scrollBody {
    border-bottom: 1px solid #e3e7e8;
}

.dataTables_filter {
    text-align: right;
}

.dataTables_filter label {
    display: block;
    margin-bottom: 8px;
    font-family: "メイリオ", "Hiragino Kaku Gothic ProN", Meiryo, sans-serif;
    font-size: 14px;
}

.dataTables_filter input {
    height: 40px;
    display: inline-block;
    box-sizing: border-box;
    margin: 0;
    padding: 0 8px;
    border: 1px solid #e3e7e8;
    background-color: #fff;
    box-shadow: 2px 2px 4px #f5f5f5 inset, -2px -2px 4px #f5f5f5 inset;
    color: #888;
    font-size: 14px;
}

.dataTables_filter input:focus {
    background-color: #e2f2fe;
    box-shadow: none;
    color: #000;
}

```

## 日本語化オプション
使用する際に以下のオプションを加えることで日本語化できる。
（参考：[コピペで動くjQuery DataTablesの導入と日本語対応](https://qiita.com/tkosuga@github/items/d39bcc51573c00e2f3b2)）

```js:sample.js
$('#example').DataTable({
    'language': {
        'url': '//cdn.datatables.net/plug-ins/3cfcc339e89/i18n/Japanese.json'
    },
    // その他オプション
});
```

## 結果
Before（CSS未適用）
![before.PNG](https://qiita-image-store.s3.amazonaws.com/0/272803/e32b1eda-b132-997b-b640-03ce12a9aa42.png)
↓
After（CSS適用後）
![after.PNG](https://qiita-image-store.s3.amazonaws.com/0/272803/787f9517-c1d9-481d-8cd2-97f4600de0a8.png)
**※注意：DataTablesで作った一覧です（標準の一覧ではありません）**

## 応用編
一覧画面にチェックボックス付きのDataTablesを配置し、選択したレコードの添付ファイルを一括でダウンロードするサンプル
![view.PNG](https://qiita-image-store.s3.amazonaws.com/0/272803/f4ddca5f-cf93-d2d5-ad84-0df832562709.png)


### 使用するライブラリ
* jQuery
    * JS:https://js.cybozu.com/jquery/3.3.1/jquery.min.js
* DataTables
    * JS:https://js.cybozu.com/datatables/v1.10.19/js/jquery.dataTables.min.js
    * CSS:https://js.cybozu.com/datatables/v1.10.19/css/jquery.dataTables.min.css
* JSZip
    * JS:https://js.cybozu.com/jszip/v3.1.5/jszip.min.js
* FileSaver
    * [GitHub](https://github.com/eligrey/FileSaver.js/releases/tag/1.3.2)からダウンロード
    * FileSaver.min.js
* kintoneUtility
    * [GitHub](https://github.com/kintone/kintoneUtility/releases/tag/v0.4.1)からダウンロード
    * kintoneUtility.min.js
* kintone/plugin-examples
    * [GitHub](https://github.com/kintone/plugin-examples/blob/master/stylesheet/51-modern-default.css)からダウンロード
    * 51-modern-default.css

### カスタマイズビュー

参考：[カスタマイズビューを利用してみよう](https://developer.cybozu.io/hc/ja/articles/202905604)
アプリの設定からカスタマイズビューのHTMLを設定

```html:カスタマイズビュー
<table id="view"></table>
```
![customize_view.PNG](https://qiita-image-store.s3.amazonaws.com/0/272803/b5e5bde2-63f2-6650-f67e-1ac829c16e1d.png)

一覧IDをメモしておく。

### Javascript

```js:main.js
// チェックボックスのHTML
var checkboxHtml = '<div class="kintoneplugin-input-checkbox">' +
    '<span class="kintoneplugin-input-checkbox-item">' +
    '<input type="checkbox">' +
    '<label></label>' +
    '</span>' +
    '</div>';

// ボタンのHTML
var buttonHtml = '<button id="download" class="kintoneplugin-button-normal">ダウンロード</button>';

// 一覧の表示イベント
kintone.events.on(['app.record.index.show'], function (event) {

    // XXXXは一覧ID
    if (event.viewId !== XXXX) {
        return event;
    }

    // DataTablesの設定
    var table = $('#view').DataTable({
        // 日本語化
        language: {
            url: '//cdn.datatables.net/plug-ins/3cfcc339e89/i18n/Japanese.json'
        },
        data: event.records,
        columnDefs: [
            {
                targets: 0,
                title: '',
                defaultContent: checkboxHtml,
                className: 'control-cell',
                orderable: false
            },
            {
                targets: 1,
                title: '顧客ID',
                data: 'company_id.value',
                defaultContent: '',
                orderable: false
            },
            {
                targets: 2,
                title: '会社ID',
                data: 'company_name.value',
                defaultContent: '',
                orderable: false
            },
            {
                targets: 3,
                title: '業界',
                data: 'industry.value',
                defaultContent: '',
                orderable: false
            },
            {
                targets: 4,
                title: '氏名',
                data: 'name.value',
                defaultContent: '',
                orderable: false
            },
            {
                targets: 5,
                title: 'フリガナ',
                data: 'name_reading.value',
                defaultContent: '',
                orderable: false
            },
            {
                targets: 6,
                title: '性別',
                data: 'gender.value',
                defaultContent: '',
                orderable: false
            },
            {
                targets: 7,
                title: 'メールアドレス',
                data: 'mailaddress.value',
                defaultContent: '',
                orderable: false
            }
        ],
        paging: false,
        order: [],
        info: false,
        searching: false,
        createdRow: function (row, record, index) {

            // チェックボックス
            const $checkbox = $('.kintoneplugin-input-checkbox', row);

            // チェックボックス選択時に行にクラス名：`selected`を追加
            $checkbox.find('input')
                .attr('id', `checkbox-${index}`)
                .on('change', function (event) {

                    if ($(event.target).prop('checked')) {
                        $(row).addClass('selected');
                    } else {
                        $(row).removeClass('selected');
                    }

                });

            $checkbox.find('label')
                .attr('for', `checkbox-${index}`);

        },
        initComplete: function () {

            // 全選択用のチェックボックスを配置
            const $checkboxAll = $(checkboxHtml).appendTo(table.column(0).header());

            $checkboxAll.find('input')
                .attr('id', 'checkbox-all')
                .on('change', (event) => {

                    var checked = $(event.target).prop('checked');

                    for (var i = 0; i < table.data().count(); i++) {

                        var row = table.row(i);
                        var record = row.data();
                        var node = row.node();

                        $('.kintoneplugin-input-checkbox input', node)
                            .prop('checked', checked)
                            .trigger('change');

                    }

                });

            $checkboxAll.find('label')
                .attr('for', 'checkbox-all');

            table.on('search', function () {
                $checkboxAll.find('input').prop('checked', false);
            });

        }
    });

    $('#download').remove();

    // ボタン配置
    $(buttonHtml).on('click', function (event) {

        // クリックイベントで選択されたレコードを取得
        var selected = [];

        for (var i = 0; i < table.data().count(); i++) {

            var row = table.row(i);
            var record = row.data();
            var node = row.node();

            // 選択されたレコードを配列に追加
            if ($(node).hasClass('selected')) {
                selected.push(record);
            }

        }

        // 0件の場合、処理を終了
        if (selected.length === 0) {
            return;
        }

        // zipファイル作成
        var zip = new JSZip();

        Promise.all(selected.map(function (record) {

            // レコード番号でフォルダ作成
            var folder = zip.folder(record['$id'].value);

            return Promise.all(record['file'].value.map(function ({ name, fileKey }) {

                // ダウンロード
                return kintoneUtility.rest.downloadFile({ fileKey }).then(function (blob) {
                    // フォルダにファイルを追加
                    folder.file(name, blob);
                });

            }));

        })).then(function () {

            // blobを作成
            zip.generateAsync({ type: 'blob' }).then(function (content) {

                // 保存処理
                saveAs(content, 'files.zip');

            });

        });

    }).appendTo(kintone.app.getHeaderMenuSpaceElement());

});

```
### CSS

```css:main.css
table.dataTable tbody th.control-cell,
table.dataTable tbody td.control-cell,
table.dataTable thead th.control-cell,
table.dataTable thead td.control-cell {
    padding: 0;
    text-align: center;
}

.control-cell .kintoneplugin-input-checkbox {
    display: inline-block;
    margin: 0;
}

.control-cell .kintoneplugin-input-checkbox-item {
    margin: 0;
}

.gaia-app-indexview-customview-html table.dataTable thead tr:first-child th,
.gaia-app-indexview-customview-html table.dataTable thead tr:first-child td {
    border-top: 0;
}

```

### JS/CSS設定
#### JS
* https://js.cybozu.com/jquery/3.3.1/jquery.min.js
* https://js.cybozu.com/datatables/v1.10.19/js/jquery.dataTables.min.js
* https://js.cybozu.com/jszip/v3.1.5/jszip.min.js
* FileSaver.min.js
* kintoneUtility.min.js
* **main.js**

#### CSS
* https://js.cybozu.com/datatables/v1.10.19/css/jquery.dataTables.min.css
* 51-modern-default.css
* **kintone-datatables.css**
* **main.css**

## 最後に
DataTablesはテーブルで何かを見せたいときに非常に便利です。
kintoneライクになることで、より活用の幅が広がるのではないでしょうか。