File Preview Modal in Lightning Web Component Salesforce ☁️⚡️

 In this blog I will share the code to preview files in a Modal in Lightning Web Component.

There are three component in this example - 

1. PreviewFileThumbnails - The parent component to upload the file.

2. PreviewFileThumbnailCard - Child component to display image preview in a thumbnail gallery view.

3. PreviewFileModal - Child component to display selected thumbnail file in a new modal popup for preview. 


Make sure to change below PDF setting in Salesforce org to see the preview in browser otherwise it will download the file instead of preview : 


Go to Setup --> Security --> File Upload and Download Security

Hit edit and update the .pdf file settings to "Execute in Browser" as shown in the image below : 



You may follow the code below now.

PreviewFileThumbnails.html

 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
<template>
  <lightning-file-upload
    name="fileUploader"
    accept={acceptedFormats}
    record-id={recordId}
    onuploadfinished={handleUploadFinished}
    multiple
  >
  </lightning-file-upload>

  <br />
  <div
    class="
      slds-page-header__row
      slds-var-m-top_x-small
      slds-var-m-left_medium
      slds-grid slds-wrap
    "
  >
    <ul class="slds-grid slds-wrap slds-gutters">
      <template if:true={loaded}>
        <template for:each={files} for:item="file">
          <c-preview-file-thumbnail-card
            key={file.Id}
            file={file}
            record-id={recordId}
            thumbnail={file.thumbnailFileCard}
          ></c-preview-file-thumbnail-card>
        </template>
      </template>
    </ul>
  </div>
</template>


PreviewFileThumbnails.js

 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
import { LightningElement, wire, api, track } from "lwc";
import { refreshApex } from "@salesforce/apex";
import { ShowToastEvent } from "lightning/platformShowToastEvent";
import getFileVersions from "@salesforce/apex/FileController.getVersionFiles";

export default class PreviewFileThumbnails extends LightningElement {
  loaded = false;
  @track fileList;
  @api recordId;
  @track files = [];
  get acceptedFormats() {
    return [".pdf", ".png", ".jpg", ".jpeg"];
  }

  @wire(getFileVersions, { recordId: "$recordId" })
  fileResponse(value) {
    this.wiredActivities = value;
    const { data, error } = value;
    this.fileList = "";
    this.files = [];
    if (data) {
      this.fileList = data;
      for (let i = 0; i < this.fileList.length; i++) {
        let file = {
          Id: this.fileList[i].Id,
          Title: this.fileList[i].Title,
          Extension: this.fileList[i].FileExtension,
          ContentDocumentId: this.fileList[i].ContentDocumentId,
          ContentDocument: this.fileList[i].ContentDocument,
          CreatedDate: this.fileList[i].CreatedDate,
          thumbnailFileCard:
            "/sfc/servlet.shepherd/version/renditionDownload?rendition=THUMB720BY480&versionId=" +
            this.fileList[i].Id +
            "&operationContext=CHATTER&contentId=" +
            this.fileList[i].ContentDocumentId,
          downloadUrl:
            "/sfc/servlet.shepherd/document/download/" +
            this.fileList[i].ContentDocumentId
        };
        this.files.push(file);
      }
      this.loaded = true;
    } else if (error) {
      this.dispatchEvent(
        new ShowToastEvent({
          title: "Error loading Files",
          message: error.body.message,
          variant: "error"
        })
      );
    }
  }
  handleUploadFinished(event) {
    const uploadedFiles = event.detail.files;
    refreshApex(this.wiredActivities);
    this.dispatchEvent(
      new ShowToastEvent({
        title: "Success!",
        message: uploadedFiles.length + " Files Uploaded Successfully.",
        variant: "success"
      })
    );
  }
}


PreviewFileThumbnailCard.html

 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
<template>
  <template if:true={file}>
    <c-preview-file-modal
      url={file.downloadUrl}
      file-extension={file.Extension}
    ></c-preview-file-modal>
    <li
      class="
        slds-col
        slds-var-p-vertical_x-small
        slds-small-size_1-of-2
        slds-medium-size_1-of-4
        slds-large-size_1-of-6
      "
    >
      <div
        class="slds-file slds-file_card slds-has-title"
        style="width: 14rem"
        onclick={filePreview}
      >
        <figure>
          <a class="slds-file__crop slds-file__crop_4-by-3 slds-m-top_x-small">
            <img src={thumbnail} alt={file.title} onclick={filePreview} />
          </a>
          <figcaption class="slds-file__title slds-file__title_card">
            <div class="slds-media slds-media_small slds-media_center">
              <lightning-icon
                icon-name={iconName}
                alternative-text={file.Title}
                title={file.Title}
                size="xx-small"
              >
                <span class="slds-assistive-text"
                  >{file.Title}.{file.Extension}</span
                >
              </lightning-icon>
              <div class="slds-media__body slds-var-p-left_xx-small">
                <span class="slds-file__text slds-truncate" title={file.Title}
                  >{file.Title}.{file.Extension}</span
                >
              </div>
            </div>
          </figcaption>
        </figure>

        <div class="slds-is-absolute" style="top: 3px; right: 5px; z-index: 10">
          <lightning-button-menu
            alternative-text="Show File Menu"
            variant="border-filled"
            icon-size="xx-small"
          >
            <lightning-menu-item
              value="preview"
              label="Preview"
            ></lightning-menu-item>
            <lightning-menu-item
              value="delete"
              label="Delete"
            ></lightning-menu-item>
          </lightning-button-menu>
        </div>
      </div>
    </li>
  </template>
