1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

kintoneで「サブテーブルごと関連レコードに表示」する裏技

Last updated at Posted at 2025-12-06

1. はじめに

kintoneでは 「サブテーブルそのものを関連レコードで表示する」 ことはできません。

理由は簡単で、

  • 関連レコード一覧は “1レコード=1行” の表
  • サブテーブルは “1レコードの中の明細行集合”

という構造の違いがあるからです。

しかし、実務ではこんなニーズが本当に多いです。

「アプリBから関連レコードでアプリAを参照するとき、
アプリAのサブテーブルもまとめて一覧に表示したい」

「1行の中に“サブテーブル全体”を綺麗に埋め込みたい」

標準機能では不可能ですが、
“サブテーブルをHTMLテーブルに変換してリッチフィールド化しておく” という方法を使うと、
(無理やり)関連レコード一覧にサブテーブル“ごと”表示することが可能になります。


2. 実現イメージ

●登場するアプリ

  • アプリA:撮影案件
    • サブテーブル「撮影明細」あり
  • アプリB:ダッシュボード/管理アプリ
    • アプリAを関連レコードで参照

●やりたいこと

  • アプリBの関連レコード一覧の1行ごとに、
    アプリAのサブテーブル「撮影明細」全体を表形式で表示したい。

標準ではできません。

●今回の裏技で実現できること

  1. アプリAの保存時に
    サブテーブル → HTMLテーブルに変換

  2. そのHTMLを
    「サブテーブルサマリー」リッチフィールドに保存

  3. アプリBの関連レコードの表示項目に
    「サブテーブルサマリー」を追加

  4. 結果:
    アプリBの関連レコード一覧の中に、サブテーブル全体がそのまま表示される!

image.png


3. 実装の全体フロー(2アプリ構成)

アプリA(サブテーブルあり)
    ↓ 保存時にサブテーブルをHTML化
    フィールド「撮影明細サマリー」に格納
    ↓
アプリB[関連レコード]
    アプリAを参照
    表示項目に「撮影明細サマリー」を含める
    ↓
サブテーブルごと関連レコード一覧に表示される!

4. コード全文(サブテーブル → HTML変換)

※この記事の主役はアプリA側です。
※このコードで生成されたHTMLが「関連レコードで持ち出せるようになる」仕組みです。

コード全文
// Subtable_RichTextTranscription.js
(function () {
  'use strict';

  const CONFIG = {
    SUBTABLE_CODE: '撮影明細',
    TARGET_RICHTEXT_CODE: '撮影明細サマリー',

    COLUMNS: [
      { label: '氏名',     code: '氏名' },
      { label: '種別', code: 'SATUKB' },
      { label: '続柄',     code: '続柄' },
      { label: '年齢', code: '年齢' },
    ],

    // ← 文字列のみ:行内の「顧客レコード番号」で appId=6 をリンク
    LINK_SPECS: [
      { textCode: '氏名', idCode: '顧客レコード番号', appId: 6 },
    ],

    // ← 非表示にしたいフィールド(LINK_SPECSとは独立)
    HIDE_FIELD_CODES: [
      'KOKNO',
      '顧客レコード番号',
      'AGECALC_BIRTHD',
    ],

    NUMERIC_COLS: new Set(['年齢', 'KOKCD']),
    skipIf: (_row) => false,
  };

  const esc = (s) => String(s ?? '')
    .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;').replace(/'/g, '&#39;');

  const toNum = (v) => {
    const raw = (typeof v === 'object' && v && 'value' in v) ? v.value : v;
    const n = Number(String(raw ?? '').replace(/,/g, ''));
    return Number.isFinite(n) ? n : 0;
  };

  const recordUrl = (appId, recId) => `${location.origin}/k/${appId}/show#record=${recId}`;

  const linkSpecByTextCode = Object.fromEntries(
    (CONFIG.LINK_SPECS || []).map(s => [s.textCode, s])
  );

  function cellText(row, code) {
    const f = row?.[code];
    if (!f) return '';
    const v = f.value;
    if (Array.isArray(v)) return v.map(e => e?.name ?? e?.code ?? e).join(', ');
    if (typeof v === 'number') return String(v);
    return String(v ?? '');
  }

  function renderCellHtml(row, code) {
    const text = cellText(row, code);
    const spec = linkSpecByTextCode[code];
    if (spec?.idCode) {
      const recId = toNum(row?.[spec.idCode]?.value);
      if (recId > 0) {
        const href = recordUrl(spec.appId, recId);
        return `<a href="${esc(href)}" target="_blank" rel="noopener noreferrer">${esc(text || `#${recId}`)}</a>`;
      }
    }
    return esc(text);
  }

  function buildHtmlTable(record) {
    const st = record[CONFIG.SUBTABLE_CODE]?.value || [];
    const rows = [];

    for (const r of st) {
      const row = r?.value || {};
      if (CONFIG.skipIf(row)) continue;

      const tds = CONFIG.COLUMNS.map(({ code }) => {
        const valHtml = renderCellHtml(row, code);
        const right = CONFIG.NUMERIC_COLS.has(code) ? 'text-align:right;' : '';
        return `<td style="padding:6px 10px; ${right}">${valHtml}</td>`;
      });

      rows.push(`<tr>${tds.join('')}</tr>`);
    }

    if (!rows.length) return '';

    const thead = `
      <thead>
        <tr>
          ${CONFIG.COLUMNS.map(({ label }) =>
            `<th style="text-align:left; padding:8px 10px; border-bottom:1px solid #ddd;">${esc(label)}</th>`
          ).join('')}
        </tr>
      </thead>`.trim();

    const tbody = `<tbody>${rows.join('')}</tbody>`;

    return `
      <div>
        <table style="border-collapse:collapse; width:100%; max-width:100%;">
          ${thead}
          ${tbody}
        </table>
      </div>
    `.trim();
  }

  // ===== 画面表示時:任意フィールドの非表示 & リッチテキスト編集不可 =====
  kintone.events.on([
    'app.record.create.show',
    'app.record.edit.show',
    'app.record.detail.show',
    'mobile.app.record.create.show',
    'mobile.app.record.edit.show',
    'mobile.app.record.detail.show'
  ], (event) => {
    const rec = event.record;

    if (rec[CONFIG.TARGET_RICHTEXT_CODE]) {
      rec[CONFIG.TARGET_RICHTEXT_CODE].disabled = true;
    }

    const toHide = Array.from(new Set(CONFIG.HIDE_FIELD_CODES || []));
    toHide.forEach(code => {
      try { kintone.app.record.setFieldShown(code, false); } catch {}
    });

    return event;
  });

  // ===== 保存時:サマリーHTMLを書き込み =====
  kintone.events.on([
    'app.record.create.submit',
    'app.record.edit.submit',
    'mobile.app.record.create.submit',
    'mobile.app.record.edit.submit'
  ], (event) => {
    const rec = event.record;
    const html = buildHtmlTable(rec);
    if (rec[CONFIG.TARGET_RICHTEXT_CODE]) {
      rec[CONFIG.TARGET_RICHTEXT_CODE].value = html;
      rec[CONFIG.TARGET_RICHTEXT_CODE].disabled = true;
    }
    return event;
  });

})();


