ポータルのカスタマイズ

にメンテナンス済み

kintone のポータル画面は、ログイン後に最初に表示されるページです。JavaScript と CSS のカスタマイズを活用すれば、業務に合わせたダッシュボードやタスク管理画面を構築できます。

ポータルのカスタマイズは、アプリのカスタマイズとは異なる仕組みで動作します。この記事では、ポータルに関連するイベントや API の使い方から、実践的なカスタマイズパターンまで解説します。

ポータルイベントの基本

ポータル画面が表示された際に発火するイベントが用意されています。

イベント名発火タイミング
portal.showポータル画面が表示された時(PC)
mobile.portal.showポータル画面が表示された時(モバイル)
portal-event.js
(() => {
  'use strict';

  kintone.events.on(['portal.show'], (event) => {
    console.log('ポータル画面が表示されました');
    return event;
  });
})();
カスタマイズファイルの登録場所

ポータルのカスタマイズは、kintone システム管理 の「JavaScript / CSS によるカスタマイズ」から登録します。アプリの設定画面からではなく、システム管理から設定する点に注意してください。設定には kintone のシステム管理者権限が必要です。

ポータル API

ポータルのカスタマイズでは、以下の API が利用できます。

kintone.portal.getContentSpaceElement()

ポータルのコンテンツエリアの要素(DOM)を取得します。この要素に対して HTML 要素を追加することで、ポータルにカスタムコンテンツを表示できます。

get-content-space.js
kintone.events.on(['portal.show'], (event) => {
  const contentSpace = kintone.portal.getContentSpaceElement();
  if (contentSpace) {
    const div = document.createElement('div');
    div.textContent = 'カスタムコンテンツ';
    contentSpace.appendChild(div);
  }
  return event;
});
チェック

kintone.portal.getContentSpaceElement()portal.show イベントのコールバック内でのみ使用できます。イベント外から呼び出すと null が返されます。

モバイル対応

モバイルでは kintone.mobile.portal.getContentSpaceElement() を使用します。

mobile-portal.js
kintone.events.on(['mobile.portal.show'], (event) => {
  const contentSpace = kintone.mobile.portal.getContentSpaceElement();
  if (contentSpace) {
    const div = document.createElement('div');
    div.textContent = 'モバイルポータルのカスタムコンテンツ';
    contentSpace.appendChild(div);
  }
  return event;
});

実践パターン 1: お知らせパネルの追加

ポータルにカスタムのお知らせパネルを表示するパターンです。

notice-panel.js
(() => {
  'use strict';

  const events = ['portal.show', 'mobile.portal.show'];

  kintone.events.on(events, (event) => {
    const isMobile = event.type === 'mobile.portal.show';
    const contentSpace = isMobile
      ? kintone.mobile.portal.getContentSpaceElement()
      : kintone.portal.getContentSpaceElement();

    if (!contentSpace) {
      return event;
    }

    // お知らせパネルの作成
    const panel = document.createElement('div');
    panel.style.cssText = `
      background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
      color: white;
      padding: 20px 24px;
      border-radius: 8px;
      margin-bottom: 16px;
      font-size: 14px;
      line-height: 1.6;
    `;

    const title = document.createElement('h2');
    title.textContent = '📢 お知らせ';
    title.style.cssText = 'margin: 0 0 8px 0; font-size: 18px;';

    const message = document.createElement('p');
    message.textContent = '2026年3月1日より、新しい経費精算フローが開始されます。詳細は総務部までお問い合わせください。';
    message.style.margin = '0';

    panel.appendChild(title);
    panel.appendChild(message);

    // ポータルの先頭に挿入
    contentSpace.prepend(panel);

    return event;
  });
})();

実践パターン 2: 自分のタスク一覧ダッシュボード

REST API を使って、ログインユーザーに割り当てられたタスクをポータルに一覧表示するパターンです。

