How to preview files in Lightning Community using LWC

on

|

views

and

comments

Hi Everyone,

In this post, I am going to show you how to download and Preview the files from Lightning Community using Lightning Web Component.

Before we get started, let’s discuss the Object Structure of the files to know how the files get stored into Salesforce.

Content Document: – Represents a document that has been uploaded to a library in Salesforce CRM Content or Salesforce Files.

Content Version: – Represents a specific version of a document in Salesforce CRM Content or Salesforce Files. One Content Document can have multiple content versions.

Content Document Link:- Represents the link between a Salesforce CRM Content document or Salesforce file and where it’s shared. A file can be shared with other users, groups, records, and Salesforce CRM Content libraries.

Here is the complete diagram.

Files in Salesforce

This is how your component will look like once you have completed the development of the component.

Features

  1. Preview the Files in both Lightning Experience and Lighting Community
  2. Download the Files in both Lightning Experience and Lighting Community
  3. Delete the files
  4. Upload new files under the same record
  5. Filter the files based on created date and file title
  6. Sync the files from Salesforce if the file has been uploaded from a different place.
  7. Control what information needs to show in the table.

Limitations

  1. The only limitation of the component is, we can use it under the record detail page only.

Use Component inside Salesforce Record Page in Lightning Experience

To use the component, Open the record detail page of any object where you wanted to use this component. Click on gear icon and then click on Edit page.

In the left, search for content manager, drag and drop the component where you wanted to place.

It requires some additional information that your salesforce admin can setup. See below screenshot

Setup Component in Lighting Experience
  1. Title: – Title of the Lighting Card
  2. Record Id: – use recordId as value for Lighting Experience and {!recordId} if component is used in Lightning Community
  3. Do you want to show Details? – to display the file details. Keep as true
  4. Accepted file format to upload the files? – It has some default values. if you wanted to remove or add make the adjustment.
  5. User Can Sync the files from Salesforce? – If true it will show a button to sync the files from salesforce.
  6. Do you want the users to upload the new files? – If true, the user will be able to see the upload button.
  7. The component is used in the community? – Check this as true if the component is being used in the Salesforce Community.
  8. Show Filters? – If true, the user will be able to see the filters.

Let’s setup the code.

Complete Code

You can get the complete code from Here.

Create the Apex Class.

/**
 * @description       : 
 * @author            : Amit Singh
 * @group             : 
 * @last modified on  : 12-02-2020
 * @last modified by  : Amit Singh
 * Modifications Log 
 * Ver   Date         Author       Modification
 * 1.0   11-26-2020   Amit Singh   Initial Version
**/
public with sharing class ContentManagerService {
    
    @AuraEnabled
    public static String getContentDetails(String recordId) {
        List<ContentDocumentLink> contentDocumentList = [SELECT ContentDocumentId, LinkedEntityId 
                                                            FROM   ContentDocumentLink 
                                                            WHERE  LinkedEntityId =: recordId];
        Set<Id> contentDocumentId = new Set<Id>();
            
        for(ContentDocumentLink cdl : contentDocumentList){
            contentDocumentId.add(cdl.ContentDocumentId);
        }
            
        List<ContentVersion> contentVersionList = [SELECT Id, VersionData, FileType, Title, FileExtension,
                                                    ContentDocument.CreatedBy.Name, ContentDocument.ContentSize,
                                                    CreatedDate, ContentDocumentId, ContentDocument.FileType
                                                    FROM   ContentVersion 
                                                    WHERE  ContentDocumentId IN : contentDocumentId];
        return JSON.serialize(contentVersionList);
    }

    @AuraEnabled
    public static void deleteContentDocument(String recordId) {
        Database.delete(recordId);
    }

    public static string ContentType(String fileType) {

        switch on fileType.toLowerCase(){
            when 'docx' {
                return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document';
            }
            when 'csv' {
                return 'application/vnd.ms-excel';
            }
            when 'wav' {
                return 'audio/wav';
            }
            when 'wmv' {
                return 'video/x-ms-wmv';
            }
            when 'mp3' {
                return 'audio/mpeg';
            }
            when 'mp4' {
                return 'video/mp4';
            }
            when 'png' {
                return 'image/png';
                
            }
            when 'pdf' {
                return 'application/pdf';
                
            }
            when else {
                return 'image/jpeg';
            }
        }

    }
}

Create the Lightning Web Component and use below code.

Code for HTML file

<!--
    @description       : 
    @author            : Amit Singh
    @group             : 
    @last modified on  : 12-02-2020
    @last modified by  : Amit Singh
    Modifications Log 
    Ver   Date         Author       Modification
    1.0   11-26-2020   Amit Singh   Initial Version
