kintone カレンダーPlusに祝日を表示

[カレンダーPlus Advent Calendar 2020](https://adventar.org/calendars/5414)の12/19 担当分です。
カレンダーPlus に Google Calendar の祝日を表示してみます。

# はじめに

こんにちは、kintone では [rex0220 プラグイン](https://rex0220.stores.jp/)を開発・販売しております。
カレンダーPlus に連携するプラグインとして、[計算式プラグイン](https://qiita.com/rex0220/items/ae83c6d5ce50ff7a9a73)があります。
今回は、カレンダーPlus のJavaScript API を利用して、Google Calendar の祝日を表示する JavaScript カスタマイズをやってみました。
処理ごとに解説を入れましたので、他に応用する場合もご参考になると思います。

またカレンダーPlusアドオンを募集との知らせを受け、さっそく[休日Plusプラグイン](https://qiita.com/rex0220/items/197cdc31b0bb928b4cdb)を開発しました。名前からしてカレンダーPlusに乗っかっています。
JavaScript カスタマイズなんてかったるいことやってられないという方は、そちらをご覧ください。

もっと簡単に祝日を表示したいという方は、カレンダーPlus 機能追加のリクエストを出してプレッシャーをかけましょう！！

# 概要

Google Calendar API で、日本の祝日を取得して、カレンダーPlusの月表示に表示します。

- カレンダーPlusで使用しているタグ名やクラス名を元にカスタマイズしますので、仕様変更で動作しなくなるリスクがあります。
- Google Calendar の日本の祝日で取得できるのは、２〜３年分です。

PC の祝日表示。いい感じですね。
![92C4DD99-66DA-4E33-8ECB-A44DDDAE6C69_1_105_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100572/7dde6a7e-4c6a-5050-4672-a1de1612f645.jpeg)

モバイル表示だと、祝日名が枠からはみ出ちゃってます。ここはご愛敬ということで！
![3EB515E7-AF15-48FC-ADBE-8BF879BFB6B5_1_105_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100572/35e50525-a8d6-63c4-921d-3735a1f1d3a9.jpeg)


# Google Calendar API の準備

[JavaScriptでGoogle Calendar APIを実装する方法を現役エンジニアが解説【初心者向け】](https://techacademy.jp/magazine/25405) に、「API Key」 と 「カレンダーID」を取得する方法が載っています。
手順通りにやれば簡単です。

ただ、「API Key」を作るページが探しにくかったので記載しておきます。
[Browser Quickstart](https://developers.google.com/calendar/quickstart/js)

取得した「API Key」 、 「カレンダーID」とGoogleAPI 呼び出し用のPATHを変数に入れておきます。

```.js
    const API_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxx'; // 取得した API KEY を指定
    const CALENDAR_ID = 'ja.japanese#holiday@group.v.calendar.google.com';
    const GAPI_PATH = 'https://www.googleapis.com/calendar/v3/calendars/' + encodeURIComponent(CALENDAR_ID) + '/events';
```

# カレンダーPlus JavaScript API の利用

[Calendar Plus JavaScript API リファレンス](https://radical-bridge.com/product/kintone-plugin/calendarPlus/CalendarPlus-JavaScript-API.pdf) に説明が載っていますが、サンプルコードが少なく、少しとっつきにくいかもしれません。

## カレンダーPlus カレンダー表示イベント('cp.calendar.show')

今回は、カレンダーPlusの月表示のカスタマイズを行いますので、カレンダー表示イベント('cp.calendar.show')を利用します。

リファレンスの説明

>カレンダー画面の描画後イベント
カレンダー画面の描画終了後に発生するイベントです。
・表示対象の日付範囲切り替え時や月別・週別・日別表示の切り替え時にもイベントが発生します。
※2019/3/6 現在、ドラッグ&ドロップによるイベントレコード更新時には当イベントが 2 回発生します。

カレンダー表示イベントは、カレンダー画面の描画終了後だけではなく、ドラッグ&ドロップによるイベントレコード更新時にも発生するので、短時間に何回実行されてもいいように処理を検討しましょう。


## カレンダーPlus カレンダー表示イベントハンドラーを登録

カレンダーPlus が表示されるのは、kintone の一覧表示カスタム画面ですので、そのタイミングでカレンダー表示イベントハンドラーを登録します。

```.js
    // kintone 一覧表示
    kintone.events.on(['app.record.index.show','mobile.app.record.index.show'], function (event) {
        if (event.viewType !== 'custom') return event;
        // カレンダー表示イベント
        calendarplus.events.on('cp.calendar.show', function (ev) {
            if (ev.view.type !== 'month') return;
            // 祝日表示
            gapi.load('client', start);
        });
        return event;
    });
```

- kintone の一覧表示イベント：'app.record.index.show','mobile.app.record.index.show'
PC とモバイル両方のカレンダーPlus表示に対応します。

- イベント情報の変数名
kintone とカレンダーPlusの両方にイベント情報がありますので、下記のようにして区別します。
    - kintone: event
    - カレンダーPlus: ev

- event.viewType： kintone の一覧画面の種類で、'custom' のみを処理します。
通常の一覧画面や標準のカレンダー表示では、処理しないように return event; します。
今回の場合、一覧画面でカレンダーPlus のイベントハンドラーを登録しても、呼び出されないので問題ないのですが、できるだけ他の画面では余計なことはしないようにしましょう。

- ev.view.type： カレンダーPlus で表示される表示形式を取得できます。
これは、カレンダーPlus で、使用している fullcalendar のViewオブジェクトを参照しています。
[fullcalendar docs view object](https://fullcalendar.io/docs/view-object) 
カレンダーPlus の月形式の表示では、'month' となります。
docs を見ると、他にも title やら calendar オブジェクトなども利用できますので、多くの情報を取得できます。

- gapi.load('client', start); 
Google カレンダーを取得するための GoogleAPI を使い、start 関数で処理を記述します。

# 祝日処理
GoogleAPI の手順に従っているので少しわかりにくいのですが、API_KEY を GAPI_PATH に渡して、祝日情報を取得しているだけです。
「// カレンダーPlus 祝日処理」以降が、実際に祝日を表示する処理になります。

```.js
    // 祝日処理
    function start() {
        // 祝日取得
        gapi.client.init({
            'apiKey': API_KEY,
        }).then(function () {
            return gapi.client.request({
                'path': GAPI_PATH
            })
        }).then(function (response) {
            // カレンダーPlus 祝日処理
            const items = response.result.items;
            setHolidays(items);
        }, function (reason) {
            console.log('google calendar api Error: ' + reason.result.error.message);
        });
    };
```

- const items = response.result.items;<br>
Google カレンダーから取得した祝日情報

# カレンダーPlus 祝日処理

items に、祝日情報が配列で入っていますので、祝日数分ループして処理します。
Chrome の開発者ツールで、items の中身を見てみましょう。
同じく DOM 構造もChrome の開発者ツールで確認できます。

- items の中身
![2020-12-18_13h36_15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100572/930be7d2-40b2-abb7-9c49-099e349a2484.png)

    - start.date　祝日の日付
    - summary 祝日の名称

- カレンダーPlus の DOM 構造
    - 月表示の日付枠は、テーブルタグ"td"内に、リンクタグ"a" という構造です。
    - 日付を識別できるものとしては、td の属性にdata-date があり、日付が入っています。<br>
    これをキーにして、element を取得できますね。<br>
    data-date="2021-01-05"


![2020-12-18_13h45_40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100572/8290eb34-1853-6c17-6ba0-b18b9023a6ee.png)

- クラス名に曜日の識別
    - fc-sun, fc-mon... そのまま曜日です。土日の識別になります。
![2020-12-18_13h55_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100572/d5f95ab3-10ce-4cb3-2772-2ef205120bcc.png)


- 祝日を該当箇所に挿入
data-date 属性で、対象日付箇所を取得して、日付名称を挿入します。
赤表示は、css で指定します。

```.js
    // カレンダーPlus 祝日表示
    function setHolidays(items) {
        // 祝日数分ループ
        items.forEach(function(item) {
            const dt = item.start.date;
            const sel = 'td[data-date="' + dt + '"] > a';
            var ele1 = document.querySelector(sel);
            if (ele1) {
                if (!ele1.parentElement.querySelector('span.xp-cplus-holiday')) {
                    ele1.classList.add('xp-cplus-holiday');
                    var html = '<span class="xp-cplus-holiday">' + item.summary + '</span>';
                    ele1.insertAdjacentHTML('beforebegin',html);
                }
            }
        });
    }
```

- const sel = 'td\[data-date="' + dt + '"\] > a';<br>
dt に日付 "2020-01-01" 等が入ってきますので、文字列は、<br>
'td\[data-date="2020-01-01"\] > a' のようになります。
- var ele1 = document.querySelector(sel);<br>
'td\[data-date="2020-01-01"\] > a' のリンクタグを取得します。<br>
属性 data-date="2020-01-01" を持つ td タグ直下の a リンクタグが対象です。
-  if (!ele1.parentElement.querySelector('span.xp-cplus-holiday')) {<br>
対象枠に「span.xp-cplus-holiday」が無かったら（未処理だったら）祝日を追加します。<br>
カレンダーPlus のカレンダー表示イベントは、何度も呼び出されるため、祝日追加は、１回のみにします。
- ele1.classList.add('xp-cplus-holiday');<br>
CSS のクラス名をリンクタグに追加します。CSS で、文字色等を指定できます。
- le1.insertAdjacentHTML('beforebegin',html);<br>
xp-cplus-holiday のクラス名を付けて、祝日表示の span タグを組み立てて、日付リンクタグの前に挿入します。<br>
クラス名があると、ブラウザーの開発ツールでいろいろ調整するのが簡単にできて便利です。


# JavaScript 全体

以上をまとめたものが下記になります。

```cplus-holiday.js
////////////////////////////////////////////////////////////////////////////
// kintone JavaScript: カレンダープラスプラグイン　google calendar 祝日表示(month)
//  2020.12.18 by rex0220
////////////////////////////////////////////////////////////////////////////
(function () {
    'use strict';
    const API_KEY = 'xxxxxxxxxxxxxxxxxxxxxxxxxx'; // 取得した API KEY を指定
    const CALENDAR_ID = 'ja.japanese#holiday@group.v.calendar.google.com';
    const GAPI_PATH = 'https://www.googleapis.com/calendar/v3/calendars/' + encodeURIComponent(CALENDAR_ID) + '/events';

    // kintone 一覧表示
    kintone.events.on(['app.record.index.show','mobile.app.record.index.show'], function (event) {
        if (event.viewType !== 'custom') return event;
        // カレンダー表示イベント
        calendarplus.events.on('cp.calendar.show', function (ev) {
            if (ev.view.type !== 'month') return;
            // 祝日処理
            gapi.load('client', start);
        });
        return event;
    });

    // 祝日処理
    function start() {
        // 祝日取得
        gapi.client.init({
            'apiKey': API_KEY,
        }).then(function () {
            return gapi.client.request({
                'path': GAPI_PATH
            })
        }).then(function (response) {
            // カレンダーPlus 祝日表示
            const items = response.result.items;
            setHolidays(items);
        }, function (reason) {
            console.log('google calendar api Error: ' + reason.result.error.message);
        });
    };

    // カレンダーPlus 祝日表示
    function setHolidays(items) {
        // 祝日数分ループ
        items.forEach(function(item) {
            const dt = item.start.date;
            const sel = 'td[data-date="' + dt + '"] > a';
            var ele1 = document.querySelector(sel);
            if (ele1) {
                if (!ele1.parentElement.querySelector('span.xp-cplus-holiday')) {
                    ele1.classList.add('xp-cplus-holiday');
                    var html = '<span class="xp-cplus-holiday">' + item.summary + '</span>';
                    ele1.insertAdjacentHTML('beforebegin',html);
                }
            }
        });
    }
})();
```


# CSS で土日祝日を赤表示

CSS で、土日祝日の表示を赤に指定します。
fc-sun の部分を変えれば、好きな曜日の色を変えられます。
PC/スマートフォン兼用の CSS です。

```cplus-holiday.css
/* calendar plus holiday color */
.fc-view-container .fc-sun > a,
.fc-view-container .fc-sat > a,
.fc-view-container .fc-sun > span,
.fc-view-container .fc-sat > span {
  color: red;
}
.fc-view-container .xp-cplus-holiday {
  color: red;
}
```

- .fc-view-container .fc-sun > a,<br>
CSS は、アプリ全体に適用されるため、カレンダーPlus で利用している FullCarendar で使用している fc-view-container クラスの中だけに、限定するように指定しています。<br>
適用範囲を限定することで、他の画面で変な現象がでる問題の防止になります。<br>
ただし、タグ名・クラス名などは仕様変更で変わってしまうリスクもあります。
- .fc-view-container .xp-cplus-holiday {<br>
xp-cplus-holiday は、祝日の名称を追加したときに付与したクラス名です。<br>
色以外に、文字の大きさなどもここで変えられます。

# JavaScript/CSS の指定

URL指定で Google API のライブラリ、カスタマイズ用JavaScript,CSSファイルを指定します。
今回の JavaScript, CSS は、PC、モバイル兼用ですので同じものをアップロードします。

- PC用の JavaScript
    - https://apis.google.com/js/api.js
    - cplus-holiday.js
- スマートフォン用の JavaScript
    - https://apis.google.com/js/api.js
    - cplus-holiday.js
- PC用の CSS
    - cplus-holiday.css
- スマートフォン用の CSS
    - cplus-holiday.css

![736AD10C-8901-4DF2-8CD4-71B9596F5BA1_1_105_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/100572/edec9ecc-6cc7-0b49-ea7a-2942f0263fb5.jpeg)

# 改善点

以上で、祝日をカレンダーに表示することができました。
しかしちょっと気になるのが、カレンダーが表示されてから祝日が表示されるまでに少しタイムラグがあることです。
カレンダー表示イベント毎に祝日データを取得しているせいですね。
「＞」ボタンなどで月の切り替えを繰り返すとタイムラグが目立ちます。

祝日データは、何回取得しても内容は変わりませんので、１回取得したらそれを使いまわしたほうがよさそうです。
ぜひタイムラグの解消に挑戦してみてください。

# 注意事項

- カレンダーPlusで使用しているタグ名やクラス名を元にカスタマイズしますので、仕様変更で動作しなくなるリスクがあります。
- カレンダーPlus へ祝日・休日を表示したいという要望が多いとのことなので、将来のバージョンアップでカレンダーPlus に機能が追加される可能性があります。急がない方は、期待して待っていましょう。

- Google Calendar API には、回数制限や割り当て制限があります。

    - [Google Calendar API Usage Limits](https://developers.google.com/calendar/pricing)
        - 1,000,000 requests/ day
    - [API リクエストの制限と割り当て](https://developers.google.com/analytics/devguides/config/mgmt/v3/limits-quotas?hl=ja)
        - デフォルト 500 requests/100seconds/user 

500人の kintone ユーザーが一斉にカレンダーを見る職場だと制限値を超えちゃいますね。
（どんな職場だ！朝９時にカレンダーチェックするルールとか？）
問題が起きたときは、想定外だったとシラを切りましょう。

# あとがき

多くの kintone プラグインがありますがカレンダーPlus は、API を公開している数少ないプラグインの一つです。
標準機能に足りないものは、APIを利用すると自前で何とか出来ちゃうのがいいですね。
ただし、保守を考えると後々困る場合が出てきます。
とりあえず欲しい機能は、カレンダーPlusへリクエストを上げておきましょう。
斎藤さん、リクエスト対応よろしくお願いします！

# 参考

- [FullCalendarでテキストとして休日（などの情報）を表示する](https://qiita.com/koike_moyashi/items/b88a7c76ed37a4fec0ee)
- [Fullcalendar](https://fullcalendar.io/)
- [Google Calendar API](https://developers.google.com/calendar/overview)
- [Calendar Plus JavaScript API リファレンス](https://radical-bridge.com/product/kintone-plugin/calendarPlus/CalendarPlus-JavaScript-API.pdf)
- [kintone 開発で Chrome デベロッパーツールを使おう](https://qiita.com/rex0220/items/58122efda9f94f669ec4)