laravel
laravel

【Laravel】FullCalendarでスケジュールのDB登録・表示【実践向け】

https://chigusa-web.com/blog/laravel-fullcalendar

目次

  1. はじめに
  2. 【紹介】個人開発
  3. FullCalendarとは
  4. VS Codeの用意
  5. イベントの登録機能の実装
    1. Laravelの登録処理の実装
      1. app.phpの修正
      2. テーブルの用意
      3. モデルの作成
      4. コントローラの作成
  6. イベントの表示
  7. ソースの全容
  8. その他
    1. 初学者へ
    2. 外部サーバーへ公開
    3. 脆弱性対策
    4. GitHubと連携
    5. GitHub Copilot
  9. さいごに

スポンサーリンク

はじめに

今回はFullCalendarを利用して、イベントを登録しカレンダーに表示する簡単なWebアプリケーションを作成してみます。
(スケジュールアプリの作成)

Laravelプロジェクトで作成し、イベントはデータベースに保存します。
そうすることにより、ブラウザを再度表示した際に、イベントを再表示することが出来ます。

Laravel Sailでの開発環境の構築方法は以下をご覧ください。

Windows

Mac

使用するバージョン

  • Windows 11 or macOS Monterey (M1)
  • Laravel Framework 8 or 9 or 10
  • FullCalendar 6.1.4

2022/10/02 WindowsとMacを使い、最新バージョンで確認しました。
Vite版の対応を追加しました。
2023/03/11 Laravel10で確認しました。

他にも私のブログで、JavaScriptについて解説している記事がありますのでご覧ください。

関連

【Laravel】Vue.js v3導入とComposition API実装【Vite】

LaravelプロジェクトにVue3を導入し、Composition APIの実装を行いました。【VSCode/Dev Containers】DockerでNode.jsの開発・デバッグVS CodeでDockerを使ったリモート開発を試してみました。Dockerを使いNode環境を用意し、VS Codeでリモートデバッグを行います。【JavaScript】Base64 エンコード・デコード(UTF-8/SJIS)【js-base64】JavaScriptを使って、Base64エンコード・デコードを行います。【Laravel】Vue.js v3導入とComposition API実装【Mix】LaravelプロジェクトにVue3を導入し、Composition APIの実装を行いました。

スポンサーリンク

【紹介】個人開発

私の個人開発ですがQuiphaというサービスを開発しました。(Laravel, Vue3など)
良かったら、会員登録して動作を試してみて下さい。

また、Laravel 9 実践入門という書籍を出版しました。
Kindle Unlimitedを契約している方であれば、読み放題で無料でご覧いただくことができます。

是非多くの方に読んでいただき、Laravelの開発に少しでもお役に立てたら幸いです。

余談ですが、Django向けに解説した記事もございますので参考にしてください。

FullCalendarとは

FullCalendarのインストール方法や、実装は以下の記事をご覧ください。
本記事は、以下の記事の続きになります。

VS Codeの用意

VS Codeのインストール方法は、以下の記事にまとめましたのでご覧ください。

設定方法は以下を参考にしてください。

イベントの登録機能の実装

Laravelの登録処理の実装

前回の記事では、カレンダーにイベントを登録することが出来ました。
ただし、ブラウザの再描画を行うと、イベントはクリアされてしまいます。

ここからは実践でも使えるように、イベントをサーバーサイドで受信し、データベースに登録する処理をLaravelプロジェクトで実装していきます。

app.phpの修正

日付を操作しますので、Laravelのタイムゾーンの設定を変更してください。

'timezone' => 'Asia/Tokyo',

デフォルトでUTCがセットされており、変更しないと9時間ずれます。

テーブルの用意

以下のコマンドでmigrationファイルを用意します。
イベントを格納するテーブルになります。

$ sail php artisan make:migration create_schedules_table

migrationファイルが作成されましたので、以下のように修正しました。(ファイル名は現在時刻)

