ガルーンポータル活用 Tips #2 「社員紹介ポータル」

目次

はじめに

今回は、大企業向けグループウェア「サイボウズ Garoon」のポータル活用企画の第二弾として、「社員紹介ポータル」を作成していきたいと思います。

完成図

キャプチャの左下のポートレットが今回作成していくポートレットです。
特定の期間内に入社した人を紹介するポータルとして活用していきます。
kintone アプリからデータを取得し、ランダムに決められた数の社員を紹介する処理を加えるので、毎回違った社員が表示され多くの社員紹介を目にできます。

基本的には前回の記事の ガルーンポータル活用 Tips #1 「行き先案内板」 と同じテクニックで kintone のレコードを取得し、HTML ポートレットに表示していきます。

kintone アプリ(自己紹介アプリ)の準備

Garoon のポータルからデータを参照させるためのアプリを作成していきます。
kintone アプリのフィールドは以下のように配置していきます。

フィールド名 フィールドコード フィールドタイプ
氏名 name 文字列(1行)
入社日 hire_date 日付
写真 attachment 添付ファイル
配属部署 department 文字列(1行)
座右の銘 motto 文字列(1行)
自己紹介 self_introduction 文字列(複数行)

認証は基本的にログインユーザーの権限を使います。
社員紹介のため問題ないと思いますが、この kintone アプリのレコードの閲覧権限はすべてのユーザーで閲覧できるように設定しておくことをおすすめします。

リソースの準備

第一弾と同様に、静的ファイル置き場として Garoon の「ファイル管理」を使います。
今回使う画像ファイルおよび JavaScript、CSS ファイル一式は以下です。

garoon-kin2.zip

では、ファイル管理を使ってリソースの準備をしていきたいと思います。

  1. 分りやすいようにポートレット用にフォルダーを作成します。今回は「新入社員紹介用」とします。

  2. 画像ファイル「icn_link.png」および javascript ファイル「jQuery.jtruncsubstr-1.0rc.js」を保存します。

  3. 「icn_link.png」「jQuery.jtruncsubstr-1.0rc.js」のダウンロードリンク内に含まれる hid(フォルダー ID)、fid(ファイル ID)を確認しておきます。 これは、後程ポートレット作成時に使いますのでメモをしておきましょう。

    例:URL が https://sample.cybozu.com/g/cabinet/view.csp?hid=<hid>&fid=<fid> のとき、hid と fid は以下になります。

    • hid:<hid>
    • fid:<fid>
  4. CSS サンプルコード を参考に、レイアウト調整用ファイル「main.css」を作成します。
    このとき、3 でコピーした「icn_link.png」のリンクを CSS サンプルコード の 223 行目に記載します。

  5. 「garoon-kin-integration.js」の次の項目を書き換えます。

    • 46 行目 APP_ID を作成した「自己紹介アプリ」のアプリ番号に書き換えます。
    • 47 行目 DATE_NUM で、何日前以降に入社した人を抽出するかを設定します。

    JavaScript ファイルの内容は、 JavaScript サンプルコード を参照してください。

    ポイント

    • 関数 generateRandomx にて乱数を生成し、毎回ランダムに表示する順番をかえています。
    • 関数 textTrimer にて自己紹介が 100 文字以上の場合はトリミングして末尾に「…」をつけています。
  6. 「main.css」および、「garoon-kin-integration.js」をファイル管理に保存します。
    このとき、3 と同様にそれぞれのファイルのダウンロードリンクをのちほどポートレット作成時に使うのでメモしておいてください。

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
 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
/*
 * Garoon Portal sample program
 * Copyright (c) 2016 Cybozu
 *
 * Licensed under the MIT License
 * https://opensource.org/license/mit/
 */
/*
 *
 * Title: Calomama Graph
 * Last Modified: 2015-05-19
 * Description: Pages Style
 *
 */
