目的

今回 Classic モードで Visualforce に LWC のでファイル一括ダウンロードする方法を共有します。

前提

jszip.js を静的リソースにアップロードする

LWCでのファイル一括ダウンロード方法

ソース構成図

force-app
   └─main
       └─default
             ├─aura
             │  └─LWCContainer
             │
             ├─classes
             │   ├─CommonHandler.cls
             │   └─CommonHandler.cls-meta.xml
             ├─lwc
             │  └─fileZipDemo
             │
             ├─pages
                 ├─fileZipDemo.page
                 └─fileZipDemo.page-meta.xml
  • Aura:LWCContainer

LWCでのファイル一括ダウンロード方法

<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

LWCでのファイル一括ダウンロード方法

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

LWCでのファイル一括ダウンロード方法

<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

LWCでのファイル一括ダウンロード方法

<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 タブを作成

LWC でのファイル一括ダウンロード方法

  • タブを開いて、ファイルを一括ダウンロードする

LWC でのファイル一括ダウンロード方法

LWC でのファイル一括ダウンロード方法

LWC でのファイル一括ダウンロード方法

参考