kintoneの添付ファイルを一括ダウンロードする

kintoneで添付ファイルを活用していると、「複数ファイルを一括でダウンロードしたい」「画像ファイルをすぐ保存したい」といったニーズが生まれがちです。
しかし、実際には1ファイルずつのダウンロードしかできず、特に画像ファイルはビューアが自動表示されてしまうため、ダウンロード操作が煩雑になります。

この記事では、そうした課題をJavaScriptカスタマイズで解決する方法を紹介します。
1レコードに添付された複数ファイルをまとめてダウンロードするための仕組みと、kintone APIの仕様上の注意点(fileKeyの扱い、API制約)を含めて詳しく解説します。また、すぐに使える汎用的なJavaScriptファイルも添付しますので、カスタマイズ初心者もぜひ挑戦してみてください。

はじめに:kintoneのファイルダウンロードの仕様と課題

kintoneでは、レコードに添付されたファイルは「fileKey」と呼ばれる一時的なIDで管理されます。
ファイルをダウンロードするには、まずレコードからこのfileKeyを取得し、次に専用のAPIを通じてファイル本体を取得する、という2段階の処理が必要です。

ここで注意すべき点は、kintoneのファイルダウンロードAPI(/k/v1/file.json)は、他のAPIと違い、kintone.api()では呼び出せないという点です。
そのため、fetch()XMLHttpRequestなど、別の方法でHTTPリクエストを送る必要があります。

本記事では、この仕様をふまえた上で、レコードに登録された複数の添付ファイルをワンクリックでダウンロードできるカスタマイズ方法を紹介します。
kintoneの柔軟性を活かして、日常業務のファイル処理を効率化する一助になれば幸いです。

JavaScriptによるカスタマイズ

本カスタマイズは、kintoneのレコード詳細画面を開いたときに、画面上部に「一括ダウンロード」ボタンを追加し、クリックするとそのレコード内にある添付ファイルをすべて一括で保存できるというものです。

本記事では、PCでの動作(モバイル版は対象外)を前提としてスクリプトを記述していきます。

レコード詳細画面に「一括ダウンロード」ボタンを追加する

まずはその出発点として、app.record.detail.show イベントを利用し、レコード詳細画面が表示されたときに実行される処理を定義します。

    kintone.events.on('app.record.detail.show', function(event) {
        record = event.record;
        if (document.getElementById('download_button')) return;
        // ここから、以降の処理を記述  
    });

イベントハンドラ内では、まず既にボタンが設置されていないかどうかをgetElementById()で確認します。これは、ボタンが重複して表示されてしまうことを防ぐためです。イベントによっては必要ないこともありますが、癖にしてしまう方が安全です。

ボタン要素の作成と画面への追加

次に、document.createElement() を使ってダウンロード用のボタンを作成します。ボタンには一意のID(download_button)と表示テキスト(一括ダウンロード)を設定しています。

        var dlbutton = document.createElement('button');
        dlbutton.id = 'download_button';
        dlbutton.innerText = '一括ダウンロード';
        
        //ここにボタンを押したときの処理を記述
        
        kintone.app.record.getHeaderMenuSpaceElement().appendChild(dlbutton);
        return event;

このボタンは、kintone.app.record.getHeaderMenuSpaceElement() を使って、レコード詳細画面のヘッダメニュー領域に追加されます。

ボタンを押したら添付ファイルを取得する

ボタンがクリックされた際に実行される処理は、async functionとして定義します。これは、後続のfetch()によるファイル取得処理が非同期であるためです。

        dlbutton.onclick = async function () {
             for (const field of Object.keys(record)) {
                if (record[field].type === "FILE") {
                    // ここに添付ファイルフィールド毎の処理を記述
                }
             }              
        };

まず、レコード内のすべてのフィールドを走査し、型が "FILE" であるフィールド(添付ファイルフィールド)を抽出します。これにより、1つのレコード内に複数の添付フィールドが存在しても、それらすべてを一括対象に含めることが可能になります。

fileKeyからファイルをダウンロード

一つの添付ファイルフィールドには複数ファイルが登録できるのでそれら一つ一つのファイルを処理していきます。各ファイルについては、まずfileKeyとファイル名を取得します。kintoneのファイルは、レコードデータの中では実体ではなく、fileKeyという一時的な識別子として管理されています。

