Garoon スケジュール・ポータルのカスタマイズをはじめよう〜会議効率化カスタマイズ〜
はじめに
この記事は、「API ドキュメントを読んだけど、使い方がわからない」「Garoon カスタマイズに興味はあるけど、何から始めてよいかわからない」といった人に向けた内容です。
Garoon スケジュールやポータルのカスタマイズを通じて、Garoon JavaScript API と Garoon REST API の使い方を知り、Garoon カスタマイズの基本を学んでみましょう。
この記事の内容は、Cybozu Days 2019 の Garoon 中級ハンズオン〜API を使って作る、カスタマイズポータル作成ハンズオン〜で行ったものです。
この記事で学べること
- Garoon JavaScript API(イベント)の使い方
- Garoon REST API の使い方
- 予定の更新
- 条件に一致する予定の取得
- 予定のカスタム項目(schedule datastore)
- Garoon スケジュールおよびポータルのカスタマイズを適用する方法
環境準備
- Garoon クラウド版、またはパッケージ版 ver. 5.9 以降
- テキストエディタ
注意事項
- モバイル画面および KUNAI では利用できません。
Garoon JavaScript API と Garoon REST API
ここでは Garoon の代表的なアプリケーションのワークフロー、スケジュール、ポータルを例に、Garoon JavaScript API と Garoon REST API を説明します。
JavaScript とは
JavaScript はブラウザーで利用できるプログラミング言語です。
Web ページはコンテンツの内容を作る HTML と見た目を整える CSS を使って作られていますが、一度読み込むとページの内容は基本的に変化しません。
しかし、JavaScript を利用すると、リアルタイムにページの内容を書き換えできるので、Web ページに動きをつけることができます。
Garoon JavaScript API とは
Garoon JavaScript API は、画面をカスタマイズするためのインターフェースで、開いている Garoon の画面に表示されている内容や HTML の要素を取得、操作できます。
Garoon JavaScript API を使うと、Garoon の画面の見た目を変えたり、入力された値が正しいかをチェックするなどの動きを変えたりできます。
JavaScript には、「◯◯が実行されたときに◯◯する」といった処理ができる「イベント」というしくみがあります。
Garoon の場合、「予定の詳細画面が表示されたとき」「ワークフローが承認されたとき」といったイベントが存在します。
このイベントを利用すると、「グループ週画面が表示されたとき、週画面に付加情報を表示する」といったカスタマイズができます。
Garoon REST API とは
Garoon REST API は、データ連携のためのインターフェースで、Garoon で管理しているデータを外部システムに渡したり、外部システムから Garoon にデータを登録、更新、削除できます。
また、Garoon REST API はスケジュールとワークフローといった、Garoon の異なるアプリケーション間でデータをやりとりしたい場合や、開いている Garoon の画面上にない内容を取得、操作したい場合にも利用します。
ハンズオン
それでは、スケジュールとポータルをそれぞれカスタマイズしていきましょう。
完成イメージ
会議の効率化(会議が短縮された時間)を数値として把握し、ポータルで全社の成果をグラフで見える化するカスタマイズです。
スケジュールのカスタマイズ
- 予定の詳細画面の「会議を終了する」ボタンをクリックすると、予定の終了日時が更新されます。
「 予定メニュー」が「打合」の予定が対象です。
- 「会議を終了するボタン」で会議を終了した予定の場合、改善成果(短縮された会議時間)を表示します。
ポータルのカスタマイズ
- ポータルに、直近 1 週間の改善成果をグラフ表示します。
- 短縮された会議時間の予定ランキング
- 1 週間でどの程度会議を効率できたかの比較
スケジュールのカスタマイズ
Step 1: カスタマイズファイルの作成
-
テキストエディタを開いて、次のコードの内容をコピー&ペーストします。
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 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
/* * Garoon meeting efficiency customize(schedule) * Copyright (c) 2020 Cybozu * * Licensed under the MIT License * https://opensource.org/license/mit/ */ (function($) { 'use strict'; // カスタマイズ対象の予定メニュー const MEETING = '打合'; const SCHEDULE_DATASTORE_KEY = 'jp.co.cybozu.schedule.sample.efficiency'; garoon.events.on('schedule.event.detail.show', (event) => { const scheduleEvent = event.event; const start = scheduleEvent.start.dateTime; const end = scheduleEvent.end.dateTime; const scheduleStartTime = new Date(start); const scheduleEndTime = new Date(end); const now = new Date(); const datastore = garoon.schedule.event.datastore.get(SCHEDULE_DATASTORE_KEY); const isClicked = datastore.value.isClicked; /** * スケジュールの終了時刻を更新する関数 * ※更新時に、予定終了時刻・削減時間・効率(%)をdatastoreに格納する */ const finishMeeting = function(ev) { // 予定の詳細画面を開いてから「会議終了」ボタンが押されるまでに時間差があることを考慮し、 // 「会議終了」ボタンを押したときの時刻を終了時刻に設定する const date = new Date(); const actualEndTime = new Date(date.setMinutes(Math.ceil(date.getMinutes() / 5) * 5, 0)); const scheduledTime = scheduleEndTime - scheduleStartTime; const actualSpentTime = actualEndTime - scheduleStartTime; // datastoreに格納する値 const value = { value: { isClicked: true, end: end, reductionTime: Math.ceil((scheduleEndTime - actualEndTime) / (1000 * 60)), efficiency: Math.ceil((scheduledTime / actualSpentTime) * 100) } }; // スケジュール更新内容 const updateEvent = { end: {dateTime: actualEndTime} }; garoon.api('/api/v1/schedule/events/' + ev.id + '/datastore/' + SCHEDULE_DATASTORE_KEY, 'PUT', value).then((resp) => { return garoon.api('/api/v1/schedule/events/' + ev.id, 'PATCH', updateEvent); }).then((resp) => { location.reload(); }).catch((err) => { window.alert('Error!!'); console.error(err); }); }; /** * 効率化達成結果を表示する関数 */ const showResult = function() { const resultReportSpace = garoon.schedule.event.getHeaderSpaceElement(); const $resultReport = $('<STRONG>', { id: 'items', class: 'sat_color1_grn_kit', type: 'text', text: '会議効率化達成!(' + datastore.value.efficiency + '%) これによって会議が' + datastore.value.reductionTime + '分削減されました!' }); $resultReport.prependTo(resultReportSpace); }; /** * 会議終了ボタンを設置する関数 */ const setButton = function(ev) { const endButtonSpace = garoon.schedule.event.getHeaderSpaceElement(); const $endButton = $('<button>', { id: 'endBtn', class: 'button_main_sub_grn_kit', type: 'button', text: '会議を終了する' }).click(() => { // クリックした時に行いたい処理を登録 finishMeeting(ev); }); const $brElements = $('<br><br>'); $brElements.prependTo(endButtonSpace); $endButton.prependTo(endButtonSpace); }; // 予定メニューが MEETING に設定した値の予定のみ、カスタマイズを実行する if (scheduleEvent.eventMenu === MEETING) { // 効率化達成結果がカスタマイズ項目に登録されていたら、結果を画面に表示する if (isClicked) { showResult(); return; } // 現在時刻が打合日時の間の場合 if (scheduleStartTime <= now && now <= scheduleEndTime) { setButton(scheduleEvent); } } }); /** * スケジュール作成(再利用)時にdatastoreを初期化する */ garoon.events.on('schedule.event.create.submit', (event) => { const initialValue = { value: { isClicked: false, end: '', reductionTime: '', efficiency: '' } }; garoon.schedule.event.datastore.set(SCHEDULE_DATASTORE_KEY, initialValue); return event; }); })(jQuery.noConflict(true));
-
ファイルの拡張子「.js」、文字コードは「UTF-8(BOM なし)」で、ファイルに名前を付けて保存します。
この記事では、ファイル名を schedule-customize.js としています。
Step 2: カスタマイズの適用
カスタマイズグループを作成し、スケジュールのカスタマイズファイルを適用します。
手順の詳細は「
スケジュールのカスタマイズ設定
」を参照してください。
- Garoon メニュー右の歯車アイコンをクリックし、[Garoon システム管理]を選択します。
- [各アプリケーションの管理]タブを選択し、[スケジュール]を選択します。
- [JavaScript/CSS によるカスタマイズ]を選択します。
- [カスタマイズグループを追加する]をクリックします。
- 次の内容を入力します。入力が終わったら、[追加する]ボタンをクリックします。
項目 設定する値 カスタマイズ 「適用する」を選択します。 カスタマイズグループ名 任意の値を入力します。この記事では「会議効率化カスタマイズ」としています。 適用対象 カスタマイズを適用するユーザーやグループを選択します。 JavaScript カスタマイズ 以下の順で、URL およびファイルを指定します。 - https://js.cybozu.com/jquery/3.1.1/jquery.min.js
- schedule-customize.js(カスタマイズファイル)
CSS カスタマイズ grn_kit.css を指定します
grn_kit.css(Garoon html/css/image-Kit for Customize)の入手方法-
https://github.com/garoon/css-for-customize
にアクセスします。
- [Clone or download] ボタンをクリックして、「Download ZIP」を選択します。
- ダウンロードした zip ファイルを解凍します。
- 解凍したファイルの「css」フォルダー以下の「grn_kit.css」を利用します。
コードの解説
イベントを使って、予定の詳細画面を開いたときに実行する処理を定義する
16 行目〜110 行目では、イベントを使って、予定の詳細画面を開いたときに実行する処理を記述しています。
|
|
略
|
|
Garoon のイベントは、garoon.events.on(処理を行うタイミングのイベントタイプ, 実行する処理の関数);
という形で指定します。
今回、処理を行うタイミングは「予定の詳細画面を開いたとき」なので「schedule.event.detail.show」というイベントタイプを指定します。
2 番目の引数には、実行する処理を関数の形で指定します(function(event) { ... }
の部分)
「会議を終了する」ボタンをクリックしたときに、Garoon REST API を実行する
30 行目〜58 行目の finishMeeting 関数は、「会議を終了する」ボタンをクリックしたときに呼び出されます。
|
|
finishMeeting 関数では、2 つの Garoon REST API を実行しています。
-
予定のカスタム項目(Schedule datastore)を更新する
予定のカスタム項目(Schedule datastore)は、スケジュールのカスタマイズにおいて複数のデータを保持できる機能です。
このカスタマイズでは、「ボタンをクリックしたか」「ボタンをクリックしたときの時刻」「削減できた時間」「効率(%)」を、カスタム項目(Schedule datastore)として保存しています。 - 予定を更新する
Garoon 上で Garoon REST API を実行するときは、
Garoon REST APIリクエストを送信する API を利用できます(50 行目、51 行目)。
この API は、garoon.api(REST API の URL パス, メソッド, APIに渡すデータ)
という形で指定します。
これは Promise を使う方法です。Promise については、
kintone における Promise の書き方の基本
を参照してください。
たとえば、
予定を更新する API を実行するときに指定する内容は、次のようになります。
- REST API の URL パス: /api/v1/schedule/events/更新する予定の ID
- メソッド:PATCH
- API に渡すデータ:更新したい内容
REST API が正常に実行できた後の処理は、.then の引数で指定した関数に記述します(52 行目〜52 行目)の .then(function(resp) { ... })
の部分)。
今回は、画面を再読み込みしています。
動作確認
- 予定を登録します。
- 予定メニュー:「打合」を選択します。
- 日時:現在時刻が開始日時と終了日時の間になるように設定します。
例:現在時刻が 10:30 なら、日時を 10:00 〜 11:00 に設定する。
- 登録した予定の「会議を終了する」ボタンをクリックして、会議を終了します。
- 予定の終了日時が更新され、短縮時間や効率の情報が表示されたことを確認します。
ポータルのカスタマイズ
Step1: カスタマイズファイルの作成
-
テキストエディタを開いて、次のコードの内容をコピー&ペーストします。
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 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397
/* * Garoon meeting efficiency customize(portlet) * Copyright (c) 2020 Cybozu * * Licensed under the MIT License * https://opensource.org/license/mit/ */ (function($) { 'use strict'; // 抽出対象とする予定メニュー const SCHEDULE_MENU = '打合'; // グラフ表示する順位数 const TARGET_RANK = 6; // グラフの幅 const WIDTH = 700; // グラフの高さ const HEIGHT = 550; const colors = { pink: 'rgba(255, 99, 132, 0.2)', blue: 'rgba(54, 162, 235, 0.2)', yellow: 'rgba(255, 206, 86, 0.2)', green: 'rgba(75, 192, 192, 0.2)', purple: 'rgba(153, 102, 255, 0.2)', orange: 'rgba(255, 159, 64, 0.2)', gray: 'rgba(120, 126, 126, 0.4)', red: 'rgba(255, 2, 13, 0.8)' }; const borderColors = { pink: 'rgba(255,99,132,1)', blue: 'rgba(54, 162, 235, 1)', yellow: 'rgba(255, 206, 86, 1)', green: 'rgba(75, 192, 192, 1)', purple: 'rgba(153, 102, 255, 1)', orange: 'rgba(255, 159, 64, 1)' }; const SCHEDULE_DATASTORE_KEY = 'jp.co.cybozu.schedule.sample.efficiency'; /** * ログインユーザの一週間の予定を取得する関数 * @returns {object} スケジュールデータの取得結果 */ const getSchedules = function() { const m = moment(); const today = m.format('YYYY-MM-DD'); const startDate = m.add(-6, 'day').format('YYYY-MM-DD'); const path = '/api/v1/schedule/events'; const params = { orderBy: 'start asc', rangeStart: startDate + 'T00:00:00+09:00', rangeEnd: today + 'T23:59:59+09:00' }; return garoon.api(path, 'GET', params).then((resp) => { return resp.data.events; }); }; /** * スケジュールに紐づくdatastoreを取得する関数 * @param {number} eventId 加工対象のスケジュールID * @returns {object} datastore取得結果 */ const getDatastore = function(eventId) { const url = '/api/v1/schedule/events/' + eventId + '/datastore/' + SCHEDULE_DATASTORE_KEY; return garoon.api(url, 'GET', {}).then((resp) => { return { id: eventId, datastore: resp.data }; }); }; /** * 複数のスケジュールデータを水平棒グラフ用の配列に加工する関数 * @param {object} events 加工対象のスケジュールデータ * @returns {array} 削減時間ランキング上位のデータ */ const makeHorizonBarData = function(events) { const horizonBarDataArray = []; events.forEach((event) => { const horizonBarDataObj = {}; if (typeof event.datastore === 'undefined' || !event.datastore.isClicked) { return; } horizonBarDataObj.subject = event.subject; horizonBarDataObj.reductionTime = event.datastore.reductionTime; horizonBarDataArray.push(horizonBarDataObj); }); // 「削減時間」を降順にソート horizonBarDataArray.sort((a, b) => { if (a.reductionTime < b.reductionTime) { return 1; } return -1; }); // グラフ表示する配列のみを返す return horizonBarDataArray.slice(0, TARGET_RANK); }; /** * 水平棒グラフを作成する関数 * @param {array} horizonBarData グラフ表示用に加工されたデータ */ const createHorizonBarGraph = function(horizonBarData) { const subjectArray = []; const reductionTimeArray = []; const ctx = document.getElementById('horizonBarChart').getContext('2d'); let i; // グラフサイズを指定 ctx.canvas.width = WIDTH; ctx.canvas.height = HEIGHT; // 配列[horizonBarData]をプロパティ別に分割する(Chart.js対応) for (i = 0; i < horizonBarData.length; i++) { subjectArray.push(horizonBarData[i].subject); reductionTimeArray.push(horizonBarData[i].reductionTime); } new Chart(ctx, { type: 'horizontalBar', data: { labels: subjectArray, datasets: [ { label: '削減時間(分)', data: reductionTimeArray, backgroundColor: [ colors.pink, colors.blue, colors.yellow, colors.green, colors.purple, colors.orange ], borderColor: [ borderColors.pink, borderColors.blue, borderColors.yellow, borderColors.green, borderColors.purple, borderColors.orange, ], borderWidth: 1 } ] }, options: { title: { display: true, position: 'top', fontSize: 25, fontColor: 'black', fontStyle: 'bold', padding: 10, text: '今週のランキング' }, responsive: false, maintainAspectRatio: false, scales: { // X軸のオプション xAxes: [ { ticks: { fontColor: 'black', beginAtZero: true, max: 40, stepSize: 5 } } ] } } }); }; /** * スケジュールデータを2軸棒×折れ線グラフ用の配列に加工する関数 * @param {object} events 加工対象のスケジュールデータ * @returns {array} 加工済データ */ const makeMixedChartData = function(events) { const mixedChartArray = []; const mixedChartObj = {}; // 現在日時から一週間分の配列[mixedChartArray]を作成する(初期化) for (let j = 0; j < 7; j++) { const labelDate = moment().add(-j, 'day').format('MM-DD'); mixedChartObj.date = labelDate; mixedChartObj.scheduleMeetingTime = 0; mixedChartObj.actualMeetingTime = 0; mixedChartArray.push(mixedChartObj); } events.forEach((event) => { const start = event.start.dateTime; const end = event.end.dateTime; const scheduleStartTime = new Date(start); const actualEndTime = new Date(end).setSeconds(0); const datastore = event.datastore; const keyDate = moment(start).format('MM-DD'); const actualSpentTime = (actualEndTime - scheduleStartTime) / (1000 * 60); let scheduledTime = 0; // 打合日時をキーとし、各プロパティに値を合算する if (typeof event.datastore === 'undefined' || !event.datastore.isClicked) { scheduledTime = actualSpentTime; } else { const scheduleEndTime = new Date(datastore.end || actualEndTime); scheduledTime = (scheduleEndTime - scheduleStartTime) / (1000 * 60); } mixedChartArray.forEach((obj) => { if (obj.date === keyDate) { obj.scheduleMeetingTime += scheduledTime; obj.actualMeetingTime += actualSpentTime; } }); }); return mixedChartArray; }; /** * 2軸棒✕折れ線グラフを作成する関数 * @param {array} mixedChartData グラフ表示用に加工されたデータ */ const createMixedChart = function(mixedChartData) { const dateLabelArray = []; const scheduleMeetingTimeArray = []; const actualMeetingTimeArray = []; const efficiencyArray = []; const ctx = document.getElementById('mixedChart').getContext('2d'); // グラフサイズを指定 ctx.canvas.width = WIDTH; ctx.canvas.height = HEIGHT; // 配列[mixedChartData]をプロパティ別に分割する(Chart.js対応) mixedChartData.forEach((data) => { const efficiency = Math.ceil((data.scheduleMeetingTime / data.actualMeetingTime) * 100); dateLabelArray.unshift(data.date); scheduleMeetingTimeArray.unshift(data.scheduleMeetingTime); actualMeetingTimeArray.unshift(data.actualMeetingTime); efficiencyArray.unshift(efficiency); }); new Chart(ctx, { type: 'bar', data: { labels: dateLabelArray, datasets: [ { type: 'bar', label: '予定所要時間', data: scheduleMeetingTimeArray, backgroundColor: colors.blue, borderColor: borderColors.blue, borderWidth: 1, yAxisID: 'y1' }, { type: 'bar', label: '実所要時間', data: actualMeetingTimeArray, backgroundColor: colors.green, borderColor: borderColors.blue, borderWidth: 1.2, yAxisID: 'y1' }, { type: 'line', label: '会議効率', data: efficiencyArray, backgroundColor: colors.pink, borderColor: borderColors.pink, borderWidth: 1.2, pointBackgroundColor: colors.pink, pointStyle: 'circle', radius: 4, pointHoverBackgroundColor: colors.pink, pointHoverRadius: 7, pointHoverBorderColor: colors.red, pointHoverBorderWidth: 2, lineTension: 0, fill: false, yAxisID: 'y2' } ] }, options: { title: { display: true, position: 'top', fontSize: 25, fontColor: 'black', fontStyle: 'bold', padding: 10, text: '比較グラフ' }, legend: { display: true }, tooltips: { enabled: true }, scales: { // Y軸のオプション yAxes: [ { id: 'y1', scaleLabel: { fontColor: 'black' }, gridLines: { color: colors.gray, zeroLineColor: 'black' }, ticks: { fontColor: 'black', beginAtZero: true, suggestedMax: 200, stepSize: 50 } }, { id: 'y2', position: 'right', autoSkip: true, gridLines: { display: false }, ticks: { fontColor: 'black', beginAtZero: true, suggestedMax: 200, stepSize: 20, callback: function callback(val) { return val + '%'; } } } ], // X軸のオプション xAxes: [ { scaleLabel: { fontColor: 'black', display: true, labelString: '日付' }, gridLines: { color: colors.gray, zeroLineColor: 'black' }, ticks: { fontColor: 'black' } } ] }, responsive: false, maintainAspectRatio: false } }); }; // 画面読み込み完了時に起動する処理 $(document).ready(() => { let targetMeeting; getSchedules().then((events) => { // REST APIは予定メニューによる絞り込みデータ取得に対応していないため、抽出後に判定を行う targetMeeting = events.filter((event) => { return event.eventMenu === SCHEDULE_MENU; }); return garoon.Promise.all(targetMeeting.map((event) => { return getDatastore(event.id); })); }).then((datastores) => { targetMeeting.forEach((event) => { const datastore = datastores.filter((ev) => { return ev.id === event.id; })[0].datastore; if (datastore.value.isClicked) { event.datastore = datastore.value; } }); const horizonBarData = makeHorizonBarData(targetMeeting); const mixedChartData = makeMixedChartData(targetMeeting); createHorizonBarGraph(horizonBarData); createMixedChart(mixedChartData); }).catch((e) => { window.alert('Error!!'); console.error(e); }); }); })(jQuery.noConflict(true));
-
ファイルの拡張子「.js」、文字コードは「UTF-8(BOM なし)」で、ファイルに名前を付けて保存します。
この記事では、ファイル名を portlet-customize.js としています。
Step 2: ポートレットの追加
手順の詳細は「
HTMLポートレットを追加する
」を参照してください。
- Garoon メニュー右の歯車アイコンをクリックし、[Garoon システム管理]を選択します。
- [各アプリケーションの管理]タブを選択し、[ポータル]を選択します。
- [HTML ポートレット]をクリックし、[HTML ポートレットを追加する]をクリックします。
- 次の内容を入力します。入力が終わったら、[追加する]ボタンをクリックします。
項目 設定する値 ポートレット名 任意の値を入力します。この記事では「会議効率化ポートレット」とします。 グループ 任意のポートレットグループを選択します。 Myポータル Myポータルで利用する場合はチェックを入れます。 ポートレットの内容 ポートレットの HTML を貼り付けます。
ポートレットの HTML
|
|
Step3: カスタマイズの適用
引き続き、追加したポートレットにカスタマイズファイルを適用します。手順の詳細は「
ポータルのカスタマイズ
」を参照してください。
- 前手順で追加したポートレットを開きます。
- [JavaScript/CSS によるカスタマイズ]を選択します。
- [カスタマイズグループを追加する]をクリックします。
- 次の内容を入力します。入力が終わったら、[追加する]ボタンをクリックします。
項目 設定する値 カスタマイズ 「適用する」を選択します。 適用対象 カスタマイズを適用するユーザーやグループを選択します。 JavaScript カスタマイズ 以下の順で、URL およびファイルを指定します。 - https://js.cybozu.com/jquery/3.1.1/jquery.min.js
- https://js.cybozu.com/momentjs/2.15.1/moment-with-locales.min.js
- https://js.cybozu.com/chartjs/v2.2.2/Chart.min.js
- portlet-customize.js(カスタマイズファイル)
CSS カスタマイズ 以下の順で、URL およびファイルを指定します。 - https://js.cybozu.com/font-awesome/v5.5.0/css/fontawesome-all.min.css
- portlet-customize.css
カスタマイズ CSS の内容をテキストエディタにコピー&ペーストし、ファイルの拡張子「.css」、文字コードは「UTF-8(BOMなし)」で保存したものです。
カスタマイズ CSS
|
|
Step4: ポータルに配置
コードの解説
コードは長いですが、ほんどがグラフに必要なデータを生成している処理です。
このコードでポイントとなる 2 点を説明します。
Garoon REST API を使って、条件に一致する予定を取得する
45 行目〜58 行目の getSchedules 関数は、ポータル画面の読み込みが完了した時に呼び出されます。
|
|
ポータルからスケジュールの予定を取得するには、Garoon REST API の
複数の予定を取得する API を実行します。
またスケジュールのカスタマイズと同じく、Garoon 上で実行するので、
Garoon REST API リクエストを送信する API を使っています。
- REST API の URL パス: /api/v1/schedule/events
- メソッド:GET
- API に渡すデータ:絞り込み条件
orderBy は並び順、rangeStart / rangeEnd は取得する予定の日時の範囲です。
予定の取得に成功したら、取得した予定を元にグラフを生成する
さきほどの getSchedules 関数は、ポータル画面の読み込みが完了した時に呼び出されます(371 行目〜395 行目)。
|
|
getSchedules 関数では Promise が返却されます。
正常に実行できたら .then の引数で指定した関数の処理を実行し、エラーが発生したら .catch の引数で指定した関数の処理を実行します。
動作確認
- スケジュールのカスタマイズ - 動作確認 の手順にしたがって、会議が終了した予定を作成します。
- ポートレットを配置したポータルを開きます。
- ポータルにグラフが表示されていることを確認します。
おわりに
cybozu developer network では、さまざまな Garoon のカスタマイズ Tips を公開しています。Tips に掲載しているコードで「どんなことをしているか?」を確認しながら、ぜひカスタマイズに挑戦してみてください。
利用している Garoon API
- Garoon JavaScript API
- Garoon REST API
利用しているライブラリ
- jQuery v3.1.1,
ドキュメント
HTML 要素の生成や操作を楽に扱うことができるライブラリです。
スケジュールのカスタマイズでは、改善成果を表示する HTML 要素の作成や予定の詳細画面への挿入などに利用しています。 - Moment.js v2.15.1,
ドキュメント
日付の加算などの日付操作やフォーマットを楽に扱うことができるライブラリです。
ポータルのカスタマイズで、直近 7 日の予定を取得する処理で現在時刻から 1 週間前の日付の計算などに利用しています。 - Chart.js v2.2.2,
ドキュメント
グラフを生成や表示するライブラリです。
ポータルのカスタマイズで、ポートレットに表示する、直近 1 週間の改善成果を棒グラフや複合グラフの生成や表示に利用しています。 - Font Awesome v5.5.0,
ドキュメント
Web アイコンフォントを表示するスタイルシートのライブラリです。
ポータルのカスタマイズで、ポートレットのタイトル部分のチェックボックスマークに利用しています。 - Garoon html/css/image-Kit for Customize,
ドキュメント
ボタンなどの UI パーツを Garoon の見た目に調和させるスタイルシートのライブラリです。
スケジュールのカスタマイズで、予定の詳細画面に追加した「会議を終了する」ボタンに利用しています。
この Tips は、2022 年 1 月版 Garoon で動作を確認しています。