またどこかで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件ぐらいだったと思います(表示カラム数多めです)
こんなに簡単なら最初からちゃんと調べておけばよかったなという、自分自身への戒めも含めて書いておきました。

【2018年】最近やってみたこと。

2018年の春先は暑くなったり寒くなったりと、なかなか安定しません。
暑かろうが寒かろうが、毎日スタバでアイスコーヒーしか飲まない自分には影響ないのですが。

Zabbixを導入してみた。

前職のサービスはAWSを採用していたこともあり、CloudWatchでサーバ監視をしていました。
が、現職ではConoHaを採用しているのでCloudWatchはございません。
サーバ監視しなきゃなーしないとなーとは思っていたのですが、ついつい後回しにしていたら先日弊社で運用しているWordpressサイトが人知れず落ちておりまして。
たまたま、記事担当ライターから連絡があって気が付いたのでよかったのですが、これは由々しき事態だなと。
というわけで色々調べた結果、Zabbixを導入することにしました。

本当はCPU使用率なども見たいのですが、当面クリティカルなのはサーバ稼働状況だけでいいかなという判断で、サーバ死活監視をしています。

・対象サーバに対してSSL通信を実施
・レスポンス返ってこなかったら死んでる判定
・チャットワークにメンションつけてアラートメッセージ発報

こんな感じです。
Zabbixを使ったのは初めてだったのですが、一旦環境構築してしまえばZabbix管理画面上で監視/監視解除がサクサクできてとてもいいです。
前述したような死活監視であれば十分です。

ConoHa側にZabbixのイメージがあったので最初はそこから構築したのですが、Zabbix自体のバージョンが古かったりPHPのバージョンが5.4だったりと色々アレだったので、結局CentOS7にZabbix3.4の環境を構築し直しました。
インフラ嫌い・苦手な自分としては1年前では考えられなかったような行動です。

構築等の流れは覚書も兼ねてそのうち書いてみようと思います。
きっと自分みたいなポンコツエンジニアができたので、誰でもできると思います。

Ruby on Railsの開発環境を作ってみた。

突然、Ruby on Railsをやってみようと思い開発環境を作ってみました。
作りたいものがあるとか必要に駆られてとかではなく、本当に何となくです。
CakePHP3も割と怪しいのに突然他の言語に手を出すとか、迷走している受験生のようです。
が、あまり気にしません。
何かネタができたら書いてみようと思います。
今のところ、ネタができる気配は感じられません。

Webサービスを作り始めてみた。

前職の時から「いつかやってみたいな」と思っていたWebサービス案があったのですが、構想が想定より大きくなってきてしまって凍結してしまいました。
詰め込みすぎて失敗するビジョンが見えてしまいました。

ビジネス的側面とかマネタイズ、マーケティングなどをあまり考えておらず、見切り発進で作り始めたのが原因かなと思っていますが、誰も巻き込んでいないので特に問題はないだろうと勝手に解釈しています。
とは言え、頓挫ではなく凍結なのでいつかは解凍します。
もう少しスモールサイズになるように設計し直した方がよさそうです。

で。
それとは別に中学時代の同級生と話をしていた時に、別のWebサービス案をふと思いつき余暇時間でちょこちょこ開発しています。
これはCakePHP3で作っているのですが、いっそのことRuby on Railsで作ったらいいんじゃないの? などと考えています。


と、楽しくやっています。
今年は色々と動きがある1年になりそうです。

CakePHP3.5にしてみたら何か変わってた。

しばらく投稿もしていませんでしたが、割と元気です。
孤独エンジニアでもなくなったので楽しくやっています。
最近はPythonやってみたいなーと思いつつ、Cake3を叩いています。

現職でまた新しいプロダクトを作ることになり、composerからサクっとプロダクトを作ってみたら何か色々変わっておりました。
CakePHP3.3から3.4にした時、「$this->request->data」が使えなくなり「$this->request->getData()」を使いやがれと言われて痛い目を見たことを思い出しました。

SALTの設定が変わってた

変わってたのですが、別に今まで通りの使い方で問題ないです。
CakePHP3.5から「.env.default」というものがconfigディレクトリ配下に増えていて、ここをごにょごにょすると環境変数セットできるようです。
が、別に使わなくていいかな!と勝手に判断してしまったので使ってません。

