ガルーンポータル活用 Tips #7 情シスへの問い合わせを減らすポータル

目次

はじめに

中堅・大規模組織向けグループウェア「サイボウズ Garoon」のポータル活用企画第 7 弾。
今回は、情シスへの問い合わせを減らすようなポータルの作り方をご提案します。

こんなときに便利です

  • 情シスへの問い合わせが減ることで、他の改善業務などに集中ことができる。
  • すぐ問い合わせるのではなく、自分達同志でシェアしたり情報収集したりする習慣をつけることで、ユーザーの自立を促進する。

完成イメージ

このポータルを構成する以下の 2 つのポートレットの作成手順を紹介します。

  • GaroonTips ポートレット:掲示板のフォローから最新のコメントをランダムで表示しています。
  • <おまけ>あなたのブラウザーポートレット:HTML ポートレットに現在のブラウザー情報を表示します。
    Chromium 版 Edge を使っている場合、Chrome と表示されます。

ポイント

  • 今回は Garoon の掲示板からの情報取得ですが、社内システムの FAQ のような kintone アプリもあれば、一緒に表示してみるとよいでしょう。ポートレットの内容に以下の記述をするだけで、ポートレットに埋め込むことができます。
1
2
<!-- sampleとAPPIDを環境に応じて書き換える -->
<iframe width="1000" height="300" frameborder="0" src="https://sample.cybozu.com/k/APPID/"></iframe>

GaroonTipsポートレットの作成

データ取得元の準備

このポートレット用に以下のような掲示板を 1 つ作成し、コメント欄にユーザーが自由に書き込めるようにします。Garoon の Tips を画像付きで投稿すれば、コメントと画像の両方が表示されます。

使いたい掲示板のタイトルをクリックした際、URL 内に含まれる aid(掲示 ID)を確認しておきます。
これは、掲示板情報の取得プログラムの修正時に使います。

たとえば URL が https://sample.cybozu.com/g/bulletin/view.csp?cid=<cid>&aid=<aid> の場合、aid は <aid> の部分です。

リソースの準備

今回使うライブラリファイル(tablecloth.js)は以下からダウンロードできます。

garoon-portal7.zip

ファイルの説明

ファイル名 説明 修正が必要か 配置先
tablecloth.js マウスオーバー時の動きを設定するプログラム 不要 ファイル管理

Garoon の「ファイル管理」を使って次の手順でリソースの準備をしていきます。

  1. カスタマイズファイルとして、 JavaScript サンプルコード を参考に「Garoontips.js」を作成します(文字コードは「UTF-8」で保存してください)

  2. CSS サンプルコード を参考に「main.css」を作成します(文字コードは「UTF-8」で保存してください)

  3. Garoon の「ファイル管理」で今回のポートレット用にフォルダーを作成します。

  4. フォルダーを選択した状態で「ファイルを追加する」をクリックし、次の 3 つのファイルを追加します。

    • main.css
    • garoon_tips.js(修正済み)
    • tablecloth.js
  5. ファイルのタイトルをクリックした際、URL 内に含まれる hid(フォルダー ID)、fid(ファイル ID)を確認しておきます。
    これは、ポートレット作成時に使います。

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

    • hid:<hid>
    • fid:<fid>

JavaScript サンプルコード

15 行目を、「データ取得元の準備」で確認した aid に書き換えてください。
このコードは、書き込まれたフォローの中から、ランダムで 5 件を表示するプログラムです。表示件数は 16 行目で変更できます。

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

