kintoneにファイルをいっぱい詰め込んだんだけど、ファイルのダウンロードはひとつずつしかできないと嘆いている、そこのアナタに朗報です!
Cybozu CDNに登録されている
JSZip
を用いて、添付ファイルをZipファイルで一括ダウンロードする方法を紹介します。
添付ファイルの一括ダウンロード/アップロードなどの操作について、kintoneコマンドラインツールを使ったやり方の説明記事もあります。
興味のある方は
添付ファイルのダウンロードとアップロードを確認してください。
今回は一括ダウンロードをより簡単に実装するため、Cybozu CDN外のライブラリを利用します。
そのような場合は、いったんファイルをダウンロードしてからアプリに適用しましょう。
また、今回紹介する記事で確認したライブラリのバージョンは以下のとおりです。
JSZipUtilsと、FileSaver.jsはgithub上で公開されているものを取得する方法をおすすめします。
デモ環境で実際に動作を確認できます。
https://dev-demo.cybozu.com/k/264/
ログイン情報は
cybozu developer networkデモ環境で確認してください。
一括ダウンロードのしくみ
固定リンクがコピーされました
今回kintoneからファイルを一括ダウンロードする処理順序は以下のとおりです。
kintone上の添付ファイルをダウンロードするためには、レコードの取得とURL生成が必要です。
この点に関しては以下のサンプルで紹介していますので、参考にしましょう。
ファイルダウンロードで必須となる2つの手順
また、「レコードの取得」、「URLの作成」、「ファイルのダウンロード」は非同期通信で行われる点に注意しましょう。
JavaScriptソースコード
固定リンクがコピーされました
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
|
/* global JSZip */
/* global JSZipUtils */
/* global saveAs */
/*
* JSZip sample program
* Copyright (c) 2016 Cybozu
*
* Licensed under the MIT License
*/
// /
// / License
// / FileSaver.js MIT https://github.com/eligrey/FileSaver.js/blob/master/LICENSE.md
// / jszip MIT or GPLv3 https://github.com/Stuk/jszip/blob/master/LICENSE.markdown
// / jszip-utils MIT or GPLv3 https://github.com/Stuk/jszip-utils/blob/master/LICENSE.markdown
// /
(() => {
'use strict';
const fieldCode = '添付ファイル'; // 添付ファイルのフィールドコード
const isGuestSpace = false;
// レコード取得関数(非同期)100件まで
const getAppRecords = () => {
const url = kintone.api.url('/k/v1/records', isGuestSpace);
const appId = kintone.app.getId();
const condition = kintone.app.getQueryCondition() || '';
const query = condition + 'order by $id asc';
const body = {
app: appId,
query: query,
field: fieldCode
};
return kintone.api(url, 'GET', body);
};
// ファイルキーの取得関数
const getFileKeys = (json) => {
const keys = [];
for (let i = 0; i < json.records.length; i++) {
const filetype = json.records[i][fieldCode];
// 複数添付ファイルが存在する場合あり
for (let j = 0; j < filetype.value.length; j++) {
keys.push(filetype.value[j]);
}
}
return keys;
};
// ファイルサイズチェック関数
const checkFileSize = (filekeys) => {
if (filekeys.length === 0) {
return kintone.Promise.reject('添付ファイルが見つかりませんでした。');
}
let totalsize = 0;
for (let i = 0; i < filekeys.length; i++) {
totalsize += parseInt(filekeys[i].size, 10);
}
if (totalsize < 999) { // 1KB未満
totalsize = String(totalsize);
} else if (totalsize < 999999) { // 1MB未満
totalsize = parseInt(totalsize / 1000, 10) + 'K';
} else if (totalsize < 999999999) { // 1GB未満
totalsize = parseInt(totalsize / 1000000, 10) + 'M';
} else {
// 1GBを上限として設定
return kintone.Promise.reject('ファイルサイズが大きすぎます。');
}
const dflag = confirm(totalsize + 'バイトダウンロードします。よろしいですか?');
if (!dflag) {
return kintone.Promise.reject('ダウンロードがキャンセルされました。');
}
return filekeys;
};
// ファイルURL取得関数(非同期)
const addFileURL = (key) => {
return new kintone.Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
const params = {
fileKey: key.fileKey
};
const url = kintone.api.urlForGet('/k/v1/file', params, isGuestSpace);
xhr.open('GET', url, true); // 非同期
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.responseType = 'blob';
xhr.onload = () => {
if (xhr.status === 200) {
const blob = new Blob([xhr.response]);
const wurl = window.URL || window.webkitURL;
const blobUrl = wurl.createObjectURL(blob);
key.blobUrl = blobUrl; // URLをkeyレコードに追加
resolve(key);
} else {
reject(JSON.parse(xhr.response));
}
};
xhr.send();
});
};
// 複数ファイルURL取得関数
const addfileURLs = (filekeys, keynum) => {
let opt_keynum = keynum || 0;
return addFileURL(filekeys[opt_keynum]).then((resp) => {
opt_keynum++;
if (opt_keynum === filekeys.length) {
return filekeys;
}
return addfileURLs(filekeys, opt_keynum);
});
};
// 非同期で1ファイルダウンロードし、zipへ組み込みする関数
const downloadFile = (zip, url, filename) => {
return new kintone.Promise((resolve, reject) => {
// getbinarycontentは 非同期でURLからファイルを取得するAPI
// JSZIP util APIを利用
JSZipUtils.getBinaryContent(url, (err, data) => {
if (err) {
reject(err);
}
zip.file(filename, data, {binary: true});
resolve(data);
});
});
};
// 複数ファイルのダウンロードを実行する関数
const downloadFiles = (files, zip, filenum) => {
const opt_zip = zip || new JSZip();
let opt_filenum = filenum || 0;
return downloadFile(opt_zip, files[opt_filenum].blobUrl, files[opt_filenum].name).then((data) => {
opt_filenum++;
if (opt_filenum === files.length) {
return opt_zip;
}
return downloadFiles(files, opt_zip, opt_filenum);
});
};
// ファイルをzip化する関数
const doZipFile = (zip) => {
return zip.generateAsync({type: 'blob'});
};
// ファイルを保存する関数
const saveZipFile = (content) => {
// FileSaver.jsを利用して保存
return saveAs(content, 'example.zip');
};
// ボタンクリック時に呼び出される関数
const getZipFile = () => {
getAppRecords()
.then(getFileKeys)
.then(checkFileSize)
.then(addfileURLs)
.then(downloadFiles)
.then(doZipFile)
.then(saveZipFile)
.catch((error) => {
alert(error);
});
};
// レコード一覧画面にボタンを配置
kintone.events.on('app.record.index.show', (e) => {
// 増殖バグ対策
if (document.getElementById('menuButton') !== null) {
return;
}
const menuButton = document.createElement('button');
menuButton.id = 'menuButton';
menuButton.textContent = '一括ダウンロード!!';
menuButton.addEventListener('click', () => {
getZipFile();
});
kintone.app.getHeaderMenuSpaceElement().appendChild(menuButton);
});
})();
|
一覧画面に一括ダウンロードボタンを配置し、ボタンの押されたタイミングでgetZipFile関数が呼ばれます。
このgetZipFiles関数にてプロミスチェーンを実装し、処理順序を明確にしています。
プロミスを利用すると処理順序が明確になり、非同期処理を簡単に扱えます。
また、then()
に関数を渡すと、関数の第一引数に前のプロミスの戻り値の内容が渡されるしくみを利用しています。
これにより、getFileKeysにレコードの一括取得(getAppRecords)の結果のオブジェクトが渡り、filekeyの一覧を抽出しています。
その後も同様に処理を重ねて、必要な情報を後処理に渡してつなげていきます。
ソース上部のfieldCodeの値を添付ファイルのフィールドコードに変更する必要があります。
変更した後、JSファイル(以下例ではdownload.js)をアップロードしましょう。
- ファイル名に日本語を含んでいる場合、文字化けすることがあります。
- 本サンプルではレコードの取得は100件までとなっています。
- 本サンプルはサブテーブルの添付ファイルには対応していません。
- FileSaver.jsの制限事項として、ブラウザーによってファイルサイズに上限があります。
- エラー処理が不十分のため、実装の際はご留意ください。
今回は一括ダウンロードの手法を紹介しました。
最低限の機能実装でも結構なボリュームがありますが、この後レコードごとにフォルダーを分けたり、ファイル名を可変にしたり、
ファイルのダウンロード中はspin.jsを利用するなどいろいろな拡張ができると思います。
いろいろなカスタマイズを行い、ぜひともコミュニティへご投稿ください!