CSRFの設定が変わってた

重要なのはむしろこっちです。
今まではCSRFコンポーネントだったので、AppController.php辺りで読み込んでいましたがCake3.5からミドルウェアになりました。
名前も「CsrfProtectionMiddleware」というベタなものになりました。

で、これをApplication.phpに書いてしまえばOKです。
でも、きっと他の場所にも書けます。

Application.phpを開くと、middlewareのfunctionがありますのでここに書きましょう。
以下のような感じになると思います。

/**
   * Setup the middleware queue your application will use.
   *
   * @param \Cake\Http\MiddlewareQueue $middlewareQueue The middleware queue to setup.
   * @return \Cake\Http\MiddlewareQueue The updated middleware queue.
   */
  public function middleware($middlewareQueue) {
    $middlewareQueue
      // Catch any exceptions in the lower layers,
      // and make an error page/response
      ->add(ErrorHandlerMiddleware::class)

      // Handle plugin/theme assets like CakePHP normally does.
      ->add(AssetMiddleware::class)

      // CSRF設定をここに追加してみる
      ->add(new CsrfProtectionMiddleware())
      
      // Add routing middleware.
      ->add(new RoutingMiddleware($this));

    return $middlewareQueue;
  }


と書けば今まで通りCSRFが適用されています。
適当にフォームヘルパー書いてHTMLソースを見てみるとわかると思います。

fullcalendarを使ってみた記事をまとめてみた。

あっさり書くつもりだったfullcalendarの記事が、うっかり5記事になってしまったのでまとめてみました。
でも、Cake3だったらfullcalendarのプラグインがあったしますのでそれを使ってもいいかもしれません。

github.com

自分は使ったことがないのでよくわかりません。ごめんなさい。


以下はfullcalendarの記事です。
全部ではないかもしれませんが、書いている内容はCake3に依存してはいないと思いますのでfullcalendarを使う機会がありましたら参考にして下さい。

tsuralabo.hatenablog.com
tsuralabo.hatenablog.com
tsuralabo.hatenablog.com
tsuralabo.hatenablog.com
tsuralabo.hatenablog.com

fullcalendar その5 〜イベントをドラッグしてイベントの日付を変更してみた〜

まさかの5回目です。
過去の記事は以下をご参照下さい。

tsuralabo.hatenablog.com
tsuralabo.hatenablog.com
tsuralabo.hatenablog.com
tsuralabo.hatenablog.com


・fullcalendarの外枠を作ってプロパティ設定できた
・表示している月だけのカレンダーデータを取得できた
・設定済みのイベントをクリックしてモーダル表示、編集とかも何となくできた
・カレンダーをクリックしたりドラッグしたりしてイベント追加できた

多分できています。
仕上げは設定済みのイベントをドラッグして別の日に移動させてデータ更新です。

fullcalendarのプロパティを確認

「editable」をtrueに設定する必要があります。
これをやっておかないと一生動きません。
多分設定済みだとは思うのですが、一応確認しておいて下さい。

イベントドラッグイベントを登録

使用するのは「eventDrop」というイベントです。
ドラッグ完了時みたいな感じのイメージです。
サラっと書いてみます。

eventDrop: function(event, delta, revertFunc, jsEvent, ui, view) {
    // ***** ここにドラッグ完了時の処理を書く *****
}

引数の意味合いはグーグル先生にお聞き下さい。
自分はeventしか今回使わなかったのでよくわかりません。

既にあるイベントデータを更新するわけですから、以下のデータがあれば実現できそうです。

・更新対象のイベントのID(今回の例ですと、schedules.idになります)
・日付移動させた先の開始日
・日付移動させた先の終了日

試行錯誤した結果、こんな感じにしました。

eventDrop: function(event, delta, revertFunc, jsEvent, ui, view) {
    // ドラッグ後の日付にデータ更新する
    moveSchedule(event.id, event.start.format('YYYY-MM-DD'), event.end.format('YYYY-MM-DD'));
}

eventの中から必要なデータを取得して、引数としてJSに渡します。
渡した先のJS内でAjax使ってsaveすれば変更完了です。