task-dashboard.js
(() => {
  'use strict';

  const TASK_APP_ID = 15; // タスク管理アプリのID

  kintone.events.on(['portal.show'], async (event) => {
    const contentSpace = kintone.portal.getContentSpaceElement();
    if (!contentSpace) {
      return event;
    }

    const loginUser = kintone.getLoginUser();

    try {
      // ログインユーザーの未完了タスクを取得
      const result = await kintone.api(
        kintone.api.url('/k/v1/records.json', true),
        'GET',
        {
          app: TASK_APP_ID,
          query: `担当者 in ("${loginUser.code}") and ステータス not in ("完了") order by 期限日 asc limit 10`,
          fields: ['レコード番号', 'タスク名', '期限日', 'ステータス', '優先度'],
        }
      );

      // ダッシュボードの作成
      const dashboard = document.createElement('div');
      dashboard.style.cssText = `
        background: white;
        border: 1px solid #e3e7ee;
        border-radius: 8px;
        padding: 20px;
        margin-bottom: 16px;
      `;

      const heading = document.createElement('h2');
      heading.textContent = `📋 マイタスク(${result.records.length} 件)`;
      heading.style.cssText = 'margin: 0 0 12px 0; font-size: 16px; color: #333;';
      dashboard.appendChild(heading);

      if (result.records.length === 0) {
        const empty = document.createElement('p');
        empty.textContent = '未完了のタスクはありません 🎉';
        empty.style.color = '#888';
        dashboard.appendChild(empty);
      } else {
        const table = document.createElement('table');
        table.style.cssText = 'width: 100%; border-collapse: collapse; font-size: 14px;';

        // ヘッダー
        const thead = document.createElement('thead');
        thead.innerHTML = `
          <tr style="background: #f5f6fa; text-align: left;">
            <th style="padding: 8px 12px; border-bottom: 2px solid #e3e7ee;">タスク名</th>
            <th style="padding: 8px 12px; border-bottom: 2px solid #e3e7ee;">期限日</th>
            <th style="padding: 8px 12px; border-bottom: 2px solid #e3e7ee;">ステータス</th>
            <th style="padding: 8px 12px; border-bottom: 2px solid #e3e7ee;">優先度</th>
          </tr>
        `;
        table.appendChild(thead);

        // ボディ
        const tbody = document.createElement('tbody');
        for (const record of result.records) {
          const row = document.createElement('tr');
          const recordId = record['レコード番号'].value;
          const taskUrl = `/k/${TASK_APP_ID}/show#record=${recordId}`;

          row.innerHTML = `
            <td style="padding: 8px 12px; border-bottom: 1px solid #eee;">
              <a href="${taskUrl}" style="color: #3498db; text-decoration: none;">${record['タスク名'].value}</a>
            </td>
            <td style="padding: 8px 12px; border-bottom: 1px solid #eee;">${record['期限日'].value || '-'}</td>
            <td style="padding: 8px 12px; border-bottom: 1px solid #eee;">${record['ステータス'].value}</td>
            <td style="padding: 8px 12px; border-bottom: 1px solid #eee;">${record['優先度'].value}</td>
          `;
          tbody.appendChild(row);
        }
        table.appendChild(tbody);
        dashboard.appendChild(table);
      }

      contentSpace.prepend(dashboard);
    } catch (error) {
      console.error('タスクの取得に失敗しました', error);
    }

    return event;
  });
})();
チェック

ポータルのカスタマイズでは、kintone.api を使って複数のアプリからデータを取得し、統合的なダッシュボードを構築できます。ただし、API の呼び出し回数が増えるとページの表示速度に影響するため、取得するフィールドを絞り込む(fields パラメータの活用)ことをおすすめします。

実践パターン 3: 集計サマリーの表示

複数アプリの集計結果をポータルに表示するパターンです。

