DataTables.jsでAjaxなデータベースアプリをサクッと作成する

##この記事について

DataTablesを使ってデータベースのCRUD操作を行うWEBアプリの作成方法を紹介します。

[画面イメージ]
<img src="https://qiita-image-store.s3.amazonaws.com/0/217094/4954b66d-d643-0a05-5f6a-1f8e2f65d553.png" width=50%>

<img src="https://qiita-image-store.s3.amazonaws.com/0/217094/1f645a39-b92b-8246-d0b5-af9dd494ead5.png" width=50%>

DataTables([公式サイト](https://datatables.net/))を調べていて、「これってレコード編集ボタンを付けたらデータベースアプリが簡単にできるんじゃ？」と思って検索したところ、英語ですが解説サイトとサンプルコードが多く見つかりました。


データの入力はBootstrapのModalフォームからAjaxで行い、DataTablesのAjax機能で再読み込みを行うという簡潔な仕様です。この方法を使うと、以下のメリットがあります。

- 全機能が同一のページに収まるためコード量が少なくて済む。画面遷移も考えないでいい。
- フロントエンドはjQueryとBootstrap。サーバサイドはJSONの出力ができれば何でもOK。高度なJavaScriptフレームワークを使わないので学習コストが低い。

Ajaxを使いますが基本的な使い方しかしません。
むしろAjaxのよい教材となるので、作りながらの勉強で大丈夫です。

##DataTablesの概要

公式サイト：https://datatables.net/

DataTablesはhtmlのtableタグを装飾・多機能化するjQueryプラグインです。
機能は単なるソートや絞り込みにとどまらず、モジュール追加でCSV・Excel・PDF出力までできます。

概要と使い方については以下の記事を見てください。Qiitaには他にも多くのDataTablesの記事があります。
[DataTablesの使い方](https://qiita.com/nissuk/items/7ac59af5de427c0585c5)

手っ取り早く試したい人にはcodepenやjsfiddleにも多くのサンプルがあります。

参考：
http://jsfiddle.net/rmcmaster/bbLjzspf/22/
https://codepen.io/spenser/pen/wKdzay

DataTablesは拡張性が高く、見た目で気に入らないところは大体修正可能です。あと、大体のイベントにコールバック処理を入れることができるので、任意の場所のタップやクリック時に任意の処理を行うことができます。

また、レスポンシブデザインに対応しています。
表示幅からあぶれたカラムは２行目にまとめて表示され、アコーディオンで開閉して確認する仕様です。

## サンプルページと参考動画

とりあえず微力ながらサンプルページを作ってみました。

[GitHub Pages](https://okoppe8.github.io/datatables_sample/index.html#)

バックエンドは省略しているので更新はできません。流用する場合はAjax処理の修正が必要です。

<details><summary>コードはこちら</summary><div>

```html:index.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
        crossorigin="anonymous">
    <link href="https://cdn.datatables.net/1.10.16/css/dataTables.bootstrap4.min.css" rel="stylesheet">
    <title>DataTables で データベースアプリを作る</title>
</head>

<body>
    <div class="container">
        <div class="row">
            <a class="popup btn btn-primary" data-id="" href="#" style="margin:5px">新規登録</a>
        </div>
        <div class="row">
            <table id="dataTable" class="table-sm table-striped table-bordered" cellspacing="0" width="100%">
                <thead>
                    <tr>
                        <th></th>
                        <th></th>
                        <th></th>
                        <th></th>
                    </tr>
                </thead>
            </table>
        </div>
    </div>
    <div class="modal fade" id="myModal" tabindex="-1" role="dialog" data-keyboard="false">
        <div class="modal-dialog modal-lg">
            <div class="modal-content">
                <div class="modal-header">
                    <h5 class="modal-title" id="modal-title"></h5>
                    <button type="button" class="close" data-dismiss="modal">
                        <span aria-hidden="true">&times;</span>
                    </button>
                </div>
                <div class="modal-body">
                    <form class="form-horizontal" id="dataform" action="" method="post">
                        <input type="hidden" id="id" name="id" class="form-control" />

                        <!--【変更箇所】登録フォームの編集 入力欄には新規登録時の初期値を設定する-->
                        <div class="form-group row">
                            <label for="displayId" class="control-label col-sm-2">番号</label>
                            <div class="col-sm-10">
                                <label class="form-control-static" id="displayId"></label>
                            </div>
                        </div>
                        <div class="form-group row">
                            <label for="title" class="control-label col-sm-2">タイトル</label>
                            <div class="col-sm-10">
                                <input type="text" class="form-control" name="title" id="title" placeholder="タスクを入力します" />
                            </div>
                        </div>
                        <div class="form-group row">
                            <label for="title" class="control-label col-sm-2">備考</label>
                            <div class="col-sm-10">
                                <input type="textarea" class="form-control" name="detail" id="detail" placeholder="備考を入力します" />
                            </div>
                        </div>
                        <div class="form-group row">
                            <label for="finished" class="control-label col-sm-2 ">完了</label>
                            <div class="col-sm-10">
                                <label class="checkbox-inline">
                                    <input type="checkbox" id="finished" name="finished" value="1">済
                                </label>
                            </div>
                        </div>
                    </form>
                </div>
                <div class="modal-footer">
                    <button type="button" id="delete" class="btn btn-warning">
                        削除
                    </button>
                    <button type="button" id="save" class="btn btn-primary">
                        保存
                    </button>
                    <button class="btn btn-default" data-dismiss="modal">
                        閉じる
                    </button>
                </div>
            </div>
        </div>
    </div>

    <script src="https://code.jquery.com/jquery-3.3.1.min.js" integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
        crossorigin="anonymous"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js"></script>
    <script src="https://cdn.datatables.net/1.10.16/js/jquery.dataTables.min.js"></script>
    <script src="https://cdn.datatables.net/1.10.16/js/dataTables.bootstrap4.min.js"></script>
    <script type="text/javascript">

        var myTable;
        $(document).ready(function () {

            // Datatableの設定
            myTable = $("#dataTable").DataTable({
                columns: [{
                    data: "id",
                    class: "right",
                    width: "20",
                }, {
                    data: "title",
                    title: "タイトル",
                }, {
                    data: "finished",
                    width: "20",
                    render: function (data) {
                        return data ? '済' : '未';
                    },
                }, {
                    data: "id",
                    orderable: false,
                    render: function (data) {
                        return '<a class="popup btn btn-info" data-id=' + data + ' href="#">&#x270f;</a>'
                    }
                }],
                // データソースの設定
                // [WebAPI 取得（一覧）に対応]
                ajax: {
                    url: 'data.json'
                },
                // 初期の並び順設定
                order: [0, 'desc'],

                // ローカライズファイル設定
                language: {
                    "url": "Japanese.json"
                },
            });

            // ENTERキーでのSubmitをさせない
            $('#dataform').on('submit', function (e) {
                e.preventDefault();
            })

            // 新規登録、編集ボタン クリック時の動作
            $(document).on('click', 'a.popup', function (e) {

                var form = $('#dataform');
                form[0].reset();
                $('#myModal').modal('show');

                // 呼び出しボタンのhrefパラメータを保存
                var id = $(this).attr('data-id');
                $("#id").val(id);

                // data-id の 有無で新規登録か更新かを判定
                // 新規登録時
                if (id == "") {
                    $("#delete").addClass("invisible");
                    $("#modal-title").html("新規")
                    $('#displayId').html("新規");
                // 更新時
            } else {
                    $("#delete").removeClass("invisible");
                    $("#modal-title").html("更新")
                    $('#displayId').html(id);

                    console.log("ajaxで参照処理");
                    // [WebAPI 参照（調査委）に対応]
                    // $.ajax({
                    //     url: 'data/' + id,
                    //     type: 'get',
                    // })
                    $.ajax({
                        url: 'data.' + id + '.json',
                        type: 'GET',
                    })
                        .done(function (data) {
                            $('input[name="title"]').val(data.title);
                            $('input[name="detail"]').val(data.detail);
                            $('input[name="finished"]').prop('checked', data.finished);
                        })
                }
            })


            // 保存ボタンクリック自の動作
            $("button#save").on('click', function (e) {

                id = $("#id").val();
                formdata = $('#dataform').serialize();

                if ($("#id").val() == "") {
                    console.log("ajaxで登録処理");
                    // [WebAPI 登録に対応]
                    // $.ajax({
                    //     url: 'data/',
                    //     type: 'post',
                    //     data: formdata,
                    // })
                    $("#myModal").modal('hide');
                    myTable.ajax.reload();
                } else {
                    console.log("ajaxで更新処理");
                    // [WebAPI 更新に対応]
                    // $.ajax({
                    //     url: 'data/' + id,
                    //     type: 'update',
                    //     data: formdata,
                    // })
                    $("#myModal").modal('hide');
                    myTable.ajax.reload(null, false);
                };

            })

            // 削除ボタンクリック時の動作
            $("button#delete").on('click', function (e) {
                // AJAX処理
                id = $("#id").val();
                console.log("ajaxで削除処理");
                // [WebAPI 削除に対応]
                // $.ajax({
                //     url: 'data/' + id,
                //     type: 'delete',
                //     data: formdata,
                // })
                $("#myModal").modal('hide');
            })


        })
    </script>
</body>

</html>
```
</div></details>

このコードは以下の動画やサイトを参考に作成しました。

C# ASP.NET MVC

<blockquote class="twitter-tweet" data-lang="ja"><p lang="und" dir="ltr"><a href="https://t.co/ZT2Kmye0AS">https://t.co/ZT2Kmye0AS</a></p>&mdash; okoppe8 (@okoppe8) <a href="https://twitter.com/okoppe8/status/960732919407849472?ref_src=twsrc%5Etfw">2018年2月6日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

Youtubeにはこの方法でデータベースアプリを作る動画が多数投稿されています。
「datatables crud」のキーワードで検索してください。自分の使っている言語の動画が出てくると思います。

## フロントエンドの作成方法

フロントエンドで作る内容は以下の項目です。

- HTML
 - DataTables のテーブルと Bootstrap のModalフォーム を同一ページに配置する。
- JavaScript
 - 新規登録や編集ボタンをクリックしたタイミングでModalフォームを表示する。
 - 編集ボタンの時は対象データのAjaxで詳細を読込み表示する。
 - 保存や削除ボタンをクリック時にAjaxで更新処理を行う。
 - 更新完了後にDataTablesのテーブルをAjaxでリロードする。

これは最低限の内容です。
実際のアプリケーションでは検索やバリデーション、エラー処理を追加する必要があります。
バリデーションについては[jQuery Validation Plugin](https://jqueryvalidation.org/)などのプラグインを使うと効率的です。

jQueryのAjaxの記述は以下のエントリが参考になります。
[はじめてのAjax(jQuery) 2017年版](https://qiita.com/zakiyamaaaaa/items/bdda422db2ccbaea60d9)

[underscorejs](http://underscorejs.org/)を使うとDataTablesのデータを直接編集することができます。詳細表示のデータ通信を不要にしたりなど、色々応用が可能です。

## サーバサイド（バックエンド）の作成方法

### REST API を使える場合

Ajaxで使うエンドポイント（URL）には「REST」という一般的な設計モデルが存在します。
平たくいうと、WebAPIの機能とURL・HTTPメソッドの組み合わせのルールです。
エンドポイントの作成は、可能な限りRESTに従うことが推奨されます。

REST API インターフェース一覧

|機能|URL|HTTPメソッド|SQLとの対応|
|:--|:--|:--:|:--:|
|取得（一覧）|data/ |GET|SELECT
|作成|data/|POST|CREATE
|取得（詳細）|data/id|GET|SELECT
|更新|data/id|PUT|UPDATE
|削除|date/id|DELETE|DELETE

参考：[REST入門 基礎知識](https://qiita.com/TakahiRoyte/items/949f4e88caecb02119aa)

Ruby on RailsやCake PHPなどの著名フレームワークを使う場合、すでにREST APIを提供する仕組みがパッケージとして用意されています。これらを利用することでより効率的に開発を行うことができます。

### REST API を作れない場合

REST APIを作成するにはフレームワークがURLルーティング(「data/1/」のようなファイル実態のないURL)を使える必要があります。
では、それができないフレームワークやCGIで今回の手法が使えないのかというとそんなことはありません。

そのような環境では、GETでアクセスしてJSONを返すスクリプトと、DB処理だけ実行してページは返さない（HTTPステータスコードは返す）を作り代用できます。
JSONを返すスクリプトは自作も可能ですが、結構古めのWeb言語でもJSON作成プラグインが作られていたりします。

自分の場合Classic ASPでも運用していますが、JSONの出力には[aspJSON](https://github.com/rcdmk/aspJSON)があったので楽に作成することができました。

## まとめ

「Single Page Application」を作ろうとしたのですが、JavaScriptフレームワークのハードルが高すぎてあきらめました。「だれでも学習可能でオワコン化する可能性のない技術」で同様のことができないか模索し、今回の手法にたどり着きました。

今回はバックエンドのサンプルコードを作らなかったので、近いうちに Django REST frameworkを使った動くサンプルコードを作りたいと思います。