save部分は普通のCake3の処理と変わりません。
これでなんとなくfullcalendarを使ったスケジュール表ができるんじゃないでしょうか。

こんなに記事が長くなると思ってませんでしたが、以上がCakePHP3 + Bootstrap3 + fullcalendarで作るスケジュール表っぽいもののサンプルになります。

問題なくできていますように。

fullcalendar その4 〜カレンダーをクリックしたりドラッグしたりしてイベント追加してみた〜

4回目になってしまいました。
過去の3回分はこちらになります。

tsuralabo.hatenablog.com
tsuralabo.hatenablog.com
tsuralabo.hatenablog.com

クリックとドラッグの違い

なんて書くとバカっぽいのですが、fullcalendarはクリックかドラッグかをちゃんと判定してくれます。
1回目の記事でちょっと書きましたが、「selectMinDistance」というプロパティがその判定基準です。
ここに設定した値よりクリック時間が長い場合、ドラッグと判定してる「ぽい」です。
selectMinDistanceのデフォルト値は「0」になっているため、1以上の値にしないとクリックとドラッグの判定はできません。
逆に言えば、ここさえ設定しておけば大したことはありません。

とりあえずクリックイベントを追加してみる

クリックはdayClickというイベントを使用します。
回数を追うごとに雑になってきましたが、こんな感じです。

dayClick: function(date, jsEvent, view) {
    // ***** ここに処理を書く *****
},

カレンダーに対してイベントを追加するので、CalendarControllerのaddアクションでも作って飛ばせばいいんじゃないかと。
ということにした場合は、こんな風になります。

dayClick: function(date, jsEvent, view) {
    // ***** 対象日にイベント追加 *****
    location.href = "<?php echo $this->Url->build('/'); ?>calendars/add/" + date.format('YYYY-MM-DD') + "/";
},

addアクションに対象日付を渡しています。
あとはその渡した日に対してデータ登録すればOKじゃないかと思われます。

ドラッグイベントも追加してみる

ドラッグの時はselectイベントを使います。
多分合ってると思うんですけど。

select: function(start, end) {
    // ***** 対象日の範囲にイベント追加 *****
    location.href = "<?php echo $this->Url->build('/'); ?>calendars/add/" + start.format('YYYY-MM-DD') + "/" + end.format('YYYY-MM-DD') + "/" + start.format('HH:mm') + "/" + end.format('HH:mm') + "/";
},


ダサい引数ですね。でも、気にしないことにします。
範囲指定をするとstartとendにドラッグした範囲の年月日開始と終了が入ってきます。
あとは用途に応じてフォーマットをかけて渡してデータ登録処理をすればOKです。
例えば、2017-11-02から2017-11-05までドラッグした場合は、startに「2017-11-02」、endに「2017-11-05」が入ってくるわけです。
上で時間もフォーマットかけて渡しているのは、fullcalendarというものは日付だけではなく時間も設定するとカレンダー上のイベントの左側に時間が表示されるのです。
そこをできればnullにしたくなかっただけなのです。
好みみたいなものですので、その辺は柔らかく対応すればいいかなと思います。

fullcalendarはstartとendに合致するデータを範囲表示するように、どこかの偉い人が作ってくれておりますのでテーブルのstart、endに相当するカラムにセットすれば恐らくそのまま帯表示されるはずです。

と思うんですが、いかがでしょうか。

fullcalendar その3 〜設定済みのイベントをクリックしてモーダル表示してみた〜

想定より長くなりましたが3回目です。
過去の2回はこちらをご覧下さい。

tsuralabo.hatenablog.com
tsuralabo.hatenablog.com

モーダル表示部分を追加してみる

前々回でBootstrapを使った外枠を作りましたので、そこにモーダル表示部分を追加してみます。
端折ってコピペしたものが以下のコードです。

<div id="contents" class="container-fluid">
  <div class="row">
    <div class="col-xs-12">
      <div class="panel">
        <div class="panel-heading">
          スケジュール表
        </div>
        <div class="panel-body">
          <div class="row">
            <div class="col-lg-12 col-md-12 col-sm-12 col-xs-12"
              <div id="calendar"></div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>
