ガルーンポータル活用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 マウスオーバー時の動きを設定するプログラム 不要 JavaScript / CSSによるカスタマイズ

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

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

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

(()=> {
  'use strict';

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

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

  // function to escape html
  const 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.
  const 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
  const 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
  const 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 ----//{{{
  const 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 ----//{{{
  const showFollowData = (followData) => {
    console.log(followData);

    // get Follows
    const follows = followData.getElementsByTagName('follow').length;
    // get random follow
    const randomFollowArray = 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')[randomFollowArray[i]];
      const topicUrlStart = '<a href="/g/bulletin/view.csp?&aid=' + TOPIC_ID + '" target="_blank">';
      const topicUrlEnd = '</a>';
      const body = (follow.getAttribute('html_text') != null) ? follow.getAttribute('html_text') : follow.getAttribute('text');

      // 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
      document.querySelector('#content-' + i).innerHTML += topicUrlStart + body + topicUrlEnd;
      document.querySelector('#img-' + i).innerHTML += topicUrlStart + '<img style="max-width: 240px" src="' + fileUrl + '">' + topicUrlEnd;
    }

    if (forNum < DISPLAY_COUNT) {
      for (let j = forNum; j < DISPLAY_COUNT; j++) {
        const parent = document.querySelector('#img-' + j).parentNode;
        if (parent) {
          parent.remove();
        }
      }
    }
  };

  // ----showPortlet finish----//}}}

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

})();

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
/*
 * 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;
}
#MyHeader {
  border-left: 6px solid #064CB6;
  border-bottom: 1px solid #0051A1;
  padding: 0.5em 0 0.5em 0.75em;
}

Garoonポートレットの準備

今回使うポートレットを作成していきます。

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

<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>

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

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

JavaScriptファイルとCSSファイルのアップロード

JavaScript / CSSによるカスタマイズから以下をアップロードします。

JavaScript
  • garoon_tips.js(修正済み)
  • tablecloth.js
CSS
  • main.css

動作確認

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

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

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
<!--
* 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">
(function() {
  // ブラウザ情報取得
  const getBrowser = function(ua, ver) {
    let name = "判定できませんでした。";

    if (ua.includes('chrome')) {
      name = 'chrome';
    } else if (ua.includes('safari')) {
      name = 'safari';
    } else if (ua.includes('opera')) {
      name = 'opera';
    } else if (ua.includes('firefox')) {
      name = 'firefox';
    } else if (ua.includes('edge')) {
      name = 'edge';
    } else if (ua.includes("msie")) {
      if (ver.includes("msie 6.")) {
        name = 'ie6';
      } else if (ver.includes("msie 7.")) {
        name = 'ie7';
      } else if (ver.includes("msie 8.")) {
        name = 'ie8';
      } else if (ver.includes("msie 9.")) {
        name = 'ie9';
      } else if (ver.includes("msie 10.")) {
        name = 'ie10';
      } else {
        name = 'ie';
      }
    } else if (ua.includes('trident/7')) {
      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>`;
})();
</script>

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

おわりに

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

ガルーンポータル活用Tips

更新履歴

  • 2020/02/19
    jQueryの追加手順およびjQuery.noConflict(true)を使うようにコードを修正
  • 2024/04/16
    • jQueryを使わないようにコードを修正
    • JavaScriptとCSSファイルをファイル管理にアップロードする方法から、ポータルの「JavaScript / CSSによるカスタマイズ」を使用する方法に変更
information

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