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モバイル)
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で動作を確認しています。