/* =========== INDEX LIST ============
  1: RESET
  2: COMMON
  3: LAYOUT
  4: MODULE
====================================== */
/* ===================================
  1: RESET
====================================== */
/*@import "normalize";*/
/* ===================================
  2: COMMON
====================================== */
.template-news h1, .template-news h2, .template-news h3, .template-news h4, .template-news h5, .template-news h6 {
  margin: 0;
  font-weight: normal;
  line-height: 1.5;
}
.template-news p,
.template-news ul,
.template-news ol,
.template-news dl {
  list-style: none;
  margin: 0;
  line-height: 1.4;
  font-size: 11px;
  font-size: 1.1rem;
}
.template-news img {
  line-height: 1;
  vertical-align: top;
}
.template-news table {
  width: 100%;
  border-collapse: collapse;
}
.template-news th,
.template-news td {
  text-align: left;
}

/* ===================================
  4: MODULE
====================================== */
.template-news {
  background: #f2f2f2;
}
.template-news-footer {
  background: #666;
  padding: 30px;
  color: #fff;
}
.template-news-footer p {
  font-size: 9px;
  font-size: 0.9rem;
}
.template-news-footer .footer-nav-wrap {
  display: table;
  width: 100%;
}
.template-news-footer .footer-nav-wrap .nav-box {
  width: 33.33333333%;
  display: table-cell;
  box-sizing: border-box;
  padding: 20px 20px 0 0;
}
.template-news-footer .footer-nav-wrap .nav-box li {
  margin-bottom: 7px;
}
.template-news-footer .footer-nav-wrap .nav-box a {
  display: inline-block;
  padding-left: 15px;
  color: #fff;
  font-size: 9px;
  font-size: 0.9rem;
}
.template-news-footer .footer-nav-wrap .nav-box a:before {
  content: "";
  display: inline-block;
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 4px 0 4px 6px;
  border-color: transparent transparent transparent #ffffff;
  margin-right: 5px;
  margin-left: -15px;
}
.template-news-footer .footer-nav-wrap .nav-box a:hover {
  color: #ffff66;
}
.template-news-contents {
  padding: 30px;
  background: #f2f2f2;
}
.template-news-box {
  margin-bottom: 20px;
  padding: 20px 30px;
  background: #fff;
}
.template-news-box h2 {
  margin: 0 0 15px;
  padding: 0;
  font-weight: bold;
  font-size: 18px;
  font-size: 1.8rem;
}
.template-news-box h2 .icon {
  display: inline-block;
  margin-right: 5px;
  vertical-align: text-top;
}
.template-news-box.box-red {
  border-top: 3px solid #ff6666;
}
.template-news-box.box-yellow {
  border-top: 3px solid #ebb218;
}
.template-news-box.box-green {
  border-top: 3px solid #91cd5c;
}
.template-news-box.box-purple {
  border-top: 3px solid #c684ca;
}
.template-news-box.box-blue {
  border-top: 3px solid #61aee4;
}
.template-news-box.box-turquoise {
  border-top: 3px solid #25d3c4;
}
.template-news-box .box-content {
  margin-bottom: 20px;
  /*
        .truncate_more {
          @include font-size(10);
          line-height: 1.5;
        }
  */
}
.template-news-box .box-content:after {
  content: '';
  display: block;
  clear: both;
}
.template-news-box .box-content h3 {
  margin-bottom: 12px;
  font-size: 13px;
  font-size: 1.3rem;
  font-weight: bold;
}
.template-news-box .box-content p {
  font-size: 10px;
  font-size: 1rem;
  line-height: 1.5;
}
.template-news-box.box-layout-left .box-content > img {
  float: left;
  margin: 0 20px 10px 0;
}
.template-news-box.box-layout-right .box-content > img {
  float: right;
  margin: 0 0 10px 20px;
}
.template-news .btn a {
  display: block;
  border: 1px solid #e0e0e0;
  padding: 15px;
  color: #0a62ad;
  text-decoration: none;
}
.template-news .btn a:hover {
  background: #fbfbfb;
  color: #cc3300;
}
.template-news .btn.btn-more {
  padding-top: 10px;
  clear: both;
}
.template-news .btn.btn-more a {
  text-align: center;
}
.template-news .btn.btn-more a:after {
  content: "";
  display: inline-block;
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 10px 6px 0 6px;
  border-color: #cccccc transparent transparent transparent;
  margin-left: 5px;
}
.template-news .btn.btn-more-close a:after {
  content: "";
  display: inline-block;
  width: 0;
  height: 0;
  border-style: solid;
  border-width: 0 6px 10px 6px;
  border-color: transparent transparent #cccccc transparent;
  margin-left: 5px;
}
.template-news .btn.btn-link {
  clear: both;
}
.template-news .btn.btn-link a {
  text-align: right;
}
.template-news .btn.btn-link a:after {
  content: url(../img/icn_link.png);
  content: url("/g/cabinet/download.csp/-/icn_link.png?fid=XX&ticket=&hid=XX&.png");
  width: 17px;
  height: 15px;
  display: inline-block;
  margin-left: 5px;
}