database\migrations\2021_11_18_144113_create_schedules_table.php

<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('schedules', function (Blueprint $table) {
            $table->id();
            $table->date('start_date')->comment('開始日');
            $table->date('end_date')->comment('終了日');
            $table->string('event_name')->comment('イベント名');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists('schedules');
    }
};

マイグレーションを実行し、テーブルを作成します。

$ sail php artisan migrate

モデルの作成

先ほど作成したテーブルに対するモデルクラスを作成します。コマンドを実行します。

$ sail php artisan make:model Schedule

クラスファイルが作成されますが、特に変更の必要はありません。

コントローラの作成

以下のコマンドでコントローラを作成します。

$ sail php artisan make:controller ScheduleController

web.phpを修正し、ルーティングを追加します。

use App\Http\Controllers\ScheduleController;

// イベント登録処理
Route::post('/schedule-add', [ScheduleController::class, 'scheduleAdd'])->name('schedule-add');

イベントを登録するための処理を追加します。
リクエストのバリデーションと、DBへの登録処理を追加しました。

イベント名は32文字の文字数制限としました。

App\Http\Controllers\ScheduleController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Schedule;

class ScheduleController extends Controller
{

    /**
     * イベントを登録
     *
     * @param  Request  $request
     */
    public function scheduleAdd(Request $request)
    {
        // バリデーション
        $request->validate([
            'start_date' => 'required|integer',
            'end_date' => 'required|integer',
            'event_name' => 'required|max:32',
        ]);

        // 登録処理
        $schedule = new Schedule;
        // 日付に変換。JavaScriptのタイムスタンプはミリ秒なので秒に変換
        $schedule->start_date = date('Y-m-d', $request->input('start_date') / 1000);
        $schedule->end_date = date('Y-m-d', $request->input('end_date') / 1000);
        $schedule->event_name = $request->input('event_name');
        $schedule->save();

        return;
    }
}

今回は取り敢えず、コントローラに処理を全て実装しています。
入力チェックなども考慮しておりますので、十分実用的に使えると思いますが、お好みでフォームリクエストの作成や、ビジネスロジックの切り出しなどを行うと良いでしょう。

calendar.jsを開き、非同期通信を行うためのaxiosを追加します。

resources\js\calendar.js

import axios from 'axios';

さらにイベントを送信する処理を追加します。

resources\js\calendar.js

import { Calendar } from "@fullcalendar/core";
import interactionPlugin from "@fullcalendar/interaction";
import dayGridPlugin from "@fullcalendar/daygrid";
import timeGridPlugin from "@fullcalendar/timegrid";
import listPlugin from "@fullcalendar/list";
import axios from 'axios';

var calendarEl = document.getElementById("calendar");

let calendar = new Calendar(calendarEl, {
    plugins: [interactionPlugin, dayGridPlugin, timeGridPlugin, listPlugin],
    initialView: "dayGridMonth",
    headerToolbar: {
        left: "prev,next today",
        center: "title",
        right: "dayGridMonth,timeGridWeek,listWeek",
    },
    locale: "ja",

    // 日付をクリック、または範囲を選択したイベント
    selectable: true,
    select: function (info) {
        //alert("selected " + info.startStr + " to " + info.endStr);

        // 入力ダイアログ
        const eventName = prompt("イベントを入力してください");

        if (eventName) {
            // Laravelの登録処理の呼び出し
            axios
                .post("/schedule-add", {
                    start_date: info.start.valueOf(),
                    end_date: info.end.valueOf(),
                    event_name: eventName,
                })
                .then(() => {
                    // イベントの追加
                    calendar.addEvent({
                        title: eventName,
                        start: info.start,
                        end: info.end,
                        allDay: true,
                    });
                })
                .catch(() => {
                    // バリデーションエラーなど
                    alert("登録に失敗しました");
                });
        }
    },
});
calendar.render();

