Developer

【Lightning Web Component】ファイルアップの自作をしてみました【コピペで作れる】

Developer
この記事は約10分で読めます。

こんにちは、アンダーソンです。
今回はLWCにて自作でのファイルアップロードを作成する機会があったので、
作成方法をまとめておきます。

スポンサーリンク

なぜ自作?

とあるクライアント様で標準のアップローダーだと

こんなポップアップが出るじゃないですか?それを見せたくないんです。

とご相談を受けましたので、LWCからのファイルアップロードを自作することになりました。

標準のおさらい

LWCではlightning-file-uploadを使用することで、標準と同じコンポーネントを作成することが可能となっています。
下記ドキュメントに載っているコンポーネントのマークアップです。

    <lightning-file-upload
        label="Attach receipt"
        name="fileUploader"
        accept="{acceptedFormats}"
        record-id="{myRecordId}"
        onuploadfinished="{handleUploadFinished}"
        multiple>
    </lightning-file-upload>

recordIdを指定すれば、そのレコードに関連する形でややこしいコンテンツファイルを自動で作成してくれる優れものです。
ファイルアップロードの制限も25ファイルまで一気に可能(multiple属性をつけておきましょう)ですし、トータル2GBまでOKとなります。
onuploadfinishedイベントを使えばアップ後のファイル名を変更するとかもできます。
この方法でする方が間違いなく良いと思います。

自作する選択肢はいつ選ぶべきか

標準コンポーネントの方が間違いなく有能なのですが、お客様によっては先述の
ポップアップ問題とかあります。あとメモ&添付ファイルをLEX環境から保存したいなんて時にもこの自作をする必要がありますかね。
ってことでコードを見ていこうと思います。

まず見た目

どのレコードに配置してもそのレコードの配下にファイルが保存されるようにしてます。
&すべて表示からはそのレコードの配下の添付ファイル一覧に飛ぶようにしました。

まずはJSファイルから

import { LightningElement, api } from 'lwc';
import { ShowToastEvent } from 'lightning/platformShowToastEvent'
import saveAttachment from '@salesforce/apex/FileController.saveAttachment';

const MAX_FILESIZE = 1500000

export default class FileUploader extends LightningElement {

    @api recordId
    @api objectApiName

    fileContents
    uploadedFile
    fileName
    contentType

    allViewUrl

    isLoading = false

    async connectedCallback(){
        this.allViewUrl = '/lightning/r/' + this.objectApiName + '/' + this.recordId + '/related/CombinedAttachments/view'
    }

    init(){
        this.filesUploaded = []
        this.fileName = ''
        this.isLoading = false
    }

    uploadFile(event){
        this.isLoading = true

        if ( event.target.files[0].size > MAX_FILESIZE ) {
            this.showNotification('ファイルサイズが大きすぎます', `ファイルサイズは${MAX_FILESIZE / 1000000}MBまでです。`, 'warning')
            this.init()
            return
        }
        this.filesUploaded = event.target.files
        this.fileName = event.target.files[0].name
        this.contentType = event.target.files[0].type
        
        this.saveAttachment();
    }

    saveAttachment(){
        let _this = this

        const reader = new FileReader();
        reader.onloadend = function() {
            this.fileContents = reader.result;
            var base64Mark = 'base64,';
            var dataStart = this.fileContents.indexOf(base64Mark) + base64Mark.length;
            this.fileContents = this.fileContents.substring(dataStart);
            saveAttachment({
                parentId: _this.recordId, 
                fileName: _this.fileName, 
                contentType: _this.contentType,
                base64Data: encodeURIComponent(this.fileContents)})
            .then(() => {
                _this.showNotification('成功', 'ファイルアップロードが完了しました。', 'success')
                _this.init()
            })
            .catch(e => {
                _this.showNotification('失敗', 'ファイルアップロードが失敗しました。システム管理者に問い合わせてください', 'error')
                console.error(e)
            })
        }
        reader.readAsDataURL(this.filesUploaded[0])
    }

    showNotification(title, message, variant) {
        const event = new ShowToastEvent({
            title: title,
            message: message,
            variant: variant,
        })
        this.dispatchEvent(event)
    }
}

次はHTML

<template>
    <lightning-spinner alternative-text="Loading" size="large" if:true={isLoading}></lightning-spinner>
    <lightning-card  variant="Narrow" title="メモ&添付ファイル" icon-name="standard:account">
        <div class="slds-text-align_center">
            <lightning-input type="file" label="ファイル添付" onchange={uploadFile}></lightning-input>
        </div>
        <p slot="footer">
            <a href={allViewUrl} target="_blank">すべて表示</a>
        </p>
    </lightning-card>
</template>

それからApex

public with sharing class FileController {
    @AuraEnabled
    public static void saveAttachment(String parentId, String fileName, String contentType, String base64Data){
        base64Data = EncodingUtil.urlDecode(base64Data, 'UTF-8');

        Attachment insertAttach = new Attachment();
        insertAttach.Name = fileName;
        insertAttach.ParentId = parentId;
        insertAttach.ContentType = contentType;
        insertAttach.Body = EncodingUtil.base64Decode(base64Data);

        insert insertAttach;
    }
}

それぞれ説明していくと長くなるので端的に。

まずJSファイルでは初期化処理としてすべて表示のURLを生成。
uploadFileメソッドでMAX_FILESIZE以下のファイルサイズかどうかを確認。

問題なければそれぞれの変数に値を格納して、saveAttachmentメソッドで
Apexを呼び出してファイルを保存します。

ファイルの読み込みに関しては、

const reader = new FileReader();
〜
reader.readAsDataURL(this.filesUploaded[0])

でファイルを読み込み、処理はこの部分。

reader.onloadend = function() {
            this.fileContents = reader.result;
            var base64Mark = 'base64,';
            var dataStart = this.fileContents.indexOf(base64Mark) + base64Mark.length;
            this.fileContents = this.fileContents.substring(dataStart);
            saveAttachment({
                parentId: _this.recordId, 
                fileName: _this.fileName, 
                contentType: _this.contentType,
                base64Data: encodeURIComponent(this.fileContents)})
            .then(() => {
                _this.showNotification('成功', 'ファイルアップロードが完了しました。', 'success')
                _this.init()
            })
            .catch(e => {
                _this.showNotification('失敗', 'ファイルアップロードが失敗しました。システム管理者に問い合わせてください', 'error')
                console.error(e)
            })
        }

となってます。

自作の場合、Apexに渡すStringの長さに上限があるのでMaxSizeをしておかないとエラーになるので要注意です。

コピペでできるのでニーズがあれば是非ご自分の環境で試してみてください。