summary-dashboard.js
(() => {
  'use strict';

  const APP_IDS = {
    案件管理: 10,
    問い合わせ: 11,
    タスク: 15,
  };

  kintone.events.on(['portal.show'], async (event) => {
    const contentSpace = kintone.portal.getContentSpaceElement();
    if (!contentSpace) {
      return event;
    }

    const loginUser = kintone.getLoginUser();

    // 各アプリから件数を並列で取得
    const [cases, inquiries, tasks] = await Promise.all([
      kintone.api(kintone.api.url('/k/v1/records.json', true), 'GET', {
        app: APP_IDS.案件管理,
        query: `担当者 in ("${loginUser.code}") and ステータス in ("進行中")`,
        totalCount: true,
        fields: ['レコード番号'],
      }),
      kintone.api(kintone.api.url('/k/v1/records.json', true), 'GET', {
        app: APP_IDS.問い合わせ,
        query: `対応者 in ("${loginUser.code}") and 対応状況 in ("未対応")`,
        totalCount: true,
        fields: ['レコード番号'],
      }),
      kintone.api(kintone.api.url('/k/v1/records.json', true), 'GET', {
        app: APP_IDS.タスク,
        query: `担当者 in ("${loginUser.code}") and ステータス not in ("完了")`,
        totalCount: true,
        fields: ['レコード番号'],
      }),
    ]);

    // カードスタイルのサマリーを構築
    const container = document.createElement('div');
    container.style.cssText = `
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
      gap: 16px;
      margin-bottom: 16px;
    `;

    const items = [
      { label: '進行中の案件', count: cases.totalCount, color: '#3498db', appId: APP_IDS.案件管理 },
      { label: '未対応の問い合わせ', count: inquiries.totalCount, color: '#e74c3c', appId: APP_IDS.問い合わせ },
      { label: '未完了タスク', count: tasks.totalCount, color: '#2ecc71', appId: APP_IDS.タスク },
    ];

    for (const item of items) {
      const card = document.createElement('a');
      card.href = `/k/${item.appId}/`;
      card.style.cssText = `
        display: block;
        background: white;
        border: 1px solid #e3e7ee;
        border-left: 4px solid ${item.color};
        border-radius: 8px;
        padding: 20px;
        text-decoration: none;
        transition: box-shadow 0.2s;
      `;
      card.addEventListener('mouseenter', () => {
        card.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)';
      });
      card.addEventListener('mouseleave', () => {
        card.style.boxShadow = 'none';
      });

      card.innerHTML = `
        <div style="font-size: 14px; color: #888; margin-bottom: 4px;">${item.label}</div>
        <div style="font-size: 32px; font-weight: bold; color: #333;">${item.count}</div>
      `;
      container.appendChild(card);
    }

    contentSpace.prepend(container);
    return event;
  });
})();

CSS カスタマイズとの併用

ポータルの見た目をさらにカスタマイズするには、CSS ファイルを追加します。

portal-style.css
/* ポータルの背景色を変更 */
.gaia-portal-content-area {
  background-color: #f5f6fa;
}

/* ポータルコンテンツエリアの幅を調整 */
.gaia-portal-content {
  max-width: 1200px;
  margin: 0 auto;
}

/* お知らせウィジェットのスタイル調整 */
.gaia-portal-notification {
  border-radius: 8px;
  overflow: hidden;
}
CSS セレクタの注意点

kintone の内部 CSS クラス名は kintone のアップデートにより変更される可能性があります。特に、kintone のフロントエンド刷新に伴い、既存の CSS クラス名が使用できなくなる場合があります。カスタマイズの CSS が影響を受けていないか、定期的に確認してください。

CSSカスタマイズの基本
kintone の CSS カスタマイズの基本を解説します。フィールドやヘッダーの見た目を変更する方法から、条件に応じた行の背景色変更、フィールドの非表示、レスポンシブ対応まで幅広く紹介します。

ポータルカスタマイズの制約

項目制約
カスタマイズファイルの登録場所kintone システム管理(アプリ設定ではない)
管理者権限kintone システム管理者権限が必要
スペースポータルスペースのポータルは portal.show では対応不可
ゲストスペースゲストスペースのポータルには別途カスタマイズが必要
イベント内でのみ有効な APIgetContentSpaceElement() はイベント外では null
スペースポータルについて

portal.show イベントは kintone のトップポータル画面でのみ発火します。スペースごとのポータル画面をカスタマイズする場合は、space.portal.show イベントを使用してください。

まとめ

  • ポータルのカスタマイズは portal.show / mobile.portal.show イベントで実装する
  • kintone.portal.getContentSpaceElement() でコンテンツエリアの DOM 要素を取得できる
  • カスタマイズファイルは kintone システム管理 から登録する(アプリ設定ではない)
  • REST API を組み合わせることで、複数アプリを横断したダッシュボードを構築できる
  • 表示速度を考慮し、Promise.all による並列取得と fields パラメータの活用を心がける
  • CSS クラス名は kintone のアップデートで変更される可能性があるため、定期的に確認する

練習問題

ポータルのカスタマイズファイルは、どこから登録しますか?

kintone.portal.getContentSpaceElement()portal.show イベント外から呼び出した場合、何が返されますか?

ポータルのカスタマイズで、複数のアプリから同時にデータを取得する際に推奨される方法はどれですか?

#kintone #JavaScript #CSS