またどこかでCTOっぽいことやってる人のブログ

フリーランスを経て、またどこかでCTOっぽいことをやってる人が書いてます。何か色々やってます。

datatablesが遅いので速くしてみた。

2018年06月。
梅雨入りしたそうですが、寒い。今日寒い。
でも、暑いの好きじゃないんです。寒い方が好み。

では本題です。
UIセンスが皆無なので、bootstrapとかどこかの凄い人が作ったJQueryライブラリをよく使います。
その中でもよく使うのが「datatables」です。

DataTables | Table plug-in for jQuery

DBからデータ取ってView側にセットすると、簡単にデータ表示ができます。
数年前、このライブラリを見つけた時は本当に衝撃でした。

いいライブラリなのは間違いないんですが、表示遅いんです。
あ、ちょっと違いました。
表示が遅くなる使い方しか知らなかったんです。
なので、Cake3で開発する時はページネーションで作ったりしていました。

https://book.cakephp.org/3.0/ja/controllers/components/pagination.html

datatablesというものは簡単にいうと「表示するデータ全部を一気に読み込んで、View側でごにょごにょした結果を表示」するんです。
ですので、ごにょごにょ部分に時間がかかってしまうと表示速度も下がってしまうというわけです(多分)
例えばモデル叩いて取得したデータをそのままViewにセット。
View側でHTMLタグ出力とデータ表示を実行。

遅くないわけがない。

JSONでやると速くなるよ!という情報は知ってたんですが、大量データ表示する時は前述のページネーションを使っていたので別にいいかなと思っていたのです。
でも、運用担当や画面をもりもり使う方などから言わせると、datatablesの方が使いやすいそうで。
であれば速くした方がいいかなーというのが今回のお話しの発端です。

とりあえずデータを取得してみる

自分はCake3ばかり使っていますが、FuelPHPだろうがLaravelでもRubyでも大差ないと思います。
所詮はモデル叩いてデータを取ってくるだけです。
Cake3だとこんな感じでいいんじゃないでしょうか。
usersテーブルからデータを取ってくるということにします。

<?php
  public function index() {
    $this->loadModel('Users');
    $userData = $this->Users->find()->all();
  }

何の変哲もないデータ取得です。
削除フラグも何も見ないで全部取ってくるぜという雑で強気な処理です。

取得したデータをController側で配列にセットしてみる

次は取得したデータを配列にブチ込みます。
もしかしたらここの部分、不要かもしれません(後述)
が、とりあえず書いておきます。

<?php
  public function index() {
    $this->loadModel('Users');
    $userData = $this->Users->find()->all();

    // 格納用配列
    $dataSet = [];

    foreach ($userData as $user) {
      // 取得したデータをtmp用配列にセット
      $tmpUserData = [
        $user->id,
        $user->user_name,
        $user->user_birth,
        $user->display_user_name
      ];

      // 配列に追加
      array_push($dataSet, $tmpData);    
    }

取得したデータをJSON形式でViewに渡す

やりたかったのはこれだけです。
モデルでデータ取得してJSON形式で単に返すだけでもできちゃうかもしれません。
わざわざ前項のような処理を入れたのは取得データと表示データ(これも後述)を合わせたかっただけです。
で、Controller側の最終形がこんな感じになります。

<?php
  public function index() {
    $this->loadModel('Users');
    $userData = $this->Users->find()->all();

    // 格納用配列
    $dataSet = [];

    foreach ($userData as $user) {
      // 取得したデータをtmp用配列にセット
      $tmpUserData = [
        $user->id,
        $user->user_name,
        $user->user_birth,
        $user->display_user_name
      ];

      // 配列に追加
      array_push($dataSet, $tmpData);    
    }

    // JSON形式でViewにセット
    $this->set('dataSet', json_encode($dataSet));
  }

ここまでで「取得したデータをJSON形式でView側に渡す」までができました。

datatables表示部分を変更してみる

あとはView側でdatatablesを表示している部分を改修すればOKなはずです。
割と端折ったコードがこんな感じです。

<html>
  <body>
    <table id="datatable" class="table table-striped table-condensed table-bordered table-hover dataTable" cellspacing="0"></table>
    <script>
       $(document).ready(function() {
         $('#datatable').dataTable( {
           data: <?php echo $dataSet; ?>,
           columns: [
             { title: "ユーザID" },
             { title: "ユーザ名" },
             { title: "誕生日" },
             { title: "表示名" }
           ],
           /* 好きなようにdatatablesのオプション入れてください */
           "order": [[0, 'desc']],
           "stateSave": true,
           "pageLength": 50
         });
      });
    </script>
  </body>
</html>

これで出来上がりです。

ボタンとか入れたい時

例えば編集ボタンとかそういうものを入れたい時は、View側を修正すればOKです。
コントローラ側の修正は不要です。
例えば、上で書いたViewに編集ボタンを入れたい場合はこんな風になります。

<html>
  <body>
    <table id="datatable" class="table table-striped table-condensed table-bordered table-hover dataTable" cellspacing="0"></table>
    <script>
       $(document).ready(function() {
         $('#datatable').dataTable( {
           data: <?php echo $dataSet; ?>,
           columns: [
             { title: "ユーザID" },
             { title: "ユーザ名" },
             { title: "誕生日" },
             { title: "表示名" },
             // ボタン表示する場合開始
             {
               render: function ( data, type, full, meta ) {
                 var buttonId = full[0];
                 var href = "<a class='btn btn-sm' role='button' href='"+ "<?php echo $this->Url->build('/', true); ?>" + "users/edit/" + buttonId + "'>編集</a>";
                 return href;
               }
             }
             // ボタン表示する場合終了
           ],
           /* 好きなようにdatatablesのオプション入れてください */
           "order": [[0, 'desc']],
           "stateSave": true,
           "pageLength": 50
         });
      });
    </script>
  </body>
</html>

こんな風にすると編集ボタンができます。

実際どのぐらい速くなったのか

計測してませんが「うお!速くなった!」とコメントがもらえる程度には速くなりました。
問い合わせデータ表示処理だったのですが、データ数が3000件ぐらいだったと思います(表示カラム数多めです)
こんなに簡単なら最初からちゃんと調べておけばよかったなという、自分自身への戒めも含めて書いておきました。