pluginから理解するFullCalendar with React

## 目次
- はじめに
- FullCalendarを動作する環境を整える
- FullCalendarの見た目を整える
- FullCalendarでインタラクションできる環境を整える
- 終わりに

## はじめに
カレンダーを簡単に作成できるライブラリ `fullcalendar` を扱う機会がありました。キャッチアップしていたのですが、pluginさえわかれば、ある程度のことはできるようになる、とキャッチアップしていて気づきました。
そのため、今回は初めて `fullcalendar` を使用する人のために、「plugin」という視点のもと、fullcalendarを紹介していきたいと思います。今回記事の中で見ていくpluginは以下の通りです。

- UIフレームワーク
  - React
  - Vue
  - Angular
- 画面
  - daygrid
  - timegrid
  - list
- 動作
  - interaction

ちなみにpluginとは、

> できることを増やすためにくっつける機能拡張用のソフトウェアのこと
参考： [「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典](https://wa3.i-3-i.info/word11533.html)

として扱います。

## FullCalendarを動作する環境を整える

ここでは React + TypeScript の環境を想定します。

```bash
npm install --save @fullcalendar/react
```

以上です。

仮に、Vue や Angular を使用していたら、それぞれの plugin を install すれば FullCalendar を使った UI 作成の環境が整います。以下のページが、それぞれの環境におけるセットアップ手順なので、是非参考にしてみてください！

- [https://fullcalendar.io/docs/react](https://fullcalendar.io/docs/react)
- [https://fullcalendar.io/docs/vue](https://fullcalendar.io/docs/vue)
- [https://fullcalendar.io/docs/angular](https://fullcalendar.io/docs/angular)

なお、ここから先は React の環境を想定して、記事を書き進めていきます。基本的には後述する plugin を FullCalendar コンポーネントの plugin プロパティに配列で格納することで利用する仕組みは変わりませんが、記述方法がフレームワークによって変わるのでご注意ください。

さて、FullCalendarを動作する環境を整えましたが、これではまだ不完全です。カレンダーとして機能するために必要なpluginをこれからみていきます。

## 見た目を整える

ここでは

- daygrid
- timegrid
- list

の三つの plugin に分けて、紹介していきます。

ただし、やることは三つとも変わらず、

- plugin を install する
- plugins オプションに、使用する plugin を設定する
- initialView に初期描画するときのモードを指定する

の三つを行えば、表示は完成です。それでは見ていきましょう！

### daygrid

まず daygrid という plugin を install します。

```bash
npm install --save @fullcalendar/daygrid
```

daygrid を install することで、

- 月表示ができる
- 週表示ができる

ようになります。

試しに表示してみましょう。

```tsx
import React, { FC } from 'react';
import FullCalendar from '@fullcalendar/react'; // どのpluginよりも先にimportする必要があります
import dayGridPlugin from '@fullcalendar/daygrid';

export const DemoCalendar: FC = () => {
  return (
    <div className="demo-app">
      <div className="demo-app-main">
        <FullCalendar
          plugins={[dayGridPlugin]} // pluginsにdayGridPluginを設定する
          headerToolbar={{
            right: 'dayGridMonth,dayGridWeek',
          }}
          initialView="dayGridMonth" // 初期表示のモードを設定する
          events={'https://fullcalendar.io/api/demo-feeds/events.json'}
        />
      </div>
    </div>
  );
};
```

するとこんな画面になります。
![スクリーンショット 2022-03-21 20.26.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/462620/4367e332-b150-e155-de6c-dede7ba36b8f.png)


ここで `events` オプションには本来イベントを渡すべきですが、ここでは簡単にテストを行うために

```bash
https://fullcalendar.io/api/demo-feeds/events.json
```

を設定して、デモデータを `events` オプションに渡すようにします（以降も同様の手段を取ります）。

initialView に値を設定すると、どっちの View で初期描画を行うか選択できます。dayGrid の場合は、

- dayGridMonth なら月表示
- dayGridWeek なら週表示

です。

ただし、用途にもよりますが daygrid による週表示はあまり使用しないかもしれません。
なぜなら後述する timegrid による週表示では、時刻が縦軸に加わります。
１日にある予定が確認できればそれで十分、であれば daygrid の週表示でも良いですが、何時に何の予定があるかまで把握したい場合は、timegrid による週表示の方がよりユーザーにわかりやすく伝えることができるでしょう。

**参考**

https://fullcalendar.io/docs/daygrid-view

### timegrid

まず timegrid という plugin を install します。

```bash
npm install --save @fullcalendar/timegrid
```

こうすることで

- 時間軸のある週単位の表示
- 時間軸のある日単位の表示

ができるようになります。

試しに表示してみましょう。

```tsx
import React, { FC } from 'react';
import FullCalendar from '@fullcalendar/react';
import dayGridPlugin from '@fullcalendar/daygrid';
import timeGridPlugin from '@fullcalendar/timegrid';

export const DemoCalendar: FC = () => {
  return (
    <div className="demo-app">
      <div className="demo-app-main">
        <FullCalendar
          plugins={[dayGridPlugin, timeGridPlugin]} // pluginsにtimeGridPluginを設定する
          initialView="timeGridWeek" // 初期表示のモードを設定する
          headerToolbar={{
            right: 'dayGridMonth,timeGridWeek',
          }}
          events={'https://fullcalendar.io/api/demo-feeds/events.json'}
          allDaySlot={false}
        />
      </div>
    </div>
  );
};
```

するとこんな画面になります。

![スクリーンショット 2022-03-21 20.33.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/462620/a43b5122-5d4f-49d4-34b8-626b61f00e25.png)


datagrid 同様、initialView に値を設定すると、どういった見た目で初期描画を行うか選択できます。

- timeGridWeek なら週単位の表示
- timeGridDay なら日単位の表示

ですね。わかりやすいです。

また、timegrid は特別に設定できるオプションがあります。例えば上述した `allDaySlot` は、grid の上部に一日中の予定を表示するかどうか、を選択できます。こういったオプションも基本は、FullCalendar コンポーネントに適用すれば良いです。

例えば `eventMaxStack` というオプションは、時間被ったイベントが複数存在したとしてもカレンダー上で全てを把握したい、みたいなケースに使用すると良さそうですね。（ドキュメントのURLを載せておくので是非覗いてみてください！）

timeGrid にはオプションがたくさん存在するので、あんな UI にしてみたいな、など要望が出てきた時に、まずはオプションで実現できないか、などを探してみると良いかもしれません！

**参考**

https://fullcalendar.io/docs/timegrid-view


https://fullcalendar.io/docs/eventMaxStack


### list

まず list という plugin を install します。

```bash
npm install --save @fullcalendar/list
```

こうすることで

- その日のイベントを一覧表示できる
- その週のイベントを一覧表示できる
- その月のイベントを一覧表示できる
- その年のイベントを一覧表示できる

ようになります。
ここまできたらどうすればこの一覧表示できるか、例を書かずともわかるかと思いますが、念のため記述すると

```tsx
import React, { FC } from 'react';
import FullCalendar from '@fullcalendar/react';
import listPlugin from '@fullcalendar/list';

export const DemoCalendar: FC = () => {
  return (
    <div className="demo-app">
      <div className="demo-app-main">
        <FullCalendar
          plugins={[listPlugin]} // pluginsにlistGridPluginを設定する
          initialView="listDay" // 初期表示のモードを設定する
          headerToolbar={{
            right: 'listDay,listWeek,listMonth',
          }}
          buttonText={{ listDay: '日', listWeek: '週', listMonth: '月' }}
          events={'https://fullcalendar.io/api/demo-feeds/events.json'}
        />
      </div>
    </div>
  );
};
```

とすることで、一覧表示ができますね！（ `buttonText` などは見やすさのために新たに設定しました）

![スクリーンショット 2022-03-21 20.41.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/462620/ec3de652-4b2b-5a8c-068a-7dd02ed9571f.png)


この設定だと、その日のイベントを一覧表示するモードですが、他にもモードはあるので、試してみてくださいね！（今回のコードでは、 `headerToolbar` を使用してモード切り替えができるようになっています）

list においても timegrid と同様に、list 専用のオプションが存在するため、必要に応じてオプションを設定すると、自分好みのリストにできるので是非試してみてくださいね。

**参考**

https://fullcalendar.io/docs/list-view


## インタラクションするための環境を整える

最後に、ここでは fullcalendar においてインタラクションするための環境を整えていきます。

> 💡 そもそもインタラクションとは？
相互作用、交流、やり取り、などの意味を持つ英単語。“action” （活動/行為）に接頭辞 “inter-” （相互の/互いに）を付したもの。
ITの分野では、人間（利用者）によるコンピュータやソフトウェアへの働きかけ（操作など）と、対になる応答（表示の変化など）の組み合わせ、および、その連続や反復のことをインタラクションということが多い。
参考情報: [IT用語辞典 e-words](https://e-words.jp/w/%E3%82%A4%E3%83%B3%E3%82%BF%E3%83%A9%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3.html)


といってもやることは見た目を整えた時と変わらず

- plugin を install
- plugin を使用できるように設定する
- plugin に対応したオプションを設定する

の 3 点になります。

まずは interaction という plugin を install します。

```bash
npm install --save @fullcalendar/interaction
```

[Plugin Index](https://fullcalendar.io/docs/plugin-index) には

> Required to detect dateClick actions, selectable actions, and event drag-n-drop & resizing.
> Not needed for eventClick or eventMouseEnter/eventMouseLeave.

とあります。つまり、interaction を install したら

- 日付をクリックしたときの動作を検出できる
- 複数の日付や時刻を選択したときの動作を検出できる
- ドラッグアンドドロップやリサイズの動作を検出できる

ようになります。

これらの動作が検出できるということは

- 日付をクリックした時に予定の登録フォームを表示する

などのアプリケーションの動きも制御できるようになります。ちなみに

> Not needed for eventClick or eventMouseEnter/eventMouseLeave.

とあるように、

- イベントのクリック
- イベントの上にマウスを置く

などは、この plugin を install しなくても検知できるそうです。

この plugin を使うとできることがたくさん増えます。

**参考**

https://fullcalendar.io/docs/date-clicking-selecting

https://fullcalendar.io/docs/event-dragging-resizing

すべてを紹介するのは、ちょっと大変なのでここではよく使う Date Clicking & Selecting の `Selecting` に関連するプロパティをいくつかご紹介します。

ではこれらのプロパティを使って試しに表示させていきましょう。

```tsx
import React, { FC } from 'react';
import FullCalendar, { DateSelectArg } from '@fullcalendar/react';
import dayGridPlugin from '@fullcalendar/daygrid';
import timeGridPlugin from '@fullcalendar/timegrid';
import interactionPlugin from '@fullcalendar/interaction';

export const DemoCalendar: FC = () => {
  const handleDateSelect = (selectionInfo: DateSelectArg) => {
    console.log('selectionInfo: ', selectionInfo); // 選択した範囲の情報をconsoleに出力
    const calendarApi = selectionInfo.view.calendar;

    calendarApi.unselect(); // 選択した部分の選択を解除
  };

  return (
    <div className="demo-app">
      <div className="demo-app-main">
        <FullCalendar
          plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]} // pluginsにlistGridPluginを設定する
          initialView="dayGridMonth" // 初期表示のモードを設定する
          headerToolbar={{
            right: 'dayGridMonth,timeGridWeek',
          }}
          events={'https://fullcalendar.io/api/demo-feeds/events.json'}
          select={handleDateSelect}
          selectable={true}
          selectMirror={true}
        />
      </div>
    </div>
  );
};

```

この状態で画面上で時刻を選択したときの挙動は以下のようになります。

![スクリーンショット 2022-03-21 20.51.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/462620/a149a8ef-0ca8-e799-6937-7f7107bb47e1.png)
![スクリーンショット 2022-03-21 20.52.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/462620/17c9c3c5-2474-b03b-fcb4-db1966241c91.png)


### selectable

ユーザーがクリックしてドラッグすることにより、複数の日または時刻を強調して表示するかどうかを設定するオプションです。

**参考**

https://fullcalendar.io/docs/selectable

### selectMirror

ユーザーがドラッグしているときに「プレースホルダー」イベントを描画するかどうかを設定するオプションです。
trueにしていると、画像2枚目の03:00~05:00のようにイベントを描画してくれます。

**参考**

https://fullcalendar.io/docs/selectMirror


### select

日付や時刻が選択された時に発火するイベントを設定できます。このイベントを設定する際に、 `selectionInfo` という引数を受け取ることができます。簡単にいうと、選択された範囲の情報を関数の引数にして、いろんなことができます。

今回の例では以下の部分です。

```tsx
const handleDateSelect = (selectionInfo: DateSelectArg) => {
  console.log('selectionInfo: ', selectionInfo); // 選択した範囲の情報をconsoleに出力
  const calendarApi = selectionInfo.view.calendar;

  calendarApi.unselect(); // 選択した部分の選択を解除
};

<FullCalendar
  plugins={[dayGridPlugin, timeGridPlugin, interactionPlugin]}
  initialView="dayGridMonth"
  select={handleDateSelect}
/>
```

`handleDateSelect` という関数は

- 選択した範囲の情報を console に出力
- カレンダーの選択した部分の選択を解除

を行います。

`select` というオプションに自ら作成した関数 `handleDateSelect` を設定してみました。こうすることで、日付や時刻を選択した時に、選択した範囲の情報を console に出力し、カレンダーの選択した部分の選択を解除します。

`handleDateSelect` で受け取る引数は start（Date 型での選択範囲の開始時刻）などいくつかあるので確認してみてください

**参考**

https://fullcalendar.io/docs/select-callback

## 終わりに

plugin から理解する FullCalendar、いかがでしたでしょうか？

この記事が、FullCalendar を使って何か実装したい、という方のお力になれれば幸いです。

また、今回「plugin から理解する」という初めての試みで書いてきましたので、役に立つ記事が書けたか少し不安です。

plugin から理解する、という視点は、eslint など他のライブラリの使い方を理解する時にも、効果を発揮するかもしれません。

もし、初めて使うライブラリの使い方がいまいち理解できないな、という方は「plugin から理解する」というアプローチをとってみても良いかもしれませんね！（余計わかりづらくなったわ！などでしたら申し訳ございません。普段通りのキャッチアップ方法でお願いいたします :bow: ）