kintoneのサブテーブルにデータフィルター機能を追加する
前回の記事では、kintoneのサブテーブルでデータを並び替える(ソートする)方法について解説し、情報を希望する順序で表示できるようにしました。
参照; kintoneでテーブルの行をデータの昇順・降順で並べ替える
しかし、多くの場合、並び替えるだけでは十分ではありません。特定の条件を満たす行だけを表示するために、データを絞り込む(フィルターする)必要もあります。
例:「ボールペン」という名前の製品の行だけを表示する。

実際には、「等しい (equal)」、「より大きい (greater than)」、「より小さい (less than)」、「含む (contains)」など、多くの種類のフィルター条件が存在します。
この記事をより分かりやすくするために、ここでは「等しい」という条件に絞って解説します。基本となる実装方法を理解すれば、他の条件にも応用することが可能です。
準備アプリ
前回の記事で作成したアプリを引き続き使用します。
「Excelのデータをkintoneのテーブルにまとめてコピー&ペーストしたい(やれそうでやれないことの補完)」
JavaScriptプログラム
※「JSEdit for kintone」プラグインを使用して、ソースコードを編集します。JSEdit for kintone の画面から jQuery を追加・インストールすることで、HTML 要素のコーディングがより簡単になります。
プログラムの処理流れ
- サブテーブル「商品一覧」のHTML要素を特定する
- cybozu.data.page[‘FORM_DATA’] を使用して、フィールドコードとHTMLクラス間のマッピングを作成します。
参考; kintoneの新規・編集レコード画面のフィールドをカスタマイズする裏技
- cybozu.data.page[‘FORM_DATA’] を使用して、フィールドコードとHTMLクラス間のマッピングを作成します。
- フィルター状態の初期化
- フィルター状態を保存するための変数(
FILTER_STATE)を作成し、各列で選択されたフィルター値を格納します。 - 各列には、1つまたは複数のフィルター値が選択される可能性があります。
- フィルター状態を保存するための変数(
- フィルターアイコンを列のヘッダーに追加する
- 各サブテーブルの列にフィルターアイコンを追加します。
- このアイコンは、フィルター設定用のポップアップを表示するためのトリガーとして機能します。
- フィルター用ポップアップの表示
- フィルターアイコンがクリックされると、列のヘッダー直下にポップアップが表示されます。
- このポップアップには、以下の要素が含まれます。
- 「すべて選択」チェックボックス
- その列に含まれる一意な値ごとのチェックボックス
- フィルターを適用するための「適用」ボタン
- フィルター値の選択
- ユーザーは、個々の値を選択または選択解除したり、「すべて選択」チェックボックスを使って一括で操作したりすることができます。
- これらのチェックボックスの状態は、
FILTER_STATEと同期して更新されます。
- フィルターの適用
- 「適用」ボタンがクリックされた後、以下の処理を実行します。
- 選択された値に基づいて
FILTER_STATEを更新します。 - サブテーブルのすべての行をループし、フィルター条件を満たさない行を非表示にし、条件を満たす行を表示します。
- 選択された値に基づいて
- 「適用」ボタンがクリックされた後、以下の処理を実行します。
- ポップアップの外をクリックした際の非表示処理
- ポップアップの外側をクリックすると、データが隠れないようにポップアップが自動的に閉じます。
ソースコード
(function() {
"use strict";
const FORM_DATA = cybozu.data.page['FORM_DATA'];
// サブテーブルのフィールドIDを取得
const ELEMENT_FIELD_ID = {};
for (const subTableId of Object.keys(FORM_DATA.schema.subTable)) {
const subTable = FORM_DATA.schema.subTable[subTableId];
ELEMENT_FIELD_ID[subTable.var] = subTableId;
}
const tableFieldCode = '商品一覧'; // 対象のサブテーブル名
let FILTER_STATE = {}; // 選択されたフィルターの状態を保持するオブジェクト
/**
* 全てのフィルターを適用し、行を表示/非表示する
* @param {Object} rec - レコードデータ
* @param {string} tableFieldCode - サブテーブルのフィールドコード
* @param {string} subtableClass - サブテーブルのDOMクラス
*/
function applyAllFilters(rec, tableFieldCode, subtableClass) {
const rows = rec[tableFieldCode].value;
for (let i = 0; i < rows.length; i++) {
// 各フィルター条件に一致するかチェック
const visible = Object.keys(FILTER_STATE).every(fieldCode => {
const selected = FILTER_STATE[fieldCode];
const val = rows[i].value[fieldCode].value || "";
if (!selected || selected.length === 0) return true; // 選択なし = 全て表示
return selected.includes(val);
});
$(`${subtableClass} tbody tr`).eq(i).css('display', visible ? '' : 'none');
}
}
// レコード作成/編集画面表示イベント
kintone.events.on(['app.record.create.show', 'app.record.edit.show'], function(event) {
const rec = event.record;
const subtableClass = `.subtable-${ELEMENT_FIELD_ID[tableFieldCode]}`;
// サブテーブルの各列にフィルターアイコンを追加
$(`${subtableClass} .subtable-label-gaia`).each(function(index) {
const th = $(this);
// filter iconをまだ作成していなければ追加
if (th.find('.filter-icon').length === 0) {
th.append(`<span class="filter-icon" style="display:inline-block;width:16px;height:16px;line-height:16px;text-align:center;font-size:12px;font-weight:bold;cursor:pointer;margin-left:5px;border:1px solid #000;border-radius:4px;background:#fff;color:#000;user-select:none;">▼</span>`);
}
// アイコンクリックでフィルターポップアップを表示
th.find('.filter-icon').off('click').on('click', function(e) {
e.stopPropagation();
$('.filter-popup').remove(); // 既存ポップアップ削除
const fieldCode = Object.keys(rec[tableFieldCode].value[0].value)[index];
const values = rec[tableFieldCode].value.map(row => row.value[fieldCode].value || "");
const uniqueValues = [...new Set(values)]; // 重複を排除してユニーク値リスト作成
// ポップアップ作成(inline style)
const popup = $(`<div class="filter-popup" style="position:absolute;background:#fff;border:1px solid #000;padding:8px;border-radius:5px;box-shadow:0 2px 6px rgba(0,0,0,0.2);z-index:1000;max-height:200px;overflow-y:auto;font-size:13px;"></div>`);
// 「全て選択」チェックボックス
const allChecked = !FILTER_STATE[fieldCode] || FILTER_STATE[fieldCode].length === uniqueValues.length;
popup.append(`<div style="margin-bottom:5px;"><input type="checkbox" id="chk_all" ${allChecked ? "checked" : ""}><label for="chk_all" style="cursor:pointer;margin-left:5px"><b>(全て選択)</b></label></div>`);
// 各ユニーク値に対するチェックボックス
uniqueValues.forEach((val, i) => {
const safeVal = val || '(空白)'; // 空白の場合の表示
const id = 'chk_' + i;
const isChecked = !FILTER_STATE[fieldCode] || FILTER_STATE[fieldCode].includes(val);
popup.append(`<div style="margin-bottom:3px;"><input type="checkbox" id="${id}" value="${val}" ${isChecked ? 'checked' : ''}><label for="${id}" style="cursor:pointer;margin-left:5px">${safeVal}</label></div>`);
});
// 適用ボタン
popup.append('<div style="margin-top:8px;text-align:right;"><button class="apply-filter" style="padding:3px 8px;cursor:pointer;">適用</button></div>');
$('body').append(popup);
const offset = th.offset();
popup.css({ top: offset.top + th.outerHeight(), left: offset.left });
// 「全て選択」チェックボックス変更時
popup.find('#chk_all').on('change', function() {
const checked = $(this).is(':checked');
popup.find('input[type=checkbox]').not(this).prop('checked', checked);
});
// フィルター適用ボタン
popup.find('.apply-filter').on('click', function() {
const selected = popup.find('input[type=checkbox]:checked').map(function() {
return this.value;
}).get();
FILTER_STATE[fieldCode] = selected.length === uniqueValues.length ? [] : selected;
applyAllFilters(rec, tableFieldCode, subtableClass);
popup.remove();
});
// ポップアップ外クリックで閉じる
$(document).on('click.filterPopup', function(ev) {
if (!popup.is(ev.target) && popup.has(ev.target).length === 0) {
popup.remove();
$(document).off('click.filterPopup');
}
});
});
});
return event;
});
})();
結果

記事を読んでいただきありがとうございます。
次回の記事では、kintoneでプラグインを作成する方法をご紹介します。ぜひお楽しみに!