FullCalendarのプラグインの導入については、前回の記事を参考にしてください。
またソースの全容は、最後に掲載しています。

jsファイルをビルドします。開発時ですのでdevを指定しました。
(ViteでもMixでも同様)

$ sail npm run dev

Viteの場合で、本番用にJS/CSSを出力する場合は、以下のコマンドを実行します。

$ sail npm run build

Viteについては以下の記事にもまとめました。

これで登録処理の流れができました。実際に試してみましょう。
以下のURLにアクセスします。

http://localhost/calendar

プロンプトに任意のイベント名を入力します。

データベースに正しく登録されました。

エラー処理も確認してみましょう。
イベント名は32文字の文字数制限をかけましたので、32文字以上のイベント名を登録してみます。

失敗アラートが表示され、カレンダー上にイベントは表示されませんし、当然DBにも登録されません。

エラーメッセージは簡易的にしましたが、バリデーション結果を表示するのも良いでしょう。
今回は不正な値が登録されないように、バリデーション処理の実装を行いました。

スポンサーリンク

イベントの表示

イベントの登録が完了しました。今度はカレンダーの表示時に、データベースに登録されているイベントを表示します。

web.phpを修正し、イベントを取得するためのルーティングを追加します。

use App\Http\Controllers\ScheduleController;

// イベント登録処理
Route::post('/schedule-add', [ScheduleController::class, 'scheduleAdd'])->name('schedule-add');
// イベント取得処理
Route::post('/schedule-get', [ScheduleController::class, 'scheduleGet'])->name('schedule-get');

イベントを取得するための処理を追加します。
カレンダーが表示されている期間のみ、イベントを取得するようにします。

基本的にカレンダーは1ヶ月分表示されています。
表示されている期間のみのデータを返却するようにしましょう。
そうしないと、毎回全イベントを返却することになり、効率が悪いです。

App\Http\Controllers\ScheduleController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Schedule;

class ScheduleController extends Controller
{
...省略

    /**
     * イベントを取得
     *
     * @param  Request  $request
     */
    public function scheduleGet(Request $request)
    {
        // バリデーション
        $request->validate([
            'start_date' => 'required|integer',
            'end_date' => 'required|integer'
        ]);

        // カレンダー表示期間
        $start_date = date('Y-m-d', $request->input('start_date') / 1000);
        $end_date = date('Y-m-d', $request->input('end_date') / 1000);

        // 登録処理
        return Schedule::query()
            ->select(
                // FullCalendarの形式に合わせる
                'start_date as start',
                'end_date as end',
                'event_name as title'
            )
            // FullCalendarの表示範囲のみ表示
            ->where('end_date', '>', $start_date)
            ->where('start_date', '<', $end_date)
            ->get();
    }
}

calendar.jsを開き、イベントを取得する処理を記述します。

「events」プロパティを追加します。
カレンダーが表示された時や、前の月・次の月のように表示が切り替わる度に呼ばれます。

resources\js\calendar.js

import { Calendar } from "@fullcalendar/core";
...省略

let calendar = new Calendar(calendarEl, {
    plugins: [interactionPlugin, dayGridPlugin, timeGridPlugin, listPlugin],
    initialView: "dayGridMonth",
    headerToolbar: {
        left: "prev,next today",
        center: "title",
        right: "dayGridMonth,timeGridWeek,listWeek",
    },
    locale: "ja",
...省略

    events: function (info, successCallback, failureCallback) {
        // Laravelのイベント取得処理の呼び出し
        axios
            .post("/schedule-get", {
                start_date: info.start.valueOf(),
                end_date: info.end.valueOf(),
            })
            .then((response) => {
                // 追加したイベントを削除
                calendar.removeAllEvents();
                // カレンダーに読み込み
                successCallback(response.data);
            })
            .catch(() => {
                // バリデーションエラーなど
                alert("登録に失敗しました");
            });
    },

});
calendar.render();

