ガルーンポータル活用Tips #4「電光掲示板ポータル(ガルーン掲示板編)」

目次

はじめに

中堅・大規模組織向けグループウェア「サイボウズGaroon」のポータル活用企画第4弾。電光掲示板風のテロップが流れるポータルを作成していきます。

あれ?電光掲示板はこの前もやったんじゃない?と思われるかもしれません。

…違うんです! 「電光掲示板ポータル(kintone連携編)」 は電光掲示板の文字をkintoneのアプリから取得しましたが、今回はシンプル版!
Garoon掲示板の最新フォローから取得してみます。複雑なロジックも少なく、よりスモールスタートしやすい版ともいえます。

こんなときに便利です

  • 重要な掲示を見逃す人がいて困ってませんか?
  • また、重要な掲示がたまりすぎて空気になっていませんか?

電光掲示板ならできるんです!ぜひお試しください。

完成イメージ

テロップだけ動画にしてみました。

このポータルは電光掲示板風ポートレットの活用例としての、各部からのお知らせを表示するポータルです。
今回作る部分は、トップの「全社通知ポートレット」のみです。掲示板のフォローからデータを抽出します。

なお、電光掲示板の部分以外は、標準ポートレットの「掲示板」の本文を利用しています。
本文からデータを取得するパターンはまた別のTipsで紹介します!

データ取得元の準備

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

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

今回はこのような掲示板です。最新のフォローのみ取得します。

リソースの準備

今回のカスタマイズでは、次の2つのファイルを作成します。

ファイル名 説明 修正が必要か 配置先
bulletinsignage.js 掲示板情報の取得とテロップの表示用ファイル 必要 JavaScript / CSSによるカスタマイズ
marquee.css テロップ部分のデザインと動きの設定用ファイル 不要 JavaScript / CSSによるカスタマイズ

サンプルコード を参考に、それぞれのファイルを作成します。
文字コードは「UTF-8」で保存してください。
このとき、bulletinsignage.jsの13行目のTOPIC_IDを、 データ取得元の準備 で確認したaidに書き換えます。

サンプルコード

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

(() => {
  'use strict';

  // 対象の掲示板のaid(掲示ID)を指定します
  const TOPIC_ID = 1;

  // SOAPリクエストのXMLを組み立てる際に、特殊文字をエスケープしてXMLが壊れるのを防ぎます
  function escapeHtml(str) {
    if (!str) {
      return '';
    }
    return str
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;')
      .replace(/'/g, '&#39;');
  }

  // Garoon SOAP APIのリクエストに必要なXMLヘッダーを組み立てます
  // services: サービス種別(base / bulletin)、action: 実行するAPI名
  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('サービスの種別を指定してください');
        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;
  }

  // 掲示板API(BulletinGetFollows)で、対象掲示の最新のフォロー(コメント)を1件取得します
  function getNewestFollow() {
    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="1"></parameters>' +
      '</' + apiName + '>' +
      '</SOAP-ENV:Body>' +
      '</SOAP-ENV:Envelope>';

    return fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'text/xml',
      },
      body: followRequest
    })
      .then(response => {
        if (!response.ok) {
          throw new Error('Network response was not ok.');
        }
        return response.text();
      })
      // レスポンスのXML文字列をDOMに変換し、follow要素を取り出します
      .then(str => (new window.DOMParser()).parseFromString(str, 'text/xml'))
      .then(data => data.getElementsByTagName('follow')[0])
      .catch(error => console.error('Fetch error:', error));
  }

  // 取得したフォローの本文から、テロップ表示用のHTML要素を生成します
  // designClass: テロップに適用するデザイン用クラス(ledText01 / ledText02)
  function makeSignageHtml(newestFollow, designClass) {
    const divElement = document.createElement('div');
    divElement.className = 'marquee ' + designClass;
    const pElement = document.createElement('p');
    // textContentはHTMLを解釈しないため、フォロー本文はそのまま安全に表示できます
    pElement.textContent = newestFollow.getAttribute('text');
    divElement.appendChild(pElement);
    return divElement;
  }

  // 生成したテロップ要素を、HTMLポートレットのコンテナ(#signage)に追加します
  // デザインはコンテナのdata-design属性で指定します(未指定時はledText01)
  function showSignage(newestFollow) {
    // 対象掲示にフォローが1件もない場合はnewestFollowが取得できないため、何もせず終了します
    if (!newestFollow) {
      return;
    }
    const container = document.getElementById('signage');
    const designClass = container.dataset.design || 'ledText01';
    container.appendChild(makeSignageHtml(newestFollow, designClass));
  }

  // 最新のフォローを取得して、テロップとして表示します
  getNewestFollow()
    .then(showSignage)
    .catch(e => console.error(e));

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