-->
<template>
    <lightning-card  variant="Narrow"  title={title} icon-name="standard:document">
        <template if:true={isLoading}>
            <lightning-spinner alternative-text="Loading" size="small" variant="brand"></lightning-spinner>
        </template>
        <lightning-button if:true={showsync} variant="brand" title="Content Sync" icon-name="utility:sync"
            onclick={handleSync} slot="actions" >
        </lightning-button>
        <div class="slds-var-p-around_small" >
            <div class="slds-grid slds-wrap">
                <div class="slds-col slds-size_1-of-4" if:true={showFilters}>
                    <lightning-input if:true={showFilters} type="search" variant="standard" name="Title" label="Title" onchange={handleSearch} >
                    </lightning-input>
                </div>
                <div class="slds-col slds-size_1-of-4" if:true={showFilters}>
                    <lightning-input  if:true={showFilters} type="search" variant="standard" name="Created By" label="Created By" onchange={handleSearch}>\
                    </lightning-input>
                </div>
                <div class="slds-col slds-size_2-of-4 slds-var-p-left_small">
                    <lightning-file-upload  if:true={showFileUpload}
                        label="Upload New File"
                        name="fileUploader"
                        accept={accept}
                        record-id={recordId}
                        onuploadfinished={handleUploadFinished}
                        multiple>
                    </lightning-file-upload>
                </div>
            </div>
        </div>
        
        <div class="slds-var-p-around_small">
            <lightning-datatable
                key-field="id"
                data={dataList}
                hide-checkbox-column
                columns={columnsList}
                onrowaction={handleRowAction}>
            </lightning-datatable>
        </div>
    </lightning-card>
</template>

Code for javascript files.

import { api, LightningElement, track, wire } from 'lwc';
import getContentDetails from '@salesforce/apex/ContentManagerService.getContentDetails';
import deleteContentDocument from '@salesforce/apex/ContentManagerService.deleteContentDocument';
import { NavigationMixin } from 'lightning/navigation';

const columns = [
    { label: 'Title',       fieldName: 'Title', wrapText : true,
        cellAttributes: { 
            iconName: { fieldName: 'icon' }, iconPosition: 'left' 
        }
    },
    { label: 'Created By',  fieldName: 'CREATED_BY',
        cellAttributes: { 
            iconName: 'standard:user', iconPosition: 'left' 
        }
    },
    { label: 'File Size',   fieldName: 'Size' },
    { label: 'Preview', type:  'button', typeAttributes: { 
            label: 'Preview',  name: 'Preview',  variant: 'brand-outline',
            iconName: 'utility:preview', iconPosition: 'right'
        } 
    },
    { label: 'Download', type:  'button', typeAttributes: { 
            label: 'Download', name: 'Download', variant: 'brand', iconName: 'action:download', 
            iconPosition: 'right' 
        } 
    },
    { label: 'Delete', type:  'button', typeAttributes: { 
            label: 'Delete',   name: 'Delete',   variant: 'destructive',iconName: 'standard:record_delete', 
            iconPosition: 'right' 
        } 
    } 
];

export default class ContentManager extends NavigationMixin(LightningElement) {

    @api title;
    @api showDetails;
    @api showFileUpload;
    @api showsync;
    @api recordId;
    @api usedInCommunity;
    @api showFilters;
    @api accept = '.csv,.doc,.xsl,.pdf,.png,.jpg,.jpeg,.docx,.doc';

    @track dataList;
    @track columnsList = columns;
    isLoading = false;

    wiredFilesResult;

    connectedCallback() {
        this.handleSync();
    }

    getBaseUrl(){
        let baseUrl = 'https://'+location.host+'/';
        return baseUrl;
    }

    handleRowAction(event){

        const actionName = event.detail.action.name;
        const row = event.detail.row;
        switch (actionName) {
            case 'Preview':
                this.previewFile(row);
                break;
            case 'Download':
                this.downloadFile(row);
                break;
            case 'Delete':
                this.handleDeleteFiles(row);
                break;
            default:
        }

    }

    previewFile(file){
        
        if(!this.usedInCommunity){
            
            this[NavigationMixin.Navigate]({
                type: 'standard__namedPage',
                attributes: {
                    pageName: 'filePreview'
                },
                state : {
                    selectedRecordId: file.ContentDocumentId
                }
            });
        } else if(this.usedInCommunity){
            
            this[NavigationMixin.Navigate]({
                type: 'standard__webPage',
                attributes: {
                    url: file.fileUrl
                }
            }, false );
        }
        
    }

    downloadFile(file){
        this[NavigationMixin.Navigate]({
                type: 'standard__webPage',
                attributes: {
                    url: file.downloadUrl
                }
            }, false 
        );
    }

    handleDeleteFiles(row){

        this.isLoading = true;

        deleteContentDocument({
            recordId : row.ContentDocumentId
        })
        .then(result => {
            this.dataList  = this.dataList.filter(item => {
                return item.ContentDocumentId !== row.ContentDocumentId ;
            });
        })
        .catch(error => {
            console.error('**** error **** \n ',error)
        })
        .finally(()=>{
            this.isLoading = false;
        });
    }