JavaScript サンプルコード

  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
/*
* Garoon Portal sample program
* Copyright (c) 2016 Cybozu
*
* Licensed under the MIT License
* https://opensource.org/license/mit/
*/

(function($) {
  'use strict';
  // calculate N month age.
  function calcOneM(n) {
    const dt = new Date();
    dt.setDate(dt.getDate() - parseInt(n, 10));
    const year = dt.getFullYear();
    const month = (dt.getMonth() < 10) ? '0' + (dt.getMonth() + 1) : dt.getMonth() + 1;
    const day = dt.getDate();
    return (year + '-' + month + '-' + day);
  }
  // generate randum numbers.
  function generateRandomx(count) {
    const generated = [];
    let generatedCount = generated.length;
    for (let i = 0; i < count; i++) {
      let candidate = Math.floor(Math.random() * count);
      for (let j = 0; j < generatedCount; j++) {
        if (candidate === generated[j]) {
          candidate = Math.floor(Math.random() * count);
          j = -1;
        }
      }
      generated[i] = candidate;
      generatedCount++;
    }
    return generated;
  }
  // 100 characters or more, the shortening.
  function textTrimer(tx, limit) {
    const afterTxt = '...';
    const textTrim = tx.substr(0, limit);
    if (limit < tx.length) {
      return textTrim + afterTxt;
    }
    return tx;
  }
  const APP_ID = 15;
  const DATE_NUM = 60;
  const nMonthAgo = '"' + calcOneM(DATE_NUM) + '"';
  $.ajax({
    type: 'GET',
    url: '/k/v1/records.json',
    data: {app: APP_ID, query: 'hire_date > ' + nMonthAgo},
    cache: false,
    dataType: 'json'
  }).done((resp) => {
    const records = resp.records;
    const fileKeyArray = [];
    let contentsEle;
    let innerDiv;
    let count = 0;
    const randomAray = generateRandomx(records.length);
    const forNum = (records.length < 5) ? records.length : 5;
    if (records.length === 0) {
      $('<div>').html('<B>' + calcOneM(DATE_NUM) + ' 以降に入社した方はいません</B>')
        .prependTo($('.template-news-contents'));
      $('.template-news-box').append(
        $('<p>', {
          text: 'Not Found'
        })
      );
      return;
    }
    for (let i = 0; i < forNum; i++) {
      contentsEle = $('#contents-' + i);
      contentsEle.append($('<h2></h2>', {
        text: records[randomAray[i]].name.value +
                  '(' + records[randomAray[i]].department.value + ')'
      }));
      innerDiv = $('<div></div>', {
        class: 'box-content'
      });
      innerDiv.append($('<img>', {
        id: 'capture-' + i,
        width: '120',
        height: '120'
      }));
      innerDiv.append($('<h3></h3>', {
        text: '座右の銘 : ' + records[randomAray[i]].motto.value
      }));
      innerDiv.append($('<p></p>', {
        text: textTrimer(records[randomAray[i]].self_introduction.value, 100)
      }));
      innerDiv.append($('<p></p>', {
        class: 'btn btn-link'
      }).append($('<a></a>', {
        href: '/k/' + APP_ID + '/show#record=' + records[randomAray[i]].$id.value,
        text: '自己紹介詳細へ'
      })));
      contentsEle.append(innerDiv);
      fileKeyArray.push(records[randomAray[i]].attachment.value[0].fileKey);
    }
    if (forNum < 5) {
      for (let j = forNum; j <= 5; j++) {
        $('#contents-' + j).append(
          $('<p>', {
            text: 'Not Found'
          })
        );
      }
    }
    // generate file objects.
    function loopFetchFile(key) {
      return new Promise((resolve) => {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', '/k/v1/file.json?fileKey=' + key, true);
        xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
        xhr.responseType = 'blob';
        xhr.onload = function() {
          const blob = xhr.response;
          const url = window.URL || window.webkitURL;
          const imageObj = url.createObjectURL(blob);
          $('#capture-' + count).attr('src', imageObj);
          if (count < fileKeyArray.length - 1) {
            count++;
            return loopFetchFile(fileKeyArray[count]);
          }
        };
        xhr.send();
      });
    }
    loopFetchFile(fileKeyArray[count]);
  }); // end of done
})(jQuery.noConflict(true)); // jquery