このfileKeyを使って、kintone.api.urlForGet('/k/v1/file.json', {fileKey}, true) を呼び出すことで、実際のファイルダウンロード用のURLを取得します。

                    for (const file of record[field].value) {
                        const fileKey = file.fileKey;
                        const filename = file.name;
                        const dlurl = kintone.api.urlForGet('/k/v1/file.json', {fileKey: fileKey}, true);
                        const headers = {'X-Requested-With': 'XMLHttpRequest',};
                        const resp = await fetch(dlurl, {method: 'GET', headers});
                        const blob = await resp.blob();
                        //ここにダウンロードを実装
                    }              

注意点として、このAPIは kintone.api() で直接呼び出すことができない仕様になっているため、fetch()XMLHttpRequest を使って明示的にHTTPリクエストを送る必要があります。

そのため、fetch() を使ってバイナリデータを取得し、.blob() でJavaScript上で扱えるファイルオブジェクトに変換しています。

ブラウザ上でのダウンロードをトリガー

取得したファイルのBlobオブジェクトは、一時的なURLに変換され、ブラウザ上で <a> タグを使ってダウンロードリンクとして扱うことができます。

このとき、リンクの download 属性にファイル名を指定し、click() を実行することで、ユーザーの操作なしにファイルのダウンロードが可能です。ブラウザの設定によってファイルの保存ダイアログで保存ファイル名を指定することもできますが、ブラウザ任せでも一意でないファイル名のファイルをダウンロードフォルダに保存することができて便利です。

                        let link = document.createElement('a');
                        link.download = filename;
                        link.href = URL.createObjectURL(blob);
                        link.click();

これをすべてのファイルに対して繰り返すことで、ユーザーは一つのボタンを押すだけで、すべての添付ファイルを一括で保存できるようになります。

実装上の注意点と補足

  • URL.createObjectURL(blob) で生成したURLは、使用後に URL.revokeObjectURL() で明示的に破棄することでメモリリークを防ぐことができます。本実装では省略していますが、大量ファイルを扱う場合は追加を検討してください。
  • 添付ファイルがサブテーブル内にある場合は、本実装では未対応です。サブテーブルの構造を理解した上でファイルをもれなく処理する必要があります。
  • 関連レコードで表示されている添付ファイルは、本実装では未対応です。eventオブジェクトに含まれるレコード情報以外に対象アプリからレコードを取得する必要があります。

すぐに使えるJavaScriptソースコードとファイル

ソースコードの全体は下の通りとなります。

(function(){

    kintone.events.on('app.record.detail.show', function(event) {
        record = event.record;
        if (document.getElementById('download_button')) return;
        var dlbutton = document.createElement('button');
        dlbutton.id = 'download_button';
        dlbutton.innerText = '一括ダウンロード';
        dlbutton.onclick = async function () {
            for (const field of Object.keys(record)) {
                if (record[field].type === "FILE") {
                    for (const file of record[field].value) {
                        //ファイルを取得
                        const fileKey = file.fileKey;
                        const filename = file.name;
                        const dlurl = kintone.api.urlForGet('/k/v1/file.json', {fileKey: fileKey}, true);
                        const headers = {'X-Requested-With': 'XMLHttpRequest',};
                        const resp = await fetch(dlurl, {method: 'GET', headers});
                        const blob = await resp.blob();
                        //ダウンロード実行
                        let link = document.createElement('a');
                        link.download = filename;
                        link.href = URL.createObjectURL(blob);
                        link.click();
                    }
                }
            };
        };
    
        kintone.app.record.getHeaderMenuSpaceElement().appendChild(dlbutton);
        return event;
    });
    
})();

アプリ固有の情報は含まれていませんので、JavaScriptファイルを追加するだけでレコード詳細画面でのファイル一括ダウンロードが実装できます。

まとめ

このように、kintoneの標準仕様では対応しきれない「一括ダウンロード」の課題を、簡単なJavaScriptによるカスタマイズで解決することができます。
fileKeyの扱いやAPIの制約を正しく理解することで、他の応用的なファイル操作カスタマイズにも展開できるベースとなるでしょう。


当社では、サイボウズ公式パートナーとしてkintoneの導入支援・伴走支援を行うほか、既存サービスとの連携・サブシステム開発・アプリの個別カスタマイズを含めたシステム構築を得意としています。お客様のビジネスニーズに合わせた最適なソリューションを提供し、業務効率化と生産性向上を実現します。ぼんやりとした課題から詳細なご相談まで応じますので、ぜひ以下のリンクからお問い合わせフォームにご記入ください。

PAGE TOP