(function($) {
  'use strict';

  // ---- settings ----//{{{

  // article id (aid)
  const TOPIC_ID = 1;
  const DISPLAY_COUNT = 5;

  // function to escape html
  function escapeHtml(str) {
    if (!str) {
      return '';
    }
    return str
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#39;');
  }

  // ---- settings ----//}}}

  // generate random 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;
  }

  // ---- xml settings ----//{{{

  // function to make XML Header for Garoon API
  // arg1:services:service type (base,bulletin)
  // arg2:action:name of API
  // return:XML header string
  function makeXMLHeader(services, action) {
    let xmlns;
    switch (services) {
      case 'base':
        xmlns = 'base_services="http://wsdl.cybozu.co.jp/base/2008"';
        break;
      case 'bulletin':
        xmlns = 'workflow_services="http://wsdl.cybozu.co.jp/bulletin/2008"';
        break;
      default:
        alert('Can not select services');
        return;
    }

    const xmlHeader =
            '<?xml version="1.0" encoding="UTF-8"?>' +
            '<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" xmlns:' + xmlns + '>' +
            '<SOAP-ENV:Header>' +
              '<Action SOAP-ENV:mustUnderstand="1" xmlns="http://schemas.xmlsoap.org/ws/2003/03/addressing">' + escapeHtml(action) + '</Action>' +
              '<Timestamp SOAP-ENV:mustUnderstand="1" Id="id" xmlns="http://schemas.xmlsoap.org/ws/2002/07/utility">' +
                  '<Created>2037-08-12T14:45:00Z</Created>' +
                  '<Expires>2037-08-12T14:45:00Z</Expires>' +
              '</Timestamp>' +
              '<Locale>jp</Locale>' +
            '</SOAP-ENV:Header>';
    return xmlHeader;
  }

  // function to set XMLHTTP
  // return xmlhttp object
  function setXMLHTTP() {
    let xmlhttp = false;
    if (typeof ActiveXObject !== 'undefined') {
      try {
        xmlhttp = new ActiveXObject('Microsoft.XMLHTTP');
      } catch (e) {
        xmlhttp = false;
      }
    }
    if (!xmlhttp && typeof XMLHttpRequest !== 'undefined') {
      xmlhttp = new XMLHttpRequest();
    }
    return xmlhttp;
  }

  // ---- xml settings end ----//}}}


  // ---- getGrnFollowData ----//{{{
  function getNewestFollow() {
    return new Promise((resolve, reject) => {
      const xhrForFollow = setXMLHTTP();
      const url = '/g/cbpapi/bulletin/api.csp';
      const apiName = 'BulletinGetFollows';

      const followRequest =
                makeXMLHeader('bulletin', apiName) +
                '<SOAP-ENV:Body>' +
                    '<' + apiName + '>' +
                        '<parameters topic_id="' + TOPIC_ID + '" offset="0" limit="100"></parameters>' +
                    '</' + apiName + '>' +
                '</SOAP-ENV:Body>' +
                '</SOAP-ENV:Envelope>';
      console.log(followRequest);

      xhrForFollow.open('POST', url, true);
      xhrForFollow.onload = function() {
        if (xhrForFollow.readyState === 4 && xhrForFollow.status === 200) {
          resolve(xhrForFollow.responseXML);
        }
      };
      xhrForFollow.send(followRequest);
    });
  }
  // ---- getGrnFollowData finish ----//}}}


  // ---- showPortlet ----//{{{
  function showFollowData(followData) {
    console.log(followData);

    // get Follows
    const follows = followData.getElementsByTagName('follow').length;
    // get random follow
    const randomFollowAray = generateRandomx(follows);
    const forNum = (follows < DISPLAY_COUNT) ? follows : DISPLAY_COUNT;

    for (let i = 0; i < forNum; i++) {
      // get follow data
      const follow = followData.getElementsByTagName('follow')[randomFollowAray[i]];
      const topicUrl = '<a href="/g/bulletin/view.csp?&aid=' + TOPIC_ID + '" target="_blank">';
      const body = (follow.getAttribute('html_text') != null) ? follow.getAttribute('html_text') : follow.getAttribute('text');
      // body = '<a href="/g/bulletin/view.csp?&aid=' + TOPIC_ID + '" target="_blank">' + body + '</a>';

      // get file data
      const files = follow.children;
      const fileName = files[0].getAttribute('name');
      const fileId = files[0].getAttribute('id');
      const fileUrl = '/g/bulletin/file_download.csp/-/' + fileName + '?fid=' + fileId + '&ticket=&.jpg';

      // set data
      $('#content-' + i).append(topicUrl + body + '</a>');
      $('#img-' + i).append(topicUrl + '<img style="max-width: 240px" src="' + fileUrl + '"></a>');
    }

    if (forNum < DISPLAY_COUNT) {
      for (let j = forNum; j <= DISPLAY_COUNT; j++) {
        $('#img-' + j).parent().remove();
      }
    }
  }
  // ----showPortlet finish----//}}}

  getNewestFollow()
    .then(showFollowData, (e) => {
      console.log(e);
    });

})(jQuery.noConflict(true));

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

 /* =========== INDEX LIST ============
  1: RESET
  2: COMMON
  3: LAYOUT
  4: MODULE
====================================== */
/* ===================================
  1: RESET
====================================== */
/*@import "normalize";*/
/* ===================================
  2: COMMON
====================================== */
.template-table h1, .template-table h2, .template-table h3, .template-table h4, .template-table h5, .template-table h6 {
  margin: 0;
  font-weight: normal;
  line-height: 1.5;
}
.template-table p,
.template-table ul,
.template-table ol,
.template-table dl {
  list-style: none;
  margin: 0;
  line-height: 1.4;
  font-size: 11px;
  font-size: 1.1rem;
}
.template-table img {
  line-height: 1;
  vertical-align: top;
}
.template-table table {
  width: 100%;
  border-collapse: collapse;
}
.template-table th,
.template-table td {
  text-align: left;
  line-height: 1.4;
  font-size: 10px;
  font-size: 1rem;
}

