自分だけのクイズ作成 - Quipha公開中

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

JavaScript
スポンサーリンク

はじめに

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

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

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

Windows
Mac
使用するバージョン
  • Windows 11 or macOS Monterey (M1)
  • Laravel Framework 8 or 9
  • FullCalendar 5.11.0

2022/04/20 WindowsとMacを使い、最新バージョンで確認しました。

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

【紹介】個人開発

私の個人開発ですが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;
    }
}

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

app.jsを修正し、非同期通信を行うためのaxiosを追加します。

resources\js\app.js
// 追加
window.axios = require("axios");
require("./calendar");

calendar.jsに開き、イベントを送信する処理を追加します。

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";

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を指定しました。

$ sail npm run dev
(または、sail npm run watch)

これで登録処理の流れができました。実際に試してみましょう。
以下の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";

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();
    }
}

その他

初学者へ

Laravelを初めて触る方へ向け、手順やアドバイスをまとめました。

外部サーバーへ公開

作成したアプリは公開して使ってもらいましょう!
Laravelアプリケーションを外部公開する方法をまとめました。

脆弱性対策

脆弱性を抱えたアプリケーションの場合、攻撃を受ける可能性があり大変危険です。
作成したアプリケーションは、脆弱性対策も意識しましょう。

GitHubと連携

GitHubと連携する方法を解説しました。
プロジェクトの管理はGitHubを活用しましょう。

さいごに

簡単なスケジュールを登録するWebアプリケーションを作成しました。
カレンダーを利用するケースはよくあると思いますが、活用してみましょう。

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

\オススメ/

コメント

  1. kkk より:

    こちらのブログ記事の方法で実装を試みましたが、下記のJSエラーが発生しますが、何が原因でしょうか。
    Fullcallenderの読み込みが正常にできていないようですが、npmでのインストールは行いました。

    app.js:5127 Uncaught ReferenceError: Calendar is not defined
        at Object../resources/js/calendar.js (app.js:5127:16)
        at __webpack_require__ (app.js:22607:42)
        at Module../resources/js/app.js (app.js:5086:1)
        at __webpack_require__ (app.js:22607:42)
        at app.js:22760:64
        at Function.__webpack_require__.O (app.js:22644:23)
        at app.js:22762:53
        at app.js:22764:12
    

    • 千草 より:

      「resources\js\calendar.js」ファイルの先頭に、以下のimport文が足りないためにエラーが発生していると思われます。

      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";
      
      var calendarEl = document.getElementById("calendar");
      

      すみません、記事の解説にあたり余計かなと思い、敢えて上記のimport文を省略していました。
      (ソースの全容は最後に記載していました)

      それが誤解を与えてしまったのだと思います。
      記事は修正しましたが、ご確認お願いいたします。

      FullCalendarのプラグインの導入については、以下の記事を参考にしてください。
      https://chigusa-web.com/blog/fullcalendar/

  2. kkk より:

    あ、すみません npm run devをよく見たら実行時にエラーが出ており、
    moduleがないといわれました。
    installしたつもりだったのですが、再度インストールしなおしたところ表示されました!

    下記の方の記事の応用編だとは理解しているのですが、
    こちらではinstallには触れられていなかったので、もしかすると見逃していたのかもしれません。
    https://chigusa-web.com/blog/fullcalendar/

    お手数おかけいたしました。

タイトルとURLをコピーしました