2019 年 5 月のメンテナンスで追加される Garoon API の紹介

目次

caution
警告

Moment.jsはメンテナンスモードになり、 日付処理できる代替ライブラリへの移行 (External link) が推奨されています。
代替ライブラリのひとつ Luxon (External link) については、 kintone カスタマイズでの導入方法の紹介記事があります。

はじめに

この記事では、画像アセットを使ったカスタマイズ例を紹介します。

ファイルキーから画像 URL を取得する(画像アセット)

画像アセット

「画像アセット」は、画像ファイルをGaroonの共有リソースとして保存する機能です。

画像アセットでは、画像ファイルをファイルキーと一緒に登録します。
そのあと、利用したい画像のファイルキーを指定し、 ファイルキーから画像 URL を取得する APIを使うと、画像のURLを取得できます。

「画像アセット」を使うメリット

これまで、Garoonのカスタマイズで画像を使う場合、「ファイル管理」アプリケーションに画像ファイルを保存していました。
そして、保存した画像ファイルのアドレス情報(URL)を右クリックで確認し、カスタマイズファイルの画像URLを書き換える必要がありました。

今回追加された ファイルキーから画像 URL を取得する APIを使うと、画像ファイルのURLを自動で取得できるため、カスタマイズファイルの画像URLを書き換える必要がなくなります!

これにより、カスタマイズの手順は下図のようにぐっと簡略化されます。

「画像アセット」を使ったポータルカスタマイズ

完成例

スケジュールに登録された、自分の予定と施設を表示するポートレットを作ります。
このポートレットでは、いまの予定や30分以内の次の予定について、予定名や時間、施設の画像を表示します。

カスタマイズ手順

手順 1.「画像アセット」に画像ファイルを登録する