/* ===================================
  4: MODULE
====================================== */
.template-table {
  background: #f2f2f2;
  font-size: 12px;
  font-size: 1.2rem;
}
.template-table-footer {
  background: #666;
  padding: 30px;
  color: #fff;
}
.template-table-footer p {
  font-size: 9px;
  font-size: 0.9rem;
}
.template-table-footer .footer-nav-wrap {
  display: table;
  width: 100%;
}
.template-table-footer .footer-nav-wrap .nav-box {
  width: 33.33333333%;
  display: table-cell;
  box-sizing: border-box;
  padding: 20px 20px 0 0;
}
.template-table-footer .footer-nav-wrap .nav-box li {
  margin-bottom: 7px;
}
.template-table-footer .footer-nav-wrap .nav-box a {
  display: inline-block;
  padding-left: 15px;
  color: #fff;
  font-size: 9px;
  font-size: 0.9rem;
}
.template-table-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-table-footer .footer-nav-wrap .nav-box a:hover {
  color: #ffff66;
}
.template-table-contents {
  padding: 30px;
  background: #f2f2f2;
}
.template-table-block {
  padding: 30px;
  background: #fff;
}
.template-table-block table {
  width: 100%;
  border-collapse: collapse;
  border-top: 1px solid #e0e0e0;
  margin-bottom: 20px;
}
.template-table-block th {
  padding: 10px;
  background: #f5f5f5;
  border-left: none;
  border-right: none;
  border-bottom: 1px solid #e0e0e0;
  vertical-align: top;
  text-align: left;
  white-space: nowrap;
}
.template-table-block td {
  padding: 10px;
  border-left: none;
  border-right: none;
  border-bottom: 1px solid #e0e0e0;
  vertical-align: top;
  text-align: left;
}
.template-table-block td:hover {
  background: #eaf3cf !important;
}

.template-table-block td a {
/*
  display:block;
*/
  width:100%;
  height:100%;
  text-decoration:none;
  color: #000000;
}

.template-table-block table.bordered {
  border: 1px solid #e0e0e0;
}
.template-table-block table.bordered th {
  border: 1px solid #e0e0e0;
}
.template-table-block table.bordered td {
  border: 1px solid #e0e0e0;
}
.template-table-block .over {
  background: #eaf3cf;
}
.template-table-block .over-row {
  background: #ffffdb;
}
.template-table-block .over-col {
  background: #eef7e8;
}
.template-table .text-left {
  text-align: left;
}
.template-table .text-center {
  text-align: center;
}
.template-table .text-right {
  text-align: right;
}
.template-table .text-top {
  vertical-align: top;
}
.template-table .text-middle {
  vertical-align: middle;
}
.template-table .text-bottom {
  vertical-align: bottom;
}

Garoon ポートレットの準備

今回使うポートレットを作成していきます。
54〜56 行目をリソースの準備で生成したリンクにそれぞれ書き換えます。

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

<style type="text/css">
<!--
#MyHeader {
  border-left: 6px solid #064CB6;
  border-bottom: 1px solid #0051A1;
  padding: 0.5em 0 0.5em 0.75em;
}
-->
</style>

