こんにちは。サイボウズの楊です。
今回は「関連レコードもCSV出力したい!」というニーズにお応えしたいと思います。
kintoneアプリには、他アプリのレコードを参照できる「関連レコード一覧」機能があります。
ひとつのフィールドをキーとして、関連するレコードを現在のレコード詳細画面に表示できます。
しかし、この場合、関連レコードのデータは表示されているだけで、閲覧しているレコード自体がデータとして保持しているわけではありません。
また、kintoneにはレコードをCSV形式でダウンロードする機能があります。
CSVで書き出す場合、関連レコードは対象のレコード自体にデータとして存在しないため、通常は関連レコードをCSVに出力できません。
今回は、関連レコードを含めてCSVを出力するTipsを紹介します。
デモ環境で実際に動作を確認できます。
ログイン情報は
cybozu developer networkデモ環境
で確認してください。
完成したアプリには、以下のようにCSVファイル出力ボタンが表示されます。
ダウンロードしたCSVファイルのイメージは以下のとおりです。
今回使用するのはアプリストアにある「案件管理」と「顧客リスト」アプリを応用したものです。
のちほど「顧客リスト」アプリの関連レコードのフィールド設定で「案件管理」アプリが必要となりますので、先に「案件管理」のアプリの追加をしてください。
フィールドコードが異なると、正常に動作しないため注意して変更しましょう。
案件管理アプリのフィールドの設定は以下のとおりです。
なお、記載していないフィールドはデフォルトのままです。
フィールドタイプ |
フィールド名 |
フィールドコード |
文字列(1行) |
会社名 |
company |
ドロップダウン |
製品名 |
product |
計算 |
小計 |
subtotal |
顧客リストアプリに「関連レコード」フィールドを追加します。
関連レコード一覧の設定の詳細は以下のとおりです。
フィールドコードが異なると、正常に動作しないため注意して変更しましょう。
顧客リストアプリのフィールドの設定は以下のとおりです。
なお、記載していないフィールドはデフォルトのままです。
フィールドタイプ |
フィールド名 |
フィールドコード |
文字列(1行) |
会社名 |
company |
文字列(1行) |
部署名 |
division |
文字列(1行) |
担当者名 |
assignee |
文字列(1行) |
TEL |
TEL |
文字列(1行) |
メールアドレス |
Mail |
関連レコード一覧 |
関連レコード一覧 |
reference |
サンプルコードはJavaScriptファイルとして保存し、顧客リストアプリへ適用してください。
適用方法は次のヘルプページを参考にしてください。
JavaScriptファイルや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
|
/*
* related records sample program
* Copyright (c) 2025 Cybozu
*
* https://opensource.org/license/mit/
* Licensed under the MIT License
*/
(() => {
'use strict';
kintone.events.on('app.record.index.show', (event) => {
const records = event.records;
const appId = kintone.app.getRelatedRecordsTargetAppId('reference');
// ボタンの有無をチェック
if (document.getElementById('btn-export-csv')) {
return;
}
const spaceEl = kintone.app.getHeaderMenuSpaceElement();
const CSVButtonEl = document.createElement('button');
CSVButtonEl.textContent = 'CSVファイル出力';
CSVButtonEl.id = 'btn-export-csv';
spaceEl.appendChild(CSVButtonEl);
CSVButtonEl.addEventListener('click', () => {
getMakeCsv(appId, records).then((resp) => {
downloadFile(resp);
});
});
});
// csvデータの作成
const getMakeCsv = (appId, customerRecords) => {
// CSVエスケープ関数
const escapeCSV = (value) => {
if (value == null) return '';
const str = String(value);
if (str.includes(',') || str.includes('"') || str.includes('\n') || str.includes('\r')) {
return `"${str.replace(/"/g, '""')}"`;
}
return str;
};
// 同じ会社名の関連レコードを取得
const fetchRelatedRecords = async (opt_index, opt_data) => {
const index = opt_index || 0;
const data = opt_data || [];
const customerRecord = customerRecords[index];
// null/undefinedチェック
if (!customerRecord) {
console.error('顧客レコードが見つかりません:', index);
return data;
}
const company = customerRecord.company?.value || '';
const division = customerRecord.division?.value || '';
const assignee = customerRecord.assignee?.value || '';
const tel = customerRecord.TEL?.value || '';
const mail = customerRecord.Mail?.value || '';
const query = `company = "${company}"`; // 会社名をキーに該当関連レコードを取得
const params = {
app: appId,
query: query
};
try {
const resp = await kintone.api(kintone.api.url('/k/v1/records', true), 'GET', params);
const row = [escapeCSV(company), escapeCSV(division), escapeCSV(assignee), escapeCSV(tel), escapeCSV(mail)];
const relatedRecords = resp.records;
if (relatedRecords.length > 0) {
relatedRecords.forEach((relatedRecord) => {
row.push(escapeCSV(relatedRecord.product.value));
row.push(escapeCSV(relatedRecord.subtotal.value));
});
}
data.push(row.join(','));
if (customerRecords.length > index + 1) {
return await fetchRelatedRecords(index + 1, data);
}
return data;
} catch (error) {
console.error('API呼び出しエラー:', error);
throw error;
}
};
const header = ['会社名', '部署名', '担当者名', 'TEL', 'メールアドレス', '製品名', '小計'].join(',');
let csvData = [header];
return new Promise((resolve, reject) => {
fetchRelatedRecords().then((resp) => {
csvData = csvData.concat(resp);
resolve(csvData);
}).catch((error) => {
console.error('関連レコード取得エラー:', error);
reject(error);
});
});
};
// ダウンロード関数
const downloadFile = (data) => {
const csv = data.join('\r\n');
// ファイル名
const filename = `client_case${getTimeStamp()}.csv`;
// Blob準備
const bom = new Uint8Array([0xef, 0xbb, 0xbf]);
const blob = new Blob([bom, csv], {type: 'text/csv'});
// ファイルダウンロード(モダンブラウザ対応)
const url = window.URL || window.webkitURL;
const blobUrl = url.createObjectURL(blob);
const a = document.createElement('a');
a.href = blobUrl;
a.download = filename;
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
url.revokeObjectURL(blobUrl);
};
// ファイル名に付与する日付の取得
const getTimeStamp = () => {
const d = new Date();
const YYYY = d.getFullYear();
let MM = d.getMonth() + 1;
let DD = d.getDate();
let hh = d.getHours();
let mm = d.getMinutes();
if (MM < 10) {
MM = `0${MM}`;
}
if (DD < 10) {
DD = `0${DD}`;
}
if (hh < 10) {
hh = `0${hh}`;
}
if (mm < 10) {
mm = `0${mm}`;
}
return `${YYYY}${MM}${DD}${hh}${mm}`;
};
})();
|
今回はレコードの詳細情報を取得し、CSVファイルを手動で作成して、そのCSVファイルの中にデータを格納しています。
それでは簡単に説明します。
レコード情報と要素の取得
固定リンクがコピーされました
kintone.app.getRelatedRecordsTargetAppId('フィールドコード')
で関連レコードのアプリIDを取得します。
関連レコード一覧の参照先のアプリIDを取得する
14
|
const appId = kintone.app.getRelatedRecordsTargetAppId('reference');
|
そして、kintone.app.getHeaderMenuSpaceElement()
でボタン作成のためにレコード一覧のメニューの右側の要素を取得します。
レコード一覧のメニューの右側の要素を取得する
21
|
const spaceEl = kintone.app.getHeaderMenuSpaceElement();
|
customerRecords
は、顧客管理アプリを一覧表示したときのevent.records
です。
indexでひとつずつレコードを取り出し、各変数の中にフィールドの値を格納します。
50
|
const customerRecord = customerRecords[index];
|
query
では、検索条件でキーとなる会社名を変数に格納します。
複数のレコードを取得する
64
|
const query = `company = "${company}"`; // 会社名をキーに該当関連レコードを取得
|
document.getElementById('btn-export-csv')
をif文の条件とすることで、ボタンがない場合のみボタン要素を生成し、増殖バグを防ぎます。
増殖バグを防ぐ処理については、
レコード一覧画面にボタンを配置してみよう
を参照してください。
16
17
|
// ボタンの有無をチェック
if (document.getElementById('btn-export-csv')) {
|
以下でボタンを押した際に処理を行います。
27
|
CSVButtonEl.addEventListener('click', () => {
|
CSVファイルの作成
固定リンクがコピーされました
CSVファイルの各項目名(ヘッダー)は手動で作成する必要があります。
CSVはカンマ区切りなので、join(',')
で配列の要素を結合した文字列を生成します。
91
|
const header = ['会社名', '部署名', '担当者名', 'TEL', 'メールアドレス', '製品名', '小計'].join(',');
|
関連レコードはkintone.api
で取得します。
kintone.api
は非同期実行なので、顧客ごとの関連レコードを取得するためfetchRelatedRecords
を再帰的に呼び出します。
取得した関連レコードの内容をCSVの各行に追加します。
46
47
|
// 同じ会社名の関連レコードを取得
const fetchRelatedRecords = (opt_index, opt_data) => {
|
現在の日付をファイル名に
固定リンクがコピーされました
Dateオブジェクトで現在の日時を取得します。
130
|
const d = new Date();
|
getMonth()
は0から始まる月を取得するため、注意して+1します。
132
|
let MM = d.getMonth() + 1;
|
月や日付の桁数が一桁場合、前に0を足して二桁にします。
136
137
138
139
140
141
142
143
144
145
146
147
|
if (MM < 10) {
MM = `0${MM}`;
}
if (DD < 10) {
DD = `0${DD}`;
}
if (hh < 10) {
hh = `0${hh}`;
}
if (mm < 10) {
mm = `0${mm}`;
}
|
最後にYYYY + MM + DD + hh + mm
の形式で返します。
148
|
return `${YYYY}${MM}${DD}${hh}${mm}`;
|
データのダウンロードについては、JavaScriptカスタマイズでCSVファイルを生成する一般的な方法を参照してください。
いかがでしたでしょうか。
これで関連レコードもCSV出力できるようになりました。
また上記のテクニックを活用することで関連レコードに限らず、自由に他アプリからのデータをCSV出力できます。
ぜひお試しください。