    handleSync(){

        let imageExtensions = ['png','jpg','gif'];
        let supportedIconExtensions = ['ai','attachment','audio','box_notes','csv','eps','excel','exe',
                        'flash','folder','gdoc','gdocs','gform','gpres','gsheet','html','image','keynote','library_folder',
                        'link','mp4','overlay','pack','pages','pdf','ppt','psd','quip_doc','quip_sheet','quip_slide',
                        'rtf','slide','stypi','txt','unknown','video','visio','webex','word','xml','zip'];

        this.isLoading = true;
        getContentDetails({
            recordId : this.recordId
        })
        .then(result => {
            let parsedData = JSON.parse(result);
            let stringifiedData = JSON.stringify(parsedData);
            let finalData = JSON.parse(stringifiedData);
            let baseUrl = this.getBaseUrl();
            finalData.forEach(file => {
                file.downloadUrl = baseUrl+'sfc/servlet.shepherd/document/download/'+file.ContentDocumentId;
                file.fileUrl     = baseUrl+'sfc/servlet.shepherd/version/renditionDownload?rendition=THUMB720BY480&versionId='+file.Id;
                file.CREATED_BY  = file.ContentDocument.CreatedBy.Name;
                file.Size        = this.formatBytes(file.ContentDocument.ContentSize, 2);

                let fileType = file.ContentDocument.FileType.toLowerCase();
                if(imageExtensions.includes(fileType)){
                    file.icon = 'doctype:image';
                }else{
                    if(supportedIconExtensions.includes(fileType)){
                        file.icon = 'doctype:' + fileType;
                    }
                }
            });
            this.dataList = finalData;
        })
        .catch(error => {
            console.error('**** error **** \n ',error)
        })
        .finally(()=>{
            this.isLoading = false;
        });
    }

    handleUploadFinished(){
        this.handleSync();
        //eval("$A.get('e.force:refreshView').fire();");
    }
    formatBytes(bytes,decimals) {
        if(bytes == 0) return '0 Bytes';
        var k = 1024,
            dm = decimals || 2,
            sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'],
            i = Math.floor(Math.log(bytes) / Math.log(k));
        return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
    }

    handleSearch(event){
        let value = event.target.value;
        let name  = event.target.name;
        if( name === 'Title' ){
            this.dataList = this.dataList.filter( file => {
                return file.Title.toLowerCase().includes(value.toLowerCase());
            });
        } else if( name === 'Created By' ){
            this.dataList = this.dataList.filter( file => {
                return file.CREATED_BY.toLowerCase().includes(value.toLowerCase());
            });
        }
    }
}

Code for meta.xml file

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>50.0</apiVersion>
    <isExposed>true</isExposed>
    <masterLabel>Content Manager</masterLabel>
    <targets>
        <target>lightning__RecordPage</target>
        <target>lightning__UtilityBar</target>
        <target>lightningCommunity__Page</target>
        <target>lightningCommunity__Default</target>
    </targets>
    <!-- Configuring the design attributes -->
    <targetConfigs>
        <targetConfig targets="lightning__RecordPage, lightningCommunity__Default">
            <property name="title" type="String" label="Title" default="Content Manager" required="true"/>
            <property name="recordId" type="String" default="recordId" label="Record Id" required="true"/>
            <property name="showDetails" type="Boolean" default="true" label="Do you want to Show Details ?"/>
            <property name="accept" type="String" default=".csv,.doc,.xsl,.pdf,.png,.jpg,.jpeg,.docx,.doc" 
                    label="User can upload the files in format?" />
            <property name="showsync" type="Boolean" default="true" label="User can sync the files from Salesforce?" />
            <property name="showFileUpload" type="Boolean" default="true" label="Do you want the users to upload a new file ?"/>
            <property name="usedInCommunity" type="Boolean" default="false" label="Component is used in Community?" />
            <property name="showFilters" type="Boolean" default="true" label="Show Filters?" />
        </targetConfig>
    </targetConfigs>
</LightningComponentBundle>

Deploy the code to your salesforce org and Enjoy.

Thanks for reading 🙂

#HappyReading #DeveloperGeeks #AskPanther #SFDCPanther

Amit Singh
Amit Singhhttps://www.pantherschools.com/
Amit Singh aka @sfdcpanther/pantherschools, a Salesforce Technical Architect, Consultant with over 8+ years of experience in Salesforce technology. 21x Certified. Blogger, Speaker, and Instructor. DevSecOps Champion
Share this
0.5 out of 5

Leave a review

Excellent

SUBSCRIBE-US

Book a 1:1 Call

Must-read