<div class="template-table-block">
  <h3 id="MyHeader">ガルーン Tips</h3>
  <table class="bordered">
    <thead>
      <tr>
        <th>画像</th>
        <th>コンテンツ</th>
      </tr>
    </thead>
    <tbody>
      <tr>
        <td style="height:1px" id="img-0"></td>
        <td style="height:1px" id="content-0"></td>
      </tr>
      <tr>
        <td style="height:1px" id="img-1"></td>
        <td style="height:1px" id="content-1"></td>
      </tr>
      <tr>
        <td style="height:1px" id="img-2"></td>
          <td style="height:1px" id="content-2"></td>
      </tr>
      <tr>
        <td style="height:1px" id="img-3"></td>
        <td style="height:1px" id="content-3"></td>
      </tr>
      <tr>
        <td style="height:1px" id="img-4"></td>
        <td style="height:1px" id="content-4"></td>
      </tr>
    </tbody>
  </table>
</div>

<script type="text/javascript" src="https://js.cybozu.com/jquery/3.4.1/jquery.min.js"></script>
<link rel="stylesheet" href="/g/cabinet/download.csp/-/main.css?fid=26&ticket=&hid=9&.css">
<script type="text/javascript" src="/g/cabinet/download.csp/-/garoon_tips.js?fid=25&ticket=&hid=9&.js"></script>
<script type="text/javascript" src="/g/cabinet/download.csp/-/tablecloth.js?fid=27&ticket=&hid=9&.js"></script>

新規に HTML ポートレットを作成し、「ポートレットの内容」に記述します。

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

動作確認

掲示板のフォローと、設置したポータルの内容が一致しているか確認します。

おまけ:ブラウザーの情報を表示するには?

Garoon ポートレットの準備

新規に HTML ポートレットを作成し、下記コードを「ポートレットの内容」に記述します。

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

<!-- 背景デザイン -->
<div style="background-color:#feebed; border:5px solid #e79794; margin:5px 0px 10px 0px; padding:5px 10px 5px 10px; border-radius:6px; text-align:left;">
  <!-- 内容デザイン -->
  <div id="result" style="font-size:28px; color:#666666; font-weight:bold; margin:0px 0px 3px 0px;">
  </div>
  <div style="font-size:110%;">
    <span style="color:#ff6600;"></span>
  </div>
</div>

<script type="text/javascript" src="https://js.cybozu.com/jquery/3.4.1/jquery.min.js"></script>
<script type="text/javascript">
(function($){

  // ブラウザ情報取得
  const getBrowser = function(ua, ver){
    let name = "判定できませんでした。";

    if (ua.indexOf('chrome') != -1) {
      /* Chrome. */
      name = 'chrome';
    } else if (ua.indexOf('safari') != -1) {
      /* Safari. */
      name = 'safari';
    } else if (ua.indexOf('opera') != -1) {
      /* Opera. */
      name = 'opera';
    } else if (ua.indexOf('firefox') != -1) {
      /* FireFox. */
      name = 'firefox';
    } else if (ua.indexOf('edge') != -1) {
      /* Edge. */
      name = 'edge';
    } else if (ua.indexOf("msie") != -1) {
      if (ver.indexOf("msie 6.") != -1) {
        name = 'ie6';
      } else if (ver.indexOf("msie 7.") != -1) {
        name = 'ie7';
      } else if (ver.indexOf("msie 8.") != -1) {
        name = 'ie8';
      } else if (ver.indexOf("msie 9.") != -1) {
        name = 'ie9';
      } else if (ver.indexOf("msie 10.") != -1) {
        name = 'ie10';
      } else {
        name = 'ie';
      }
    } else if (ua.indexOf('trident/7') != -1) {
      name = 'ie11';
    }
    return name;
  };

  const userAgent = window.navigator.userAgent.toLowerCase();
  const appVersion = window.navigator.appVersion.toLowerCase();
  document.getElementById("result").innerHTML = "<br>あなたが利用しているブラウザは <font color=\"red\">" + getBrowser(userAgent, appVersion) + "</font> です。<br>" + userAgent + "<br><br>";
})(jQuery.noConflict(true));
</script>

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

おわりに

今回は情シスですが、総務やその他の社内問い合わせを受ける部署でも応用が効くと思います。お試しあれ!

ガルーンポータル活用 Tips

更新履歴

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