</template>


PreviewFileThumbnailCard.js

 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
import { LightningElement, api } from "lwc";

export default class PreviewFileThumbnailCard extends LightningElement {
  @api file;
  @api recordId;
  @api thumbnail;

  get iconName() {
    if (this.file.Extension) {
      if (this.file.Extension === "pdf") {
        return "doctype:pdf";
      }
      if (this.file.Extension === "ppt") {
        return "doctype:ppt";
      }
      if (this.file.Extension === "xls") {
        return "doctype:excel";
      }
      if (this.file.Extension === "csv") {
        return "doctype:csv";
      }
      if (this.file.Extension === "txt") {
        return "doctype:txt";
      }
      if (this.file.Extension === "xml") {
        return "doctype:xml";
      }
      if (this.file.Extension === "doc") {
        return "doctype:word";
      }
      if (this.file.Extension === "zip") {
        return "doctype:zip";
      }
      if (this.file.Extension === "rtf") {
        return "doctype:rtf";
      }
      if (this.file.Extension === "psd") {
        return "doctype:psd";
      }
      if (this.file.Extension === "html") {
        return "doctype:html";
      }
      if (this.file.Extension === "gdoc") {
        return "doctype:gdoc";
      }
    }
    return "doctype:image";
  }

  filePreview() {
    console.log("###Click");
    const showPreview = this.template.querySelector("c-preview-file-modal");
    showPreview.show();
  }
}


PreviewFileModal.html

 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
<template>
  <template if:true={showModal}>
    <section
      role="dialog"
      tabindex="-1"
      aria-labelledby="modal-heading-01"
      aria-modal="true"
      aria-describedby="modal-content-id-1"
      class="slds-modal slds-fade-in-open"
    >
      <div class="slds-modal__container">
        <!-- Header Start -->
        <header class="slds-modal__header">
          <lightning-button-icon
            class="slds-modal__close"
            title="Close"
            icon-name="utility:close"
            icon-class="slds-button_icon-inverse"
            onclick={closeModal}
          ></lightning-button-icon>

          <h2 id="id-of-modalheader-h2" class="slds-text-heading_large">
            File Preview
          </h2>
        </header>
        <!-- Header End -->
        <div class="slds-modal__content" id="modal-content-id-1">
          <lightning-layout>
            <lightning-layout-item flexibility="auto">
              <article class="slds-card">
                <div
                  class="slds-card__body slds-card__body_inner"
                  style="margin: 0"
                >
                  <template if:false={showFrame}>
                    <img src={url} />
                  </template>
                  <template if:true={showFrame}>
                    <iframe
                      src={url}
                      style="width: 100%; height: 800px"
                    ></iframe>
                  </template>
                </div>
              </article>
            </lightning-layout-item>
          </lightning-layout>
        </div>
        <footer class="slds-modal__footer slds-grid slds-grid_align-spread">
          <lightning-button
            variant="brand-outline"
            label="Cancel"
            onclick={closeModal}
            title="Cancel"
            class="slds-var-m-left_x-small"
          ></lightning-button>
        </footer>
      </div>
    </section>
    <div class="slds-backdrop slds-backdrop_open"></div>
  </template>
</template>


PreviewFileModal.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import { LightningElement, api } from "lwc";

export default class PreviewFileModal extends LightningElement {
  @api url;
  @api fileExtension;
  showFrame = false;
  showModal = false;
  @api show() {
    console.log("###showFrame : " + this.fileExtension);
    if (this.fileExtension === "pdf") this.showFrame = true;
    else this.showFrame = false;
    this.showModal = true;
  }
  closeModal() {
    this.showModal = false;
  }
}


PreviewFileModal.css

1
2
3
4
.slds-modal__container {
  width: 90% !important;
  max-width: 90% !important;
}


FileController.cls

 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
public with sharing class FileController{
@AuraEnabled(cacheable=true)
    public static List<ContentVersion> getVersionFiles(String recordId){
        try {
            return [
		SELECT
		Id,
                Title,
                ContentDocumentId,
                FileType, 
		ContentSize,
		FileExtension,
		VersionNumber,
		CreatedDate,
		VersionData,
                FirstPublishLocationId
		FROM ContentVersion
		WHERE FirstPublishLocationId =:recordId
			ORDER BY CreatedDate DESC
		];
        
        } catch (Exception e) {
            throw new AuraHandledException(e.getMessage());
        }
    }
}


Output


Checkout the complete tutorial below

 If you have any question please leave a comment below.

If you would like to add something to this post please leave a comment below.
Share this blog with your friends if you find it helpful somehow !

Thanks
Happy Coding :)

Post a Comment

3 Comments

  1. Added data-id = contentDocumentId on the div as an attribute

    "div class="slds-file slds-file_card slds-has-title" style="width: 14rem" data-id={file.ContentDocumentId}
    onclick={previewHandler}>
    "
    , but when fetching it in the onclick function as e.target.dataset.id its coming as undefined, even though i can see the value is populated correctly in html.

    console.log(event.target.dataset.id)
    undefined.

    Any idea why ?

    ReplyDelete
    Replies
    1. Got it to work. Used event.currentTarget.dataset.id instead event.target.dataset.id

      Delete
    2. Awesome, thanks for sharing it here !

      Delete