Overview
This article introduces how to bulk download attachment files from multiple records in an App, using the
JSZip
library. This is especially useful for users who have stored many files in a Kintone App, and wish to download all the files at once.
The Bulk Download Flow
The bulk download process from Kintone is as follows:
To download attachment files from Kintone, fileKeys need to be obtained from the records using the
Get Records API. The
Download File API can then be used, using the fileKey as the parameter value.
Note that the steps Get Records, Create fileKey list, and Create URLs need to run asynchronously.
Prepare the App
Create an App
with the following field settings.
Field Type |
Field Name |
Field Code |
Attachment |
Attachment |
Attachment |
Set the Libraries
This sample uses
JSZip
v3.1.4,
JSZipUtils
v0.0.2 and
FileSaver.js
v 1.0.0. Set the following files and URL into the App's
JavaScript and CSS Customization settings
.
Sample Code
Prepare the following JavaScript code in a text editor and navigate to the Kintone App's settings.
Upload the file into the Upload JavaScript for PC option of the
JavaScript and CSS Customization settings
.
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
|
/* global JSZip */
/* global JSZipUtils */
/* global saveAs */
// /
// / 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
// /
(function() {
'use strict';
var fieldCode = 'Attachment'; // Field code of Attachment field
var isGuestSpace = false;
// Get records function(async) - limit of 100 records
function getAppRecords() {
var url = kintone.api.url('/k/v1/records', isGuestSpace);
var appId = kintone.app.getId();
var condition = kintone.app.getQueryCondition() || '';
var query = condition + 'order by $id asc';
var body = {
'app': appId,
'query': query,
'field': fieldCode
};
return kintone.api(url, 'GET', body);
}
// Extract and list fileKeys
function getFileKeys(json) {
var keys = [];
for (var i = 0; i < json.records.length; i++) {
var filetype = json.records[i][fieldCode];
// When multiple files are attached
for (var j = 0; j < filetype.value.length; j++) {
keys.push(filetype.value[j]);
}
}
return keys;
}
// Check file sizes
function checkFileSize(filekeys) {
if (filekeys.length === 0) {
return kintone.Promise.reject('Attachements were not found.');
}
var totalsize = 0;
for (var i = 0; i < filekeys.length; i++) {
totalsize += parseInt(filekeys[i].size, 10);
}
if (totalsize < 999) { // less than 1KB
totalsize = String(totalsize);
} else if (totalsize < 999999) { // less than 1MB
totalsize = parseInt(totalsize / 1000, 10) + 'K';
} else if (totalsize < 999999999) { // less than 1GB
totalsize = parseInt(totalsize / 1000000, 10) + 'M';
} else {
// Limit to 1GB
return kintone.Promise.reject('File size is too large');
}
var dflag = confirm('Download ' + totalsize + ' bytes. Is it OK to proceed?');
if (!dflag) {
return kintone.Promise.reject('Download canceled');
}
return filekeys;
}
// Get file URL (async)
function addFileURL(key) {
return new kintone.Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
var params = {
'fileKey': key.fileKey
};
var url = kintone.api.urlForGet('/k/v1/file', params, isGuestSpace);
xhr.open('GET', url, true); // async
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.responseType = 'blob';
xhr.onload = function() {
if (xhr.status === 200) {
var blob = new Blob([xhr.response]);
var wurl = window.URL || window.webkitURL;
var blobUrl = wurl.createObjectURL(blob);
key.blobUrl = blobUrl; // Add URL to key record
resolve(key);
} else {
reject(JSON.parse(xhr.response));
}
};
xhr.send();
});
}
// Get multiple file URLs
function addfileURLs(filekeys, keynum) {
var opt_keynum = keynum || 0;
return addFileURL(filekeys[opt_keynum]).then(function(resp) {
opt_keynum++;
if (opt_keynum === filekeys.length) {
return filekeys;
}
return addfileURLs(filekeys, opt_keynum);
});
}
// Asynchronously download a file and add it to the zip file
function downloadFile(zip, url, filename) {
return new kintone.Promise(function(resolve, reject) {
// getBinaryContent is an API that retrieves files from URLs asynchronously
JSZipUtils.getBinaryContent(url, function(err, data) {
if (err) {
reject(err);
}
zip.file(filename, data, {binary: true});
resolve(data);
});
});
}
// Download multiple files
function downloadFiles(files, zip, filenum) {
var opt_zip = zip || new JSZip();
var opt_filenum = filenum || 0;
return downloadFile(opt_zip, files[opt_filenum].blobUrl, files[opt_filenum].name).then(function(data) {
opt_filenum++;
if (opt_filenum === files.length) {
return opt_zip;
}
return downloadFiles(files, opt_zip, opt_filenum);
});
}
// Zip the file
function doZipFile(zip) {
return zip.generateAsync({type: 'blob'});
}
// Save the file
function saveZipFile(content) {
// Use FileSaver.js
return saveAs(content, 'example.zip');
}
// Button click function
function getZipFile() {
getAppRecords()
.then(getFileKeys)
.then(checkFileSize)
.then(addfileURLs)
.then(downloadFiles)
.then(doZipFile)
.then(saveZipFile)
.catch(function(error) {
alert(error);
});
}
// Create a button in the Record List view
kintone.events.on('app.record.index.show', function(e) {
// For debug
if (document.getElementById('menuButton') !== null) {
return;
}
var menuButton = document.createElement('button');
menuButton.id = 'menuButton';
menuButton.innerHTML = 'Download at once';
menuButton.onclick = function() {
getZipFile();
};
kintone.app.getHeaderMenuSpaceElement().appendChild(menuButton);
});
})();
|
The JavaScript and CSS Customization settings should look like the following:
Attention
Caution:
The order in which JavaScript and CSS are uploaded to an App matters. In this example, ensure that the 3 libraries are uploaded before the sample JavaScript file. The order of the uploads can be changed by clicking and dragging on the arrows for each item on the Upload JavaScript / CSS page.
After saving the settings, update the App.
The source code places the button for the bulk download on the Record List view. When the button is clicked, the getZipFile
function is called.
getZipFile
starts a promise chain. This makes the order of the later processes clearer and handling of asynchronous processes easier. then()
is used to pass on the object obtained from getAppRecords from promise to promise, and a list of fileKeys is created from this object.
Test the Integration
Add a few records into the App, and attach files to the Attachment fields. Navigate to the Record List page of the App. A button should be displayed above the list of records. Click the button to start the download process.
Limitations
- In this sample, only data of up to 100 records will be downloaded as a Zip file.
- The sample does not support Attachment fields placed in
Table fields
.
- FileSaver.js has restrictions on file sizes, depending on what browser is being used.
Reference