<!-- ここからモーダル表示部分として追記 -->
<div id="calendarModal" class="modal fade">
  <div class="modal-dialog modal-dialog-center">
    <div class="modal-content">
      <div class="modal-header">
        <button type="button" class="close" data-dismiss="modal">
          <span aria-hidden="true">×</span>
          <span class="sr-only">close</span>
        </button>
        <span id="modalTitle" class="modal-title"></span>
      </div>
      <div id="modalBody" class="modal-body"></div>
      <div id="modalFooter" class="modal-footer"></div>
    </div>
  </div>
</div>
<!-- ここまでモーダル追加部分で追記しました -->
<script>
  <!-- fullcalendar発火部分 -->
</script>

特に何も凝っておらず普通のモーダルです。

fullcalendarのイベントにモーダル発火部分を書いてみる

さて、とりあえずモーダルの準備ができたので発火させる必要があります。
表示しているイベントをクリックした時に発火させることにします。
アレですね。カレンダーに表示された帯みたいなイベント。
デフォルトだと紺色っぽいアレです。

記載箇所は以下です。
これもまた端折って前回のものをコピペすることにします。

<script>
  $(document).ready(function() {
    $('#calendar').fullCalendar({
        header: {
          left: 'prev,next today',
          center: 'title',
          right: 'month,agendaWeek,agendaDay,listMonth'
        },
        timeFormat: 'HH:mm',
        timezone: 'Asia/Tokyo', 
        eventLimit: true, 
        editable: true, 
        slotEventOverlap: true, 
        selectable: true, 
        selectHelper: true, 
        selectMinDistance: 1, 
        events: function(start, end, timezone, callback) {
           // 前回実装したカレンダーデータ取得部分
           setCalendarList(start.format('YYYY-MM-DD'), end.format('YYYY-MM-DD'), callback);
        },
        eventClick: function(calEvent, jsEvent, view) {
           // ***** 今回はここにクリックイベントを追加 *****
           $('#modalTitle').html(calEvent.title); // モーダルのタイトルをセット
           $('#modalBody').html(calEvent.description); // モーダルの本文をセット
           $('#calendarModal').modal(); // モーダル着火
        },
        dayClick: function(date, jsEvent, view) {
           // カレンダー空白部分クリック時のイベント
        },
        select: function(start, end) {
           // カレンダー空白部分をドラッグして範囲指定した時のイベント
        },
        eventDrop: function(event, delta, revertFunc, jsEvent, ui, view) {
           // イベントをドラッグして別日に移動させた時のイベント
        }
    });
  });
</script>


「eventClick」という名が指す通り、イベントクリック時のイベントはここに書きます。
calEventの中に「クリックされたイベント情報」が詰まっていますので、その中からデータを抜いてモーダルにセットしています。

例えばこれ以外にも編集ボタンとか削除ボタンを実装したいという場合は、今回記載したイベントの中でHTML要素を生成してモーダルにセットすれば大丈夫です。
こんな感じになります。

eventClick: function(calEvent, jsEvent, view) {
   // 編集ボタンと削除ボタン、ついでに閉じるボタンのHTML要素を追加
   var editHtml = 
       '<a role="button" class="btn btn-danger pull-left" href="' + "<?php echo $this->Url->build('/'); ?>" + 'calendars/delete/' + calEvent.id + '/">削除</a>' + 
       '<a role="button" class="btn btn-primary" href="' + "<?php echo $this->Url->build('/'); ?>" + 'calendars/edit/' + calEvent.id + '/">編集</a>' +
       '<a role="button" class="btn btn-default" data-dismiss="modal">閉じる</a>';
   $('#modalTitle').html(calEvent.title);
   $('#modalBody').html(calEvent.description);
   $('#calendarModal').modal(); // モーダル着火
}

CalendarsControllerのdeleteアクションで削除、editアクションで編集ということにしています。
calEventから対象のスケジュールデータのIDを渡してあげれば削除も編集もできると思います(ここは割愛します)

ちょっとずつですが、fullcalendarがカタチになってきているような気がします。
気のせいである可能性はゼロじゃありませんが、まぁいいです。