5. コードのポイントと役割

✔ ①表示したい列だけを自由に選べる

COLUMNS: [
  { label: '氏名', code: '氏名' },
  { label: '種別', code: 'SATUKB' },
]

→ 関連レコード側でもそのまま綺麗に出る。


✔ ②サブテーブルの行ごとにHTML <tr> を生成

→ 見栄えも安定し、PDF・モバイルでも強い。


✔ ③リンク付きセルも作れる

LINK_SPECS: [
  { textCode: '氏名', idCode: '顧客レコード番号', appId: 6 },
]
  • 氏名 → 顧客詳細画面へジャンプ
  • 関連レコード一覧の中でもリンクが生きる

= まるで「サブテーブルつき関連レコード」


✔ ④リッチテキストは編集不可&元フィールドは非表示

  • 誤編集を防ぐ
  • 詳細画面もスッキリ

6. 関連レコード側(アプリB)の設定

アプリB側ではほんの1ステップでOK:

関連レコードの「表示するフィールド」に
アプリAの「撮影明細サマリー」(HTMLフィールド)を追加するだけ。

すると……
関連レコード一覧の1行ごとにサブテーブル全体が表示されます。

これは実質的に:

「サブテーブルごと関連レコードに持ち出す」
ことに成功しています。


7. 応用例(さらに強化できます)

  • サブテーブルの中でさらに色付けや強調表示を追加
  • 金額だけ右寄せ
  • アイコンを入れる
  • 合計行を最下部に生成
  • skipIf() で明細のフィルタリング
  • PDF/帳票用のサマリーとしても利用可能
  • Process管理のステータスに応じて表示内容切替

汎用性が高く、どの業務にも応用可能です。


8. 制作背景(実務でのニーズ)

実務の現場では:

  • 管理アプリから明細までまとめて見たい
  • ダッシュボードアプリでサブテーブルごと全件把握したい
  • 組織横断の管理画面で「1行=1案件」にまとめたい

という要望が本当に多いです。

しかし標準では、
サブテーブルを直接関連レコードに持ち出すことはできません。

今回の方法は、その制約を

「サブテーブルをHTMLとして1フィールドに“圧縮”する」

ことで無理やり乗り越えるものです。


9. まとめ

  • kintone標準では「サブテーブルごと関連レコード表示」は不可
  • しかしサブテーブルをHTMLに変換し1つのフィールドにまとめることで、
    関連レコード一覧にサブテーブル全体を持ち出せる
  • 実用性が高く、汎用性も高い
  • 他アプリのダッシュボード設計で非常に役立つ
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?