ポートレットに表示する施設の画像ファイルを準備し、「画像アセット」アプリケーションに追加します。

  1. [Garoonシステム管理]から、[各アプリケーションの管理]タブを選択し、[画像アセット]を選択します。
  2. [画像アセット一覧]を選択し、[画像アセットを追加する]をクリックします。

  3. 「画像アセットの追加」画面で、[ファイルを添付]をクリックし、施設の画像ファイルを指定します。
    次のように入力します。

    項目名
    ファイルキー ファイルキーから画像 URL を取得する APIで画像を指定するためのキーです。
    今回のカスタマイズでは、施設情報の施設コードをキーに画像を指定するので、ファイルキーには「施設コード」を入力します。

    「施設コード」の確認は、[Garoon システム管理] > [各アプリケーションの管理] > [スケジュール] > [施設/施設グループ] で確認できます(参考: 施設を管理する (External link)
  4. [追加する]ボタンをクリックします。
  5. 同様の手順で、ポートレットに表示する施設の画像ファイルすべてを、画像アセットに登録します。
手順2. カスタマイズファイルの準備

今回のカスタマイズでは、次の2つのカスタマイズファイルを利用します。
また、カスタマイズファイル内では、 Cybozu CDNに公開されているMoment.jsとjQueryを利用しています。

  • カスタマイズファイル
    • ポートレットを表示するJavaScriptファイル(next_room_main.js)
    • ポートレットに適用するcssファイル(next_room.css)
  • 外部ライブラリ
    • Moment.js 2.10.3
      https://js.cybozu.com/momentjs/2.10.3/moment-with-locales.min.js
    • jQuery 3.3.1
      https://js.cybozu.com/jquery/3.3.1/jquery.min.js
カスタマイズファイル
  • ポートレットを表示するJavaScriptファイル(next_room_main.js)
    Garoonの予定を取得し、予定からHTMLを生成するスクリプトファイルです。

    • 今回のポイントは、97行目のgaroon.assets.images.getUrl(facility.code)です。
      garoon.assets.images.getUrlは、画像アセットに登録された画像について、ファイルキーから画像URLを取得するAPIです。
      さきほど画像アセットに登録するとき、ファイルキーに「施設コード」を設定しました。
      そのため、APIに「施設コード」を指定し、施設の画像URLを取得しています。

        1
        2
        3
        4
        5
        6
        7
        8
        9
       10
       11
       12
       13
       14
       15
       16
       17
       18
       19
       20
       21
       22
       23
       24
       25
       26
       27
       28
       29
       30
       31
       32
       33
       34
       35
       36
       37
       38
       39
       40
       41
       42
       43
       44
       45
       46
       47
       48
       49
       50
       51
       52
       53
       54
       55
       56
       57
       58
       59
       60
       61
       62
       63
       64
       65
       66
       67
       68
       69
       70
       71
       72
       73
       74
       75
       76
       77
       78
       79
       80
       81
       82
       83
       84
       85
       86
       87
       88
       89
       90
       91
       92
       93
       94
       95
       96
       97
       98
       99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      
      /**
      * Garoon sample program
      * schedule room portlet (main)
      * Copyright (c) 2019 Cybozu
      *
      * Licensed under the MIT License
      */
      
      (function() {
        'use strict';
        const myJQuery = jQuery.noConflict(true);
        (function($) {
      
          // ログインユーザの今日の予定を取得する
          const fetchTodayEvents = function(now) {
            const start = now.clone().hour(0).minute(0).seconds(0).toISOString();
            const end = now.clone().hour(23).minute(49).seconds(59).toISOString();
            const url = '/api/v1/schedule/events?rangeStart=' + start + '&rangeEnd=' + end;
            return garoon.api(url, 'GET', {}).then((resp) => {
              return resp.data;
            });
          };
      
          // 現在の予定か30分以内の予定の場合を対象とする
          const seekNearyEvents = function(events, now) {
            const enableEvents = [];
            const nextTime = now.clone().add(30, 'minutes');
      
            for (let i = 0; i < events.length; i++) {
              const event = events[i];
              // 終日イベントは除外
              if (event.eventType === 'ALL_DAY') {
                continue;
              }
              const start = event.start.dateTime;
              const end = event.end.dateTime;
              // 時間未指定のイベントを除外
              if (start === now.format('YYYY-MM-DD') + 'T00:00:00+09:00' && end === now.format('YYYY-MM-DD') + 'T23:59:59+09:00') {
                continue;
              }
              // 現在の予定か、30分以内の予定の場合を対象とする
              if (now.isBetween(moment(start), moment(end))) {
                event.isCurrent = true;
                enableEvents.push(event);
                continue;
              }
              if (nextTime.isAfter(moment(start)) && nextTime.isBetween(moment(start), moment(end))) {
                event.isCurrent = false;
                enableEvents.push(event);
                continue;
              }
            }
            return enableEvents;
          };
      
          moment.locale('ja');
          const now = moment();
      
          const $title = $('<h2></h2>').text('現在時刻 ' + now.format('HH:mm'));
          $('.next_room').append($title);
          const $contents = $('<div></div>').addClass('next_room_contents');
          $('.next_room').append($contents);
      
          return fetchTodayEvents(now).then((resp) => {
            const events = seekNearyEvents(resp.events, now);
            // 予定がない場合
            if (!events) {
              const $subject = $('<p></p>').addClass('next_room_subject');
              $subject.text('なし');
              $contents.append($subject);
              return;
            }
      
            for (let i = 0; i < events.length; i++) {
              const event = events[i];
              const $event = $('<div></div>').addClass('next_room_event');
              const $subject = $('<p></p>').addClass('next_room_subject');
              let subjectText = event.subject + ' (' +
                  moment(event.start.dateTime).format('HH:mm') + ' ~ ' +
                  moment(event.end.dateTime).format('HH:mm') + ')';
              if (event.isCurrent) {
                subjectText = '現在の予定:' + subjectText;
              } else {
                subjectText = '次の予定:' + subjectText;
              }
              $subject.text(subjectText);
              $event.append($subject);
      
              const $list = $('<div></div>').addClass('next_room_facilities');
              for (let j = 0; j < event.facilities.length; j++) {
                const facility = event.facilities[j];
                const $item = $('<div></div>').addClass('facilitiy_item');
                const $facilityName = $('<div></div>').addClass('facilitiy_name');
                const $facilityImage = $('<div></div>').addClass('facilitiy_image');
      
                $facilityName.text(facility.name);
                const imgUrl = garoon.assets.images.getUrl(facility.code);
                $facilityImage.html('<img src="' + imgUrl + '" width="150" height="auto"></img>');
      
                $item.append($facilityName);
                $item.append($facilityImage);
                $list.append($item);
              }
              $event.append($list);
              $contents.append($event);
            }
          });
        })(myJQuery);
      })();
  • ポートレットに適用するcssファイル(next_room.css)
    ポートレットに適用するスタイルシートファイルです。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    
    .next_room {
      background-color: #b1d1d6;
      padding: 10px;
    }
    
    .next_room > h2 {
      background-color: #F9E5DA;
      padding: 10px;
      font-weight: bold;
      font-size: 16px;
    }
    .next_room .next_room_contents {
      font-size: 12px;
      padding: 0;
    }
    
    .next_room_subject {
      font-weight: bold;
    }
    
    .next_room_event {
      padding: 3px 10px 5px;
      margin: 5px;
      background-color: #d3e5e8;
    }
    
    .next_room_facilities {
      margin: 0px auto;
      padding: 0;
      width: 100%;
      list-style-type: none;
      display: flex;
      flex-wrap: wrap;
    }
    
    .next_room_facilities .facilitiy_item {
      padding: 5px;
      background-color: #f2f2f2;
    }
手順3. カスタマイズの適用
  1. [Garoonシステム管理]から、[各アプリケーションの管理]タブを選択し、[ポータル]を選択します。
  2. [HTMLポートレット]を選択し、[HTMLポートレットを追加する]をクリックします。
  3. 次のように入力します。HTMLポートレット設定の詳細は、 HTMLポートレットの設定 (External link) を参照してください。
    項目名
    ポートレット名 ポートレット名を入力します。
    今回は「次の予定ポートレット」としました。
    グループ 適宜設定してください。
    My ポータル 適宜設定してください。
    ポートレットの内容 「テキスト」を選択し、次の内容を入力してください。
    <div class="next_room">
    </div>
  4. 入力が完了したら、[追加する]ボタンをクリックし、ポートレットを追加します。
  5. HTML ポートレットの一覧から、先ほど追加したポートレットを選択します。
  6. [JavaScript/CSS によるカスタマイズ]をクリックします。
  7. 次のように入力します。入力後、[設定する]ボタンをクリックします。

    項目名
    カスタマイズ 適用する
    JavaScript カスタマイズ 次の順でリンクおよびファイルを追加します。
    • https://js.cybozu.com/momentjs/2.10.3/moment-with-locales.min.js
    • https://js.cybozu.com/jquery/3.3.1/jquery.min.js
    • ポートレットを表示する JavaScript ファイル(next_room_main.js)
    CSS カスタマイズ 次のファイルを追加します。
    • ポートレットに適用する css ファイル(nextRoom.css)
  8. 追加したポートレットをポータルに追加します。
    ポータルへの追加方法は「 ポートレットを配置する (External link) 」を参照してください。

手順は以上です。

「予定の登録」「予定の変更」画面の登録/変更実行前イベント(スケジュール)

「予定の登録」画面や「予約の変更」画面で、「登録する」または「変更する」ボタンをクリックした後に発生するイベントです。

このイベントによって、「入力内容のエラーチェック」や「定例ミーティングの場合はメモ欄にアジェンダのテンプレートを追加する」といった処理ができるようになりました。

information

2019年5月現在、このイベントはPromise対応していません。そのため、非同期処理の実行を待ってから登録または変更処理の実行はできません。

「予定の登録画面/予約の変更画面の登録前実行イベント」を使ったカスタマイズ例

完成例

予定を登録するときうっかり施設を設定し忘れて、当日打ち合わせする場所がない!となってしまったことはありませんか?
そんなうっかりミスを防ぐため、予定の登録または変更時に、施設の指定がなければエラーにするカスタマイズを紹介します。

カスタマイズ手順

手順1. カスタマイズファイルの準備

今回のカスタマイズでは、次のカスタマイズファイルを利用します。

  • カスタマイズファイル
    • 施設の設定有無をチェックするJavaScriptファイル(check_set_facility.js)
カスタマイズファイル
  • 施設の設定有無をチェックするJavaScriptファイル(check_set_facility.js)
    Garoon予定の登録または変更時に、施設が設定されていなければエラーにして予定を登録させないスクリプトファイルです。
    • 今回のポイントは、9行目と11行目です。

      • エラーダイアログを表示し予定を登録(変更)させないようにするには、イベントオブジェクトのエラープロパティに、表示したいエラーメッセージを設定します。
        今回は、施設の設定がされていない場合、つまり 施設に関するプロパティの長さが0なら、エラープロパティに値を設定します。
      • 最後にイベントオブジェクトを返却してeventの内容をGaroonに反映させます。
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      
      (function() {
        'use strict';
      
        const events = ['schedule.event.create.submit', 'schedule.event.edit.submit'];
        garoon.events.on(events, (event) => {
          const scheduleEvent = event.event;
          const facilities = scheduleEvent.facilities;
          if (facilities.length === 0) {
            event.error = '施設を指定してください。';
          }
          return event;
        });
      })();
手順2. カスタマイズの適用
  1. [Garoonシステム管理]から、[各アプリケーションの管理]タブを選択し、[スケジュール]を選択します。
  2. [JavaScript/CSSによるカスタマイズ]を選択し、[カスタマイズグループを追加する]をクリックします。
  3. 次のように入力します。入力後、[追加する]ボタンをクリックします。

    項目名
    カスタマイズ 適用する
    カスタマイズグループ名 カスタマイズグループ名を入力します。
    適用対象 カスタマイズを適用したい対象(ユーザー、組織またはロール)を選択します。
    JavaScript カスタマイズ カスタマイズファイルを追加します。
    • 施設の設定有無をチェックする JavaScript ファイル(check_set_facility.js)
    CSS カスタマイズ なし

手順は以上です。

おわりに

2019年5月の定期メンテナンスで、Garoon APIが複数登録され、Garoonカスタマイズの幅が広がりました。

この記事で紹介しているAPI以外にも、ワークフローのフィールドを編集可/不可に設定できたり、在席情報を取得・更新できるAPIなども追加されています。

これからもGaroon APIは追加される予定なので、Garoonカスタマイズに挑戦して、運用に合わせてより使いやすくしていただければと思います。

information

このTipsは、2019年5月版Garoonで動作を確認しています。