/* テロップのスクロール動作。デザイン(色やサイズ)はHTMLポートレット側で調整します */
.marquee {
  overflow: hidden;
  white-space: nowrap;
  width: 100%;
  box-sizing: border-box;
}

.marquee p {
  display: inline-block;
  padding-left: 100%;
  animation: marquee 15s linear infinite;
  white-space: nowrap;
  margin: 0;
}

@keyframes marquee {
  0% {
    transform: translateX(0);
  }
  100% {
    transform: translateX(-100%);
  }
}

Garoonポートレットの準備

今回使うポートレットを作成していきます。
新規にHTMLポートレットを作成し、「ポートレットの内容」に次の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
<!--
* Garoon Portal sample program
* Copyright (c) 2026 Cybozu
*
* Licensed under the MIT License
* https://opensource.org/license/mit/
-->

<style type="text/css">
/* 電光掲示板風(ledText01)。色・余白・サイズはここで調整できます */
.ledText01 {
  margin: 0 auto 20px; /* 周りの余白 */
  padding: 5px 0; /* 文字周りの余白 */
  /* color: #FFB400; */ /* フォントカラー例:オレンジ */
  /* color: #FF51A8; */ /* フォントカラー例:ピンク */
  /* color: #00FF00; */ /* フォントカラー例:グリーン */
  color: #00D9FF; /* フォントカラー例:ブルー */
  font-size: 40px; /* フォントサイズ */
  background: #000;
  font-family: 'Courier New', monospace;
  text-shadow: 0 0 10px #00D9FF, 0 0 20px #00D9FF;
}

/* シンプルパターン(ledText02) */
.ledText02 {
  margin: 0 auto 20px;
  padding: 5px 0;
  font-size: 20px;
  color: #fff;
  background: #333;
}
</style>

<!--
  テロップの表示領域です。
  data-design でデザインを切り替えられます。
  ledText01: 電光掲示板風
  ledText02: シンプル
-->
<div id="signage" data-design="ledText01"></div>

ポイント

テロップのデザインは、#signage要素のdata-design属性で切り替えられます。
ledText01(電光掲示板風)で表示が崩れる場合は、ledText02(シンプルパターン)に書き換えてお試しください。

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

JavaScript / CSSによるカスタマイズから以下を追加します。

JavaScriptカスタマイズ
  • bulletinsignage.js
CSSカスタマイズ
  • marquee.css

ポータルに配置

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

動作確認

掲示板の最新のフォローと、電光掲示板の内容が一致しているか確認します。

おわりに

シンプルに実装できる電光掲示板風ポートレットを紹介しました。
冒頭の完成イメージのように各部の掲示板とセットで配置すると、全社の重要な情報&各部からのリアルタイム発信をまとめて把握できる、実用的なポータルになりますね。
今後も簡単に作れる凝ったポートレットをどんどん紹介していきますので、お楽しみに~。

ガルーンポータル活用Tips

更新履歴

  • 2020/02/19
    jQueryの追加手順およびjQuery.noConflict(true)を使うようにコードを修正
  • 2024/04/02
    • JavaScriptとCSSファイルをファイル管理にアップロードする方法から、ポータルの「JavaScript / CSSによるカスタマイズ」を使用する方法に変更
    • jQueryからJavaScriptへサンプルコードをリファクタリング
  • 2026/06/01
    • テロップが横方向にはみ出す不具合を修正(jQueryへの依存を削除し、スクロールをCSSアニメーションに一本化)
information

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