How to Utilize Salesforce CLI sf (v2)

The Salesforce CLI is not just a tool; it’s the cornerstone of development on the Salesforce Platform. It’s your go-to for building, testing, deploying, and more. As one of the most important development tools in our ecosystem

Save the day of a Developer with Apex Log Analyzer

Table of Contents What is Apex Log Analyzer? Apex Log Analyzer, a tool designed with Salesforce developers in mind, is here to simplify and accelerate your...

Salesforce PodCast

Introduction Hey Everyone, Welcome to my podcast, the first-ever podcast in India for Salesforce professionals. Achievement We are happy to announce that we have been selected as Top...

Recent articles

More like this

78 COMMENTS

  1. Hello Panther, is there any possibility we can developer same UI, based on External links(Video,Image,png) using LWC or Lightning. the feature need to have like zoom in/out, option also?

  2. Hello sir,
    How to create new folder(with folder icon) using apex class in lightning web component.
    When clicking foder icon then their input field folder name when enter name of folder then cliking on save button then save in database.

  3. Thank you! Save me a bunch of development time.
    I had to tweak it a little (add a parameter to xml to adapt baseUrl) to handle preview&download for Community: it didn’t properly work in our multiple-community org. Would you happen to know a more robust way to download files rather than the semi-hardcoded url used here?

  4. Hi, i have troubles with the visibility of Files.
    I only see files i own. Also as Administrator.
    I think that because of the visibility flag on the ContentDocument Object.
    How did you solve this problem?

    As a service employee i want to see all Files on a Case not only the files i uploaded.

  5. I am Java Developer. Since i am new for sales force . My concept similar to this. That is how to display doc/docx file into UI components? using java/jsp . Can you explain how it possible using java ?

    • Hi Karthika,

      I do not think you can do this using Java inside Salesforce. But yes if you are developing the Java Application then you can show the same using JSP. And I do not have idea about JSP.

  6. Amazing code! Thank you for sharing this. Everything appears to be working (uploading files are showing in the Files related list) but they are not showing in the grid for me. I am the admin and have this in our Sandbox with me as the owner of the record and the file. Any ideas on why this might be happening for me?

  7. Actually my bad! I am getting an error in the console log.

    **** error ****

    h {status: 500, body: {…}, headers: {…}}
    body:
    exceptionType: “System.LimitException”
    isUserDefinedException: false
    message: “JSON string exceeds heap size limit”
    stackTrace: “Class.System.JSON.serialize: line 3, column 1\nClass

      • To resolve the error Remove “VersionData” From the below Query and it will work

        List contentVersionList = [SELECT Id, VersionData, FileType, Title, FileExtension,
        ContentDocument.CreatedBy.Name, ContentDocument.ContentSize,
        CreatedDate, ContentDocumentId, ContentDocument.FileType
        FROM ContentVersion
        WHERE ContentDocumentId IN : contentDocumentId];

  8. File preview solution doesnt work in communities..it redirects to a community page but displays invalid page.

    • If you are querying the file and their content it’s hard to improve there are ways like using content libraries. or you can use some javascript libraries for the same. I have not used any of there so not able to provide more insight on this.

  9. Hi , I am unable too see the file as guest user . Image is breaking whereas I can see with logged in user . I have enabled the salesforce CRM content for guest user and the object where file is attached is also public read-only for now .Can you please help .

    • So far I know, to display the file to the guest user you must need to make sure your file is visible by guest user using some trigger on content version or using content distribution user.

      Due to security issues the guest user can not see the images

  10. The preview does not work properly in LWR sites atleast. Only shows the first page of the document although the document having multiple pages.

  11. Hi, I have used this code and its working fine, but unfortunatly the document in the preview or download cloumn does not fit in to the title in the same row. It is mixed up between the rows.
    Do you have any ideas what is the problem?

  12. Hello,
    We would like to be able to transfer file (copy) from Content manager row to another Case. How it is better to to so? I tried use screen Flow but doesn’t work.
    Could you help me please?

  13. Hi,
    I have the similar requirement, but I want the preview feature in an Omnistudio/Vlocity Omniscript page. Is it possible or is there any workaround ?
    Thanks in advance.

LEAVE A REPLY

Please enter your comment!
Please enter your name here

please change titleHi Amit, Firstly, I have been following ever since I have started learning Salesforce. Thanks a lot for all the knowledge you have been sharing on Youtube/Blogs. I have a doubt regarding this article. I'm trying to build a personal portfolio, where in I want to allow users to Preview/download my resume. I thought I would create a static resource for my resume.pdf and may be I could preview/download it from my LWC Component. Do you think its possible to do so or is uploading a file on a record and fetching it is the only way out for this?Preview is not fully avialableHow to preview files in Lightning Community using LWC
5/5

Stuck in coding limbo?

Our courses unlock your tech potential