目的
今回 Classic モードで Visualforce に LWC のでファイル一括ダウンロードする方法を共有します。
前提
jszip.js を静的リソースにアップロードする
ソース構成図
force-app
└─main
└─default
├─aura
│ └─LWCContainer
│
├─classes
│ ├─CommonHandler.cls
│ └─CommonHandler.cls-meta.xml
├─lwc
│ └─fileZipDemo
│
├─pages
├─fileZipDemo.page
└─fileZipDemo.page-meta.xml
- Aura:LWCContainer
<aura:application access="GLOBAL" extends="ltng:outApp"> </aura:application>
<?xml version="1.0" encoding="UTF-8"?>
<AuraDefinitionBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>50.0</apiVersion>
<description>A Lightning Application Bundle</description>
</AuraDefinitionBundle>
({
myAction: function (component, event, helper) {},
});
- class:CommonHandler
public with sharing class CommonHandler {
/**
*セッションIdを取得
*/
@AuraEnabled
public static String getSessionId() {
try{
return UserInfo.getSessionId();
} catch (Exception e) {
throw new AuraHandledException(e.getMessage());
}
}
/**
* 選択された一括ダウンロード
* @param parameterJson: contentDocumentIds
* @return results ダウンロード情報
*/
@AuraEnabled
public static List <Map <String, String>> doDownloadFile(String parameter){
List<Map<String, String>> results = new List<Map<String, String>>();
String tempalteUrl = '/services/data/v51.0/sobjects/ContentVersion/{0}/VersionData';
try {
List<String> contentVersionIds = (List<String>) JSON.deserialize(parameter, List<String>.class);
if (contentVersionIds.size() > 0){
List<ContentVersion> contentVersions = [SELECT
Id,
Title,
FileExtension,
ContentSize
FROM
ContentVersion
WHERE
Id IN :contentVersionIds
AND IsLatest = true
];
for(ContentVersion contentVersion: contentVersions) {
Map<String, String> obj = new Map<String, String>();
obj.put('name', contentVersion.Title + '.' + contentVersion.FileExtension);
List<String> Ids = new List<String>();
Ids.add(contentVersion.Id);
obj.put('url', String.format(tempalteUrl, Ids));
results.add(obj);
}
}
if (results.size() > 0)
return results;
return null;
} catch (Exception e) {
throw new AuraHandledException(e.getMessage());
}
}
/**
* ファイル一覧を取得
*/
@AuraEnabled
public static List<ContentVersion> getFileList(){
try {
List<ContentVersion> contentVersions = [SELECT
Id,
Title,
FileExtension,
ContentSize
FROM
ContentVersion
];
if(contentVersions.size() > 0)
return contentVersions;
return null;
} catch (Exception e) {
throw new AuraHandledException(e.getMessage());
}
}
<?xml version="1.0" encoding="UTF-8"?>
<ApexClass xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>52.0</apiVersion>
<status>Active</status>
</ApexClass>
- LWC:fileZipDemo
<template>
<template if:true="{loading}">
<lightning-spinner
alternative-text="Loading"
size="medium"
></lightning-spinner>
</template>
<lightning-card>
<div class="slds-p-horizontal_small">
<div class="slds-form">
<div class="slds-form__row">
<div class="slds-form__item">
<button
class="slds-button slds-button_brand"
onclick="{downloadFile}"
>
一括ダウンロード
</button>
</div>
</div>
</div>
</div>
<div slot="footer">
<lightning-datatable key-field="Id" columns="{columns}" data="{datas}">
</lightning-datatable>
</div>
</lightning-card>
</template>
import { LightningElement, track } from "lwc";
import { ShowToastEvent } from "lightning/platformShowToastEvent";
import { loadScript } from "lightning/platformResourceLoader";
import jszip from "@salesforce/resourceUrl/jszip";
import getSessionId from "@salesforce/apex/CommonHandler.getSessionId";
import doDownloadFile from "@salesforce/apex/CommonHandler.doDownloadFile";
import getFileList from "@salesforce/apex/CommonHandler.getFileList";
const zipFileNamePrefix = "zipFileNamePrefix";
export default class FileZipDemo extends LightningElement {
@track columns;
@track datas;
@track loading;
/**
* メッセージ表示
* @param {window} that
* @param {string} title タイトール
* @param {string} message メッセージ
* @param {string} variant タイプ info、success、warning、error
*/
showToast(title, message, variant) {
const event = new ShowToastEvent({
title: title,
message: message,
variant: variant,
});
this.dispatchEvent(event);
}
/**
* ファイルサイズ変換
* @param {*} size バイト
* @returns 変換後のサイズ
*/
fileSizeUnit(size) {
// 1 KB = 1024 Byte
const kb = 1024;
const mb = Math.pow(kb, 2);
const gb = Math.pow(kb, 3);
const tb = Math.pow(kb, 4);
const pb = Math.pow(kb, 5);
const round = (size, unit) => {
return Math.round((size / unit) * 100.0) / 100.0;
};
if (size >= pb) {
return round(size, pb) + "PB";
} else if (size >= tb) {
return round(size, tb) + "TB";
} else if (size >= gb) {
return round(size, gb) + "GB";
} else if (size >= mb) {
return round(size, mb) + "MB";
} else if (size >= kb) {
return round(size, kb) + "KB";
}
return size + "バイト";
}
/**
* デートフォマート
* @param {Date} date date
* @param {string} fmt format
* @returns {string} StringDate
*/
dateFormat(date, fmt = "YYYY/mm/dd") {
let ret;
const opt = {
"Y+": date.getFullYear().toString(), // 年
"m+": (date.getMonth() + 1).toString(), // 月
"d+": date.getDate().toString(), // 日
"H+": date.getHours().toString(), // 時
"M+": date.getMinutes().toString(), // 分
"S+": date.getSeconds().toString(), // 秒
};
for (let k in opt) {
ret = new RegExp("(" + k + ")").exec(fmt);
if (ret) {
fmt = fmt.replace(
ret[1],
ret[1].length == 1 ? opt[k] : opt[k].padStart(ret[1].length, "0")
);
}
}
return fmt;
}
/**
* 添付ファイルダウンロード
*/
async downloadFile(e) {
try {
e.preventDefault();
this.loading = true;
//画面選択項目のレコードIdを取得
let targets = this.template
.querySelector("lightning-datatable")
.getSelectedRows();
if (targets.length > 0) {
let targetIds = [];
targets.forEach((e) => {
targetIds.push(e.Id);
});
let results = await doDownloadFile({
parameter: JSON.stringify(targetIds),
});
if (results) await this.allDownload(results);
else this.showToast("", "添ファイルが存在しません。", "warning");
} else {
this.showToast("", "ファイルを選択してください。", "warning");
}
} catch (err) {
console.error("Error: " + err);
this.showToast("", err.body.message, "error");
} finally {
this.loading = false;
}
}
/**
* resultsにより、ファイルを一括ダウンロード
* @param {*} results
*/
async allDownload(results) {
try {
let sessionId = await getSessionId();
console.time("downloadtime");
let result = await this.getNameContentPairsFrom(results, sessionId);
console.timeEnd("downloadtime");
console.time("ziptime");
let zipBlob = await this.generateZipBlob(result);
console.timeEnd("ziptime");
this.saveBlob(
zipBlob,
`${zipFileNamePrefix}${this.dateFormat(
new Date(),
"YYYYmmddHHMMSS"
)}.zip`
);
} catch (e) {
console.timeEnd("downloadtime");
console.error(e);
}
}
/**
* ファイル圧縮
* @param {*} nameContentPairs
*/
generateZipBlob(nameContentPairs) {
let zip = new JSZip();
nameContentPairs.forEach((nameContentPair) => {
zip.file(nameContentPair.name, nameContentPair.content);
});
return zip.generateAsync({
type: "blob",
compression: "DEFLATE",
compressionOption: {
level: 1,
},
});
}
/**
* RESTAPIにリクエストを出し、ファイル情報を取得
* @param {object} results
* @param {string} sessionId セッションID
*/
async getNameContentPairsFrom(results, sessionId) {
let promises = results.map(async (result) => {
let name = result.name;
let response = await fetch(result.url, {
headers: {
Authorization: `Bearer ${sessionId}`,
},
});
let content = await response.blob();
return { name, content };
});
let pairs = [];
for (let promise of promises) {
pairs.push(await promise);
}
return pairs;
}
/**
* ファイル保存
* @param {*} blob ファイルの中身
* @param {*} name ファイル名
*/
saveBlob(blob, name = undefined) {
if (window.navigator.msSaveBlob) {
if (name) window.navigator.msSaveBlob(blob, name);
else window.navigator.msSaveBlob(blob);
} else {
let a = document.createElement("a");
a.href = URL.createObjectURL(blob);
if (name) a.download = name;
a.style.display = "none";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
}
/**
* 初期化
*/
async connectedCallback() {
this.columns = [
{ label: "Id", fieldName: "Id" },
{ label: "ファイル名", fieldName: "Title" },
{ label: "拡張子", fieldName: "FileExtension" },
{ label: "ファイルサイズ", fieldName: "ContentSize" },
];
this.datas = await getFileList();
this.datas = this.datas.map((e, i) => {
return { ...e, ContentSize: this.fileSizeUnit(e.ContentSize) };
});
}
/**
* jszipロード
*/
renderedCallback() {
if (this.jsinit) return;
this.jsinit = true;
Promise.all([loadScript(this, jszip)])
.then(() => {
console.log("ライブラリロード成功");
})
.catch((error) => {
this.showToast("", "JSライブラリロードに失敗しました", "error");
});
}
}
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>52.0</apiVersion>
<isExposed>true</isExposed>
<targets>
<target>lightning__Tab</target>
</targets>
</LightningComponentBundle>
- page:FileZipDemo
<apex:page
standardStylesheets="false"
sidebar="false"
applyBodyTag="false"
docType="html-5.0"
>
<!-- lightning Design System -->
<apex:includeLightning />
<div id="lwc-container"></div>
<script>
$Lightning.use("c:LWCContainer", function () {
//AuraAppContainer
$Lightning.createComponent(
"c:fileZipDemo", //LWCコンポネント
{}, //lwcコンポネントに渡すパラメータ
"lwc-container", //divのid
function (cmp) {
//コールバック関数
console.log("Load Success:" + cmp);
}
);
});
</script>
</apex:page>
<?xml version="1.0" encoding="UTF-8"?>
<ApexPage xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>52.0</apiVersion>
<availableInTouch>false</availableInTouch>
<confirmationTokenRequired>false</confirmationTokenRequired>
<label>FileZipDemo</label>
</ApexPage>
Salesforce 側動作確認
- Salesforce 側 Visualforce タブを作成
- タブを開いて、ファイルを一括ダウンロードする
参考
How to use JSZip
stuk.github.io
Download the generated zip file
stuk.github.io
Extremely slow performance of generateAsync with 3.2.X JSZip versions · Issue #617 · Stuk/jszip · …