これでプログラムは完成です。

イベントを登録してみましょう。DBにも格納されますので、ブラウザを閉じて再度カレンダーを表示してもイベントは表示されます。(永続化)

ソースの全容

resources\js\calendar.js

import { Calendar } from "@fullcalendar/core";
import interactionPlugin from "@fullcalendar/interaction";
import dayGridPlugin from "@fullcalendar/daygrid";
import timeGridPlugin from "@fullcalendar/timegrid";
import listPlugin from "@fullcalendar/list";
import axios from 'axios';

var calendarEl = document.getElementById("calendar");

let calendar = new Calendar(calendarEl, {
    plugins: [interactionPlugin, dayGridPlugin, timeGridPlugin, listPlugin],
    initialView: "dayGridMonth",
    headerToolbar: {
        left: "prev,next today",
        center: "title",
        right: "dayGridMonth,timeGridWeek,listWeek",
    },
    locale: "ja",

    // 日付をクリック、または範囲を選択したイベント
    selectable: true,
    select: function (info) {
        //alert("selected " + info.startStr + " to " + info.endStr);

        // 入力ダイアログ
        const eventName = prompt("イベントを入力してください");

        if (eventName) {
            // Laravelの登録処理の呼び出し
            axios
                .post("/schedule-add", {
                    start_date: info.start.valueOf(),
                    end_date: info.end.valueOf(),
                    event_name: eventName,
                })
                .then(() => {
                    // イベントの追加
                    calendar.addEvent({
                        title: eventName,
                        start: info.start,
                        end: info.end,
                        allDay: true,
                    });
                })
                .catch(() => {
                    // バリデーションエラーなど
                    alert("登録に失敗しました");
                });
        }
    },
    events: function (info, successCallback, failureCallback) {
        // Laravelのイベント取得処理の呼び出し
        axios
            .post("/schedule-get", {
                start_date: info.start.valueOf(),
                end_date: info.end.valueOf(),
            })
            .then((response) => {
                // 追加したイベントを削除
                calendar.removeAllEvents();
                // カレンダーに読み込み
                successCallback(response.data);
            })
            .catch(() => {
                // バリデーションエラーなど
                alert("登録に失敗しました");
            });
    },
});
calendar.render();

App\Http\Controllers\ScheduleController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Schedule;

class ScheduleController extends Controller
{

    /**
     * イベントを登録
     *
     * @param  Request  $request
     */
    public function scheduleAdd(Request $request)
    {
        // バリデーション
        $request->validate([
            'start_date' => 'required|integer',
            'end_date' => 'required|integer',
            'event_name' => 'required|max:32',
        ]);

        // 登録処理
        $schedule = new Schedule;
        // 日付に変換。JavaScriptのタイムスタンプはミリ秒なので秒に変換
        $schedule->start_date = date('Y-m-d', $request->input('start_date') / 1000);
        $schedule->end_date = date('Y-m-d', $request->input('end_date') / 1000);
        $schedule->event_name = $request->input('event_name');
        $schedule->save();

        return;
    }

    /**
     * イベントを取得
     *
     * @param  Request  $request
     */
    public function scheduleGet(Request $request)
    {
        // バリデーション
        $request->validate([
            'start_date' => 'required|integer',
            'end_date' => 'required|integer'
        ]);

        // カレンダー表示期間
        $start_date = date('Y-m-d', $request->input('start_date') / 1000);
        $end_date = date('Y-m-d', $request->input('end_date') / 1000);

        // 登録処理
        return Schedule::query()
            ->select(
                // FullCalendarの形式に合わせる
                'start_date as start',
                'end_date as end',
                'event_name as title'
            )
            // FullCalendarの表示範囲のみ表示
            ->where('end_date', '>', $start_date)
            ->where('start_date', '<', $end_date)
            ->get();
    }
}

その他

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です