またどこかのCTOになった人のブログ

またどこかのCTOになった人が書いてます。最近はCakePHP3とConoHaのネタが多いです。

fullcalendar その2 〜表示している月のデータだけ取得してイベント表示してみた〜

前回の続きになります。
前回の記事はこちらです。

tsuralabo.hatenablog.com

カレンダーデータ格納用テーブルを準備

カレンダーデータなので、きっとDBの中に格納されていることでしょう。
ということにして、テーブルはこんな感じで作ります。

CREATE TABLE `schedules` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) DEFAULT NULL COMMENT '予定タイトル',
  `description` varchar(255) DEFAULT NULL COMMENT '予定詳細',
  `calendar_color` varchar(255) DEFAULT NULL COMMENT 'カレンダー背景色',
  `calendar_text_color` varchar(255) DEFAULT NULL COMMENT 'カレンダー文字色',
  `start_date` date DEFAULT NULL COMMENT '開始日',
  `start_time` char(5) DEFAULT NULL COMMENT '開始時刻',
  `end_date` date DEFAULT NULL COMMENT '終了日',
  `end_time` char(5) DEFAULT NULL COMMENT '終了時刻',
  `is_deleted` tinyint(1) NOT NULL DEFAULT 0 COMMENT '削除フラグ(0:有効 / 1:削除済み)',
  `created` datetime DEFAULT NULL COMMENT 'データ作成日',
  `created_user_id` int(11) DEFAULT NULL COMMENT 'データ作成者',
  `modified` datetime DEFAULT NULL COMMENT 'データ更新日',
  `modified_user_id` int(11) DEFAULT NULL COMMENT 'データ更新者',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='スケジュール管理テーブル';

他に必要なデータがあればカラム追加なり、いらなければ削除なり。
ただ、fullcalendarを使うのであれば日付と時間はカラム単位で分けておいた方が無難です。

カレンダーデータを取得

Ajax実行用のコントローラを作成して、その中にfunctionを作りました。
あとはそのfunctionに対してパラメータを渡してデータ取得です。
取得したデータは配列に格納し直して、JSON形式で返します。

サンプルなのでこんなfunctionにします。

<?php
  /**
   * 日付範囲に合致するスケジュール一覧を取得
   * @param type $startDate
   * @param type $endDate
   */
  public function getScheduleList($startDate, $endDate) {
    
    // スケジュールデータを取得
    $scheduleModel = TableRegistry::get('Schedules');
    $scheduleData = $scheduleModel->getScheduleList($startDate, $endDate);
    
    // 取得データを整形して配列に再セット
    $scheduleList = [];
    
    foreach ($scheduleData as $schedule) {
      $scheduleList[] = [
          'id' => $schedule->id,
          'title' => $schedule->title,
          'description' => $schedule->description,
          'start' => $schedule->start_date.(!empty($schedule->start_time) ? " ".$schedule->start_time : " 00:00"),
          'end' => $schedule->end_date.(!empty($schedule->end_time) ? " ".$schedule->end_time : " 23:59"),
          'color' => $schedule->calendar_color,
          'textColor' => $schedule->calendar_text_color
      ];
    }
    
    // 処理結果をJSON形式で返す
    $this->response->body(json_encode($scheduleList));
  }

「$scheduleList」の中でデータをセットしていますが、idとかtitleとかそういうのは全部fullcalendarがカレンダー表示時に使うプロパティです。
JSでごにょごにょやるのが得意ではないので、データを返す前に整形しています。


別件なのですが。
Ajax実行用のコントローラ内にfunctionを作って、確実にJS側から呼んでいるはずなのにエラーになる場合は多分CSRF制約に引っかかっています。
上手いやり方があるのかもしれませんが、自分は知らないのでAjax実行する時は対象のfunctionをbeforeFilterで解除するようにしています。
あまり合っている自信ないですが、こんな感じです。

<?php
class ApisController extends AppController {
  
  // CSRF解除アクション
  private $csrfAllowAction = [
    'getScheduleList'
  ];
  
  public function beforeFilter(Event $event) {
    parent::beforeFilter($event);
    
    // Ajax送信時のCSRFを無効
    if (in_array($this->request->action, $this->csrfAllowAction)) {
      $this->eventManager()->off($this->Csrf);
    }
    
    $this->viewBuilder()->layout('ajax');
    $this->autoRender = false;
  }

  /* 以下略 */

Ajaxで取得したデータをfullcalendarのコールバックに設定

ここまで書いてアレなんですが、Ajax実行する箇所を書いてなかったです。
あまり気にせず、まとめて書くことにします。

/**
 * 対象日付スケジュールデータセット処理
 * @param {type} startDate
 * @param {type} endDate
 * @param {type} callback
 * @returns {undefined}
 */
function setCalendarList(startDate, endDate, callback) {
  
  $.ajax({
    type: 'post',
    dataType : "text",
    async: true,
    cache: false,
    url : /* ここに getScheduleList を実行するURLを記載 */
  })
  .then(
    function(data) {
      // JSONパース
      var obj = jQuery.parseJSON(data);
      var events = [];
      
      $.each(obj, function(index, value) {
        events.push({
          // イベント情報をセット
          id: value['id'],
          title: value['title'],
          description: value['description'],
          start: value['start'],
          end: value['end'],
          color: value['color'],
          textColor: value['textColor']
        });  
      });
      
      // コールバック設定
      callback(events);
    }
  );

  return;
}


Ajax側で取ったデータを配列ループさせて、event.pushで登録しています。
最後にcallback設定するのを忘れないようにして下さい。
colorとtextColorはそのままなんですが、カレンダー表示時の背景色と文字色です。
別に設定する必要もないのですが、変更もできますよという例で書いてあります。
スケジュール設定画面にカラーピッカーでも埋め込んで、16進数でデータ登録すればいいと思います。

できたっぽいので呼んでみる

前回作成したはずのView側から、今回作成したJSのfunctionを呼んでAjaxでデータ取得してみます。

<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) {
           // ***** ここでカレンダーデータ取得JSを呼ぶ *****
           setCalendarList(start.format('YYYY-MM-DD'), end.format('YYYY-MM-DD'), callback);
        },
        eventClick: function(calEvent, jsEvent, view) {
           // カレンダーに設定したイベントクリック時のイベント
        },
        dayClick: function(date, jsEvent, view) {
           // カレンダー空白部分クリック時のイベント
        },
        select: function(start, end) {
           // カレンダー空白部分をドラッグして範囲指定した時のイベント
        },
        eventDrop: function(event, delta, revertFunc, jsEvent, ui, view) {
           // イベントをドラッグして別日に移動させた時のイベント
        }
    });
  });
</script>


こんな感じで呼びます。
startとendには「表示中のカレンダーに該当する月の月初日と月末日」が入っています。
ですので、例えば表示している月が2017年11月であればstartには「2017年11月01日」、endには「2017年11月30日」に相当する値が入っています。
それをフォーマットして渡しています(フォーマット形式はテーブル構成で変わるかもしれません)
callbackはそのまま渡してOKです。

eventsに書いた処理は年月等を変更した時に呼ばれます。
前月とか翌月とか。
上の例で言うとheaderに書いた辺りです。

よくわからなかったらconsole.logを仕込むとかalertを仕込んだりして動かしてみるといいかと思います。