Garoon ポートレットの準備

今回使うポートレットを作成していきます。
22,24,25 行目の「main.css」「jQuery.jtruncsubstr-1.0rc.js」「garoon-kin-integration.js」のダウンロードリンクを リソースの準備 で生成したリンクにそれぞれ書き換えます。

 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
<!--
* Garoon Portal sample program
* Copyright (c) 2016 Cybozu
*
* Licensed under the MIT License
* https://opensource.org/license/mit/
-->
<div class="template-news">
  <div class="template-news-contents">
    <div id="contents-0" class="template-news-box box-yellow box-layout-left">
    </div>
    <div id="contents-1" class="template-news-box box-green box-layout-left">
    </div>
    <div id="contents-2" class="template-news-box box-purple box-layout-left">
    </div>
    <div id="contents-3" class="template-news-box box-blue box-layout-left">
    </div>
    <div id="contents-4" class="template-news-box box-turquoise box-layout-left">
    </div>
  </div>
</div>
<link rel="stylesheet" href="/g/cabinet/download.csp/-/main.css?fid=12&ticket=&hid=3&.css">
<script type="text/javascript" src="https://js.cybozu.com/jquery/3.4.1/jquery.min.js"></script>
<script type="text/javascript" src="/g/cabinet/download.csp/-/jquery.jtruncsubstr-1.0rc.js?fid=14&ticket=&hid=3&.js"></script>
<script type="text/javascript" src="/g/cabinet/download.csp/-/garoon-kin-integration.js?fid=13&ticket=&hid=3&.js"></script>"></script>

ポートレットを作成したらポータルに配置しましょう。

動作確認

kintone にいくつかのレコードを登録します。

javascript で 50 日前以降に入社した社員を、Garoon ポートレットで絞り込んで表示するように指定します。(実行日:2016/07/15)
Garoon を開いて設置したポータルを確認します。

おわりに

無事に社員紹介ポートレットができました!

今回は、Garoon ポートレットを kintone と連携させて動的に社員紹介を掲示するポートレットを作成しました。
応用すれば、他にもいろいろなパターンで動的なポートレットを作成できると思うのでぜひチャレンジしてみてください!

ガルーンポータル活用 Tips

更新履歴

  • 2020/02/19
    jQuery の追加手順および jQuery.noConflict(true) を使うようにコードを修正