├── .eslintignore ├── .eslintrc.json ├── .forceignore ├── .gitignore ├── .gitmodules ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── _config.yml ├── config └── project-scratch-def.json ├── force-app └── main │ └── default │ ├── classes │ ├── GetFilesController.cls │ ├── GetFilesController.cls-meta.xml │ ├── GetFilesControllerTest.cls │ └── GetFilesControllerTest.cls-meta.xml │ ├── lwc │ ├── .eslintrc.json │ ├── customDatatable │ │ ├── customDatatable.html │ │ ├── customDatatable.js │ │ ├── customDatatable.js-meta.xml │ │ └── linkPreview.html │ ├── filePreviewCol │ │ ├── filePreviewCol.css │ │ ├── filePreviewCol.html │ │ ├── filePreviewCol.js │ │ └── filePreviewCol.js-meta.xml │ ├── filePreviewComp │ │ ├── filePreviewComp.html │ │ ├── filePreviewComp.js │ │ └── filePreviewComp.js-meta.xml │ ├── fileUtils │ │ ├── fileUtils.js │ │ └── fileUtils.js-meta.xml │ └── filesRelatedList │ │ ├── filesRelatedList.html │ │ ├── filesRelatedList.js │ │ └── filesRelatedList.js-meta.xml │ └── staticresources │ ├── nopreviewimg.png │ └── nopreviewimg.resource-meta.xml ├── media └── lwc-files.gif ├── package-lock.json ├── package.json └── sfdx-project.json /.eslintignore: -------------------------------------------------------------------------------- 1 | **/lwc/**/*.css 2 | **/lwc/**/*.html 3 | **/lwc/**/*.json 4 | **/lwc/**/*.svg 5 | **/lwc/**/*.xml 6 | .sfdx -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es6": true 5 | }, 6 | "extends": "eslint:recommended", 7 | "globals": { 8 | "Atomics": "readonly", 9 | "SharedArrayBuffer": "readonly" 10 | }, 11 | "parserOptions": { 12 | "ecmaVersion": 2018, 13 | "sourceType": "module" 14 | }, 15 | "rules": { 16 | } 17 | } -------------------------------------------------------------------------------- /.forceignore: -------------------------------------------------------------------------------- 1 | # List files or directories below to ignore them when running force:source:push, force:source:pull, and force:source:status 2 | # More information: https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_exclude_source.htm 3 | # 4 | 5 | package.xml 6 | 7 | # LWC configuration files 8 | **/jsconfig.json 9 | **/.eslintrc.json 10 | 11 | # LWC Jest 12 | **/__tests__/** -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used for Git repositories to specify intentionally untracked files that Git should ignore. 2 | # If you are not using git, you can delete this file. For more information see: https://git-scm.com/docs/gitignore 3 | # For useful gitignore templates see: https://github.com/github/gitignore 4 | 5 | # Salesforce cache 6 | .sfdx/ 7 | .localdevserver/ 8 | 9 | # LWC VSCode autocomplete 10 | **/lwc/jsconfig.json 11 | 12 | # LWC Jest coverage reports 13 | coverage/ 14 | 15 | # Logs 16 | logs 17 | *.log 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | 22 | # Dependency directories 23 | node_modules/ 24 | 25 | # Eslint cache 26 | .eslintcache 27 | 28 | # MacOS system files 29 | .DS_Store 30 | 31 | # Windows system files 32 | Thumbs.db 33 | ehthumbs.db 34 | [Dd]esktop.ini 35 | $RECYCLE.BIN/ 36 | 37 | #Profiles and flexipages 38 | flexipages 39 | profiles 40 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surajp/lwc-files-list/30ca9874ebb96a5e7c24b267ba00cf127a7baa67/.gitmodules -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # List files or directories below to ignore them when running prettier 2 | # More information: https://prettier.io/docs/en/ignore.html 3 | # 4 | 5 | .localdevserver 6 | .sfdx 7 | 8 | coverage/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "none", 3 | "overrides": [ 4 | { 5 | "files": "**/lwc/**/*.html", 6 | "options": { "parser": "lwc","printWidth":160 } 7 | }, 8 | { 9 | "files": "*.{cmp,page,component}", 10 | "options": { "parser": "html"} 11 | }, 12 | { 13 | "files": "*.{cls,trigger,apex}", 14 | "options": { "parser": "apex","printWidth":160 } 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | ## 2020-6-20 3 | 4 | * Move buttons to the right and apply colors consistent with Salesforce styling guidelines 5 | * Show spinner during file upload 6 | * Change label from 'Version Details' to 'Version History' 7 | * Shrink preview size for faster loading and better visibility 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Suraj Pillai 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Files Related List LWC 2 | 3 | This is an lwc that shows the list of files related to a record in a tabular form, with support for viewing previous versions as well, without having to navigate away from the record page 4 | 5 | ![](media/lwc-files.gif) 6 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-slate -------------------------------------------------------------------------------- /config/project-scratch-def.json: -------------------------------------------------------------------------------- 1 | { 2 | "orgName": "Demo company", 3 | "edition": "Developer", 4 | "features": ["AuthorApex"], 5 | "settings":{ 6 | "lightningExperienceSettings":{ 7 | "enableS1DesktopEnabled": true 8 | }, 9 | "mobileSettings": { 10 | "enableS1EncryptedStoragePref2": true 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /force-app/main/default/classes/GetFilesController.cls: -------------------------------------------------------------------------------- 1 | /** 2 | @description Gets related files for a record 3 | @author Suraj Pillai 4 | @date Apr, 2020 5 | **/ 6 | public with sharing class GetFilesController { 7 | /** 8 | @description get related latest contentVersions of all Content documents given a record id 9 | @param recordId Id of the record to be fetched 10 | @return list of file wrapper objects, each representing a content version 11 | **/ 12 | @AuraEnabled(cacheable=true) 13 | public static FilesWrapper[] getFilesList(Id recordId) { 14 | /*{{{*/ 15 | 16 | FilesWrapper[] filesList = new List{}; 17 | for (ContentDocumentLink link : [ 18 | SELECT 19 | ContentDocumentId, 20 | ContentDocument.LatestPublishedVersion.Title, 21 | ContentDocument.LatestPublishedVersion.CreatedDate, 22 | ContentDocument.LatestPublishedVersion.CreatedBy.Name 23 | FROM ContentDocumentLink 24 | WHERE LinkedEntityId = :recordId 25 | ]) { 26 | filesList.add( 27 | new FilesWrapper( 28 | link.ContentDocumentId, 29 | link.ContentDocument.LatestPublishedVersion.Title, 30 | link.ContentDocument.LatestPublishedVersion.CreatedBy.Name, 31 | Date.valueOf(link.ContentDocument.LatestPublishedVersion.CreatedDate), 32 | link.ContentDocument.LatestPublishedVersionId 33 | ) 34 | ); 35 | } 36 | return filesList; 37 | } /*}}}*/ 38 | 39 | /** 40 | @description get details of contentversions for a content document 41 | @param recordId Id of the content document whose version details are to be fetched 42 | @return list of file version wrapper objects, each representing a content version 43 | **/ 44 | @AuraEnabled 45 | public static FileVersionWrapper[] getFileVersionDetails(Id fileId) { 46 | /*{{{*/ 47 | FileVersionWrapper[] contentversions = new List{}; 48 | for (ContentVersion cv : [SELECT title, createddate, createdby.name, ReasonForChange FROM ContentVersion WHERE ContentDocumentId = :fileId]) { 49 | contentVersions.add(new FileVersionWrapper(cv.Id, cv.title, cv.createdby.name, Date.valueOf(cv.createddate), cv.ReasonForChange)); 50 | } 51 | return contentVersions; 52 | } /*}}}*/ 53 | 54 | public class FilesWrapper /*{{{*/ { 55 | @AuraEnabled 56 | public String id { get; set; } 57 | @AuraEnabled 58 | public String title { get; set; } 59 | @AuraEnabled 60 | public String createdBy { get; set; } 61 | @AuraEnabled 62 | public Date createdDate { get; set; } 63 | @AuraEnabled 64 | public String latestVersionId { get; set; } 65 | 66 | public FilesWrapper(String id, String title, String createdBy, Date createdDate, String latestVersionId) { 67 | this.id = id; 68 | this.title = title; 69 | this.createdBy = createdBy; 70 | this.createdDate = createdDate; 71 | this.latestVersionId = latestVersionId; 72 | } 73 | } /*}}}*/ 74 | 75 | public class FileVersionWrapper /*{{{*/ { 76 | @AuraEnabled 77 | public String id { get; set; } 78 | @AuraEnabled 79 | public String title { get; set; } 80 | @AuraEnabled 81 | public String createdBy { get; set; } 82 | @AuraEnabled 83 | public Date createdDate { get; set; } 84 | @AuraEnabled 85 | public String reasonForChange { get; set; } 86 | 87 | public FileVersionWrapper(String id, String title, String createdBy, Date createdDate, String reasonForChange) { 88 | /*{{{*/ 89 | this.id = id; 90 | this.title = title; 91 | this.createdBy = createdBy; 92 | this.createdDate = createdDate; 93 | this.reasonForChange = reasonForChange; 94 | } /*}}}*/ 95 | } /*}}}*/ 96 | 97 | @AuraEnabled 98 | public static Id createContentDocLink(Id contentVersionId, Id recordId) { 99 | Id docId = [SELECT ContentDocumentId FROM ContentVersion WHERE Id = :contentVersionId].ContentDocumentId; 100 | insert new ContentDocumentLink(ContentDocumentId = docId, LinkedEntityId = recordId); 101 | return docId; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /force-app/main/default/classes/GetFilesController.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 48.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /force-app/main/default/classes/GetFilesControllerTest.cls: -------------------------------------------------------------------------------- 1 | @isTest 2 | public with sharing class GetFilesControllerTest { 3 | @testSetup 4 | public static void setupData() { 5 | Account a = new Account(name = 'Sample'); 6 | insert a; 7 | ContentVersion cv = new ContentVersion(Title = 'Test File', VersionData = Blob.valueOf('File content'), PathOnClient = 'sample.txt'); 8 | insert cv; 9 | cv = [SELECT ContentDocumentId FROM ContentVersion WHERE Id = :cv.Id]; 10 | insert new ContentDocumentLink(LinkedEntityId = a.Id, ContentDocumentId = cv.ContentDocumentId); 11 | cv = new ContentVersion( 12 | Title = 'Test File', 13 | VersionData = Blob.valueOf('File content'), 14 | PathOnClient = 'sample.txt', 15 | ContentDocumentId = cv.ContentDocumentId 16 | ); 17 | insert cv; 18 | } 19 | 20 | @isTest 21 | public static void testFilesList() { 22 | Account a = [SELECT Id FROM Account LIMIT 1]; 23 | GetFilesController.FilesWrapper[] wrapperList = GetFilesController.getFilesList(a.Id); 24 | System.assertEquals(1, wrapperList.size()); 25 | GetFilesController.FileVersionWrapper[] versionWrapperList = GetFilesController.getFileVersionDetails(wrapperList[0].id); 26 | System.assertEquals(2, versionWrapperList.size()); 27 | } 28 | 29 | @isTest 30 | public static void testCreateContentDocLink() { 31 | ContentVersion cv = new ContentVersion(Title = 'Test File', VersionData = Blob.valueOf('File content'), PathOnClient = 'sample.txt'); 32 | insert cv; 33 | Account a = [SELECT Id FROM Account]; 34 | Id contentDocId = GetFilesController.createContentDocLink(cv.Id, a.Id); 35 | GetFilesController.FilesWrapper[] wrapperList = GetFilesController.getFilesList(a.Id); 36 | System.assertEquals(2, wrapperList.size()); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /force-app/main/default/classes/GetFilesControllerTest.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 48.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["@salesforce/eslint-config-lwc/recommended"] 3 | } 4 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/customDatatable/customDatatable.html: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/customDatatable/customDatatable.js: -------------------------------------------------------------------------------- 1 | import LightningDataTable from "lightning/datatable"; 2 | import linkPreview from "./linkPreview.html"; 3 | 4 | export default class CustomDatatable extends LightningDataTable { 5 | static customTypes = { 6 | filePreview: { 7 | template: linkPreview, 8 | typeAttributes: ["anchorText", "versionId"] 9 | } 10 | }; 11 | } 12 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/customDatatable/customDatatable.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 48.0 4 | false 5 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/customDatatable/linkPreview.html: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/filePreviewCol/filePreviewCol.css: -------------------------------------------------------------------------------- 1 | .overlay { 2 | position: fixed; 3 | z-index: 101; 4 | left: 30px; 5 | height: 5rem; 6 | } 7 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/filePreviewCol/filePreviewCol.html: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/filePreviewCol/filePreviewCol.js: -------------------------------------------------------------------------------- 1 | import { LightningElement, api } from "lwc"; 2 | import { NavigationMixin } from "lightning/navigation"; 3 | 4 | export default class FilePreviewCol extends NavigationMixin(LightningElement) { 5 | showPreview = false; 6 | @api label = ""; 7 | @api versionId = ""; 8 | @api fileId = ""; 9 | 10 | navigateToFile(event) { 11 | event.preventDefault(); 12 | this[NavigationMixin.Navigate]({ 13 | type: "standard__namedPage", 14 | attributes: { 15 | pageName: "filePreview" 16 | }, 17 | state: { 18 | recordIds: this.fileId, 19 | selectedRecordId: this.fileId 20 | } 21 | }); 22 | } 23 | 24 | get fileOrVersionId() { 25 | return this.versionId || this.fileId; 26 | } 27 | 28 | handleMouseOver() { 29 | this.showPreview = true; 30 | } 31 | 32 | handleMouseOut() { 33 | this.showPreview = false; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/filePreviewCol/filePreviewCol.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 48.0 4 | false 5 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/filePreviewComp/filePreviewComp.html: -------------------------------------------------------------------------------- 1 | 9 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/filePreviewComp/filePreviewComp.js: -------------------------------------------------------------------------------- 1 | import { LightningElement, api } from "lwc"; 2 | import NOPREVIEWIMGURL from "@salesforce/resourceUrl/nopreviewimg"; 3 | import { getPreviewUrl } from "c/fileUtils"; 4 | 5 | export default class FilePreviewComp extends LightningElement { 6 | @api fileId; 7 | @api heightInRem; 8 | 9 | get baseUrl() { 10 | return `https://${ 11 | window.location.hostname.split(".")[0] 12 | }--c.documentforce.com`; 13 | } 14 | 15 | get url() { 16 | return getPreviewUrl(this.fileId); 17 | } 18 | 19 | fallback(event) { 20 | if (event.target.src != NOPREVIEWIMGURL) { 21 | event.target.src = NOPREVIEWIMGURL; 22 | this.template.querySelector("img").style.width = "200px"; 23 | this.template.querySelector("img").style.height = "100px"; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/filePreviewComp/filePreviewComp.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 48.0 4 | false 5 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/fileUtils/fileUtils.js: -------------------------------------------------------------------------------- 1 | const getDocBaseUrl = () => { 2 | return `https://${ 3 | window.location.hostname.split(".")[0] 4 | }--c.documentforce.com`; 5 | }; 6 | 7 | const getDownloadUrl = (fileId) => { 8 | return `${getDocBaseUrl()}/sfc/servlet.shepherd/version/download/${fileId}`; 9 | }; 10 | 11 | const getPreviewUrl = (fileId) => { 12 | return `${getDocBaseUrl()}/sfc/servlet.shepherd/version/renditionDownload?rendition=THUMB240BY180&versionId=${fileId}&operationContext=CHATTER&page=0`; 13 | }; 14 | 15 | const getContentDocUrl = (fileId) => { 16 | return `/lightning/r/ContentDocument/${fileId}/view`; 17 | }; 18 | 19 | export { getDownloadUrl, getPreviewUrl, getContentDocUrl }; 20 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/fileUtils/fileUtils.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 48.0 4 | false 5 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/filesRelatedList/filesRelatedList.html: -------------------------------------------------------------------------------- 1 | 58 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/filesRelatedList/filesRelatedList.js: -------------------------------------------------------------------------------- 1 | import { LightningElement, wire, api } from "lwc"; 2 | import getRelatedFiles from "@salesforce/apex/GetFilesController.getFilesList"; 3 | import getFileVersionDetails from "@salesforce/apex/GetFilesController.getFileVersionDetails"; 4 | import createContentDocLink from "@salesforce/apex/GetFilesController.createContentDocLink"; 5 | import { deleteRecord, createRecord } from "lightning/uiRecordApi"; 6 | import { ShowToastEvent } from "lightning/platformShowToastEvent"; 7 | import { refreshApex } from "@salesforce/apex"; 8 | 9 | const actions = [ 10 | { label: "Version History", name: "show_details" }, 11 | { label: "Upload New Version", name: "upload_version" }, 12 | { label: "Delete File", name: "delete" } 13 | ]; 14 | 15 | const BASE64EXP = new RegExp(/^data(.*)base64,/); 16 | const columns = [ 17 | { 18 | label: "Id", 19 | fieldName: "id", 20 | type: "filePreview", 21 | typeAttributes: { 22 | anchorText: { fieldName: "title" }, 23 | versionId: { fieldName: "latestVersionId" } 24 | } 25 | }, 26 | { label: "Uploaded Date", fieldName: "createdDate", type: "date" }, 27 | { label: "Uploaded by", fieldName: "createdBy", type: "string" }, 28 | { type: "action", typeAttributes: { rowActions: actions } } 29 | ]; 30 | 31 | const versionColumns = [ 32 | { 33 | label: "Download Link", 34 | fieldName: "id", 35 | type: "filePreview", 36 | typeAttributes: { anchorText: "Download" } 37 | }, 38 | { label: "Title", fieldName: "title", type: "string" }, 39 | { label: "Reason for Change", fieldName: "reasonForChange", type: "string" }, 40 | { label: "Uploaded Date", fieldName: "createdDate", type: "date" }, 41 | { label: "Uploaded by", fieldName: "createdBy", type: "string" } 42 | ]; 43 | 44 | export default class FilesRelatedList extends LightningElement { 45 | @api 46 | recordId; 47 | 48 | _filesList; 49 | fileTitle; 50 | fileName; 51 | files = []; 52 | showModal = false; 53 | columns = columns; 54 | versionColumns = versionColumns; 55 | versionDetails = []; 56 | fileUpload = false; 57 | _currentDocId = null; 58 | showPreview = false; 59 | currentPreviewFileId = null; 60 | showSpinner = false; 61 | 62 | handleFileNameChange(event) { 63 | this.fileTitle = event.detail.value; 64 | } 65 | 66 | handleFileChange() { 67 | //{{{ 68 | const inpFiles = this.template.querySelector("input.file").files; 69 | if (inpFiles && inpFiles.length > 0) this.fileName = inpFiles[0].name; 70 | } //}}} 71 | 72 | @wire(getRelatedFiles, { recordId: "$recordId" }) 73 | getFilesList(filesList) { 74 | //{{{ 75 | this._filesList = filesList; 76 | const { error, data } = filesList; 77 | if (!error && data) { 78 | this.files = data; 79 | console.log("files found " + JSON.stringify(this.files)); 80 | } 81 | } //}}} 82 | 83 | closeModal() { 84 | //{{{ 85 | this.showModal = false; 86 | this._currentDocId = null; 87 | this.fileUpload = false; 88 | this.versionDetails = []; 89 | this.fileName = ""; 90 | this.fileTitle = ""; 91 | refreshApex(this._filesList); 92 | } //}}} 93 | 94 | handleRowAction(event) { 95 | //{{{ 96 | console.log(">> handle row action " + event.detail.action.name); 97 | const action = event.detail.action.name; 98 | const row = event.detail.row; 99 | this._currentDocId = row.id; 100 | if (action === "show_details") { 101 | this.fileUpload = false; 102 | this.showVersionDetails(); 103 | } else if (action === "upload_version") { 104 | this.fileUpload = true; 105 | this.showModal = true; 106 | } else if (action === "delete") { 107 | this._deleteRecord([this._currentDocId]); 108 | } 109 | } //}}} 110 | 111 | deleteFiles() { 112 | //{{{ 113 | const selectedRowIds = this.template 114 | .querySelector("c-custom-datatable[data-tablename='filestable']") 115 | .getSelectedRows() 116 | .map((row) => row.id); 117 | if (selectedRowIds.length > 0) { 118 | //eslint-disable-next-line 119 | let decision = confirm( 120 | `Are you sure you want to delete ${selectedRowIds.length} records?` 121 | ); 122 | if (decision) { 123 | this._deleteRecord(selectedRowIds); 124 | } 125 | } 126 | } //}}} 127 | 128 | _deleteRecord(recordIds) { 129 | //{{{ 130 | Promise.all(recordIds.map((id) => deleteRecord(id))) 131 | .then(() => { 132 | refreshApex(this._filesList); 133 | this.dispatchEvent( 134 | new ShowToastEvent({ 135 | variant: "success", 136 | message: `Record(s) deleted successfully` 137 | }) 138 | ); 139 | }) 140 | .catch((err) => { 141 | this.dispatchEvent( 142 | new ShowToastEvent({ 143 | variant: "error", 144 | message: `Error occurred while deleting records: ${ 145 | err.body ? err.body.message || err.body.error : err 146 | }` 147 | }) 148 | ); 149 | }); 150 | } //}}} 151 | 152 | newFileUpload() { 153 | //{{{ 154 | this.showModal = true; 155 | this.fileUpload = true; 156 | } //}}} 157 | 158 | showVersionDetails() { 159 | //{{{ 160 | console.log(">> file version details"); 161 | getFileVersionDetails({ fileId: this._currentDocId }) 162 | .then((result) => { 163 | console.log(">> version details " + JSON.stringify(result)); 164 | this.versionDetails = result; 165 | this.showModal = true; 166 | }) 167 | .catch((err) => { 168 | console.error(JSON.stringify(err)); 169 | }); 170 | } //}}} 171 | 172 | handleUpload(event) { 173 | //{{{ 174 | event.preventDefault(); 175 | this.showSpinner = true; 176 | try { 177 | const file = this.template.querySelector("input.file").files[0]; 178 | const reasonForChange = this.template.querySelector( 179 | "lightning-input.reason" 180 | ).value; 181 | const reader = new FileReader(); 182 | let fileData = ""; 183 | reader.onload = () => { 184 | fileData = reader.result; 185 | this._uploadFile(file, fileData, reasonForChange); 186 | }; 187 | reader.readAsDataURL(file); 188 | } catch (err) { 189 | console.error(err); 190 | this.dispatchEvent( 191 | new ShowToastEvent({ 192 | variant: "error", 193 | message: `File upload failed: ${err.body.message || err.body.error}` 194 | }) 195 | ); 196 | this.showSpinner = false; 197 | } 198 | } //}}} 199 | 200 | _uploadFile(file, fileData, reasonForChange) { 201 | //{{{ 202 | const payload = { 203 | Title: this.fileTitle || this.fileName, 204 | PathOnClient: file.name, 205 | ReasonForChange: reasonForChange, 206 | VersionData: fileData.replace(BASE64EXP, "") 207 | }; 208 | if (this._currentDocId) { 209 | payload.ContentDocumentId = this._currentDocId; 210 | } 211 | createRecord({ apiName: "ContentVersion", fields: payload }) 212 | .then((cVersion) => { 213 | this.showSpinner = false; 214 | if (!this._currentDocId) { 215 | this._createContentDocLink(cVersion.id); 216 | } else { 217 | this.closeModal(); 218 | this.dispatchEvent( 219 | new ShowToastEvent({ 220 | variant: "success", 221 | message: `Content Document Version created ${cVersion.id}` 222 | }) 223 | ); 224 | } 225 | }) 226 | .catch((err) => { 227 | this.dispatchEvent( 228 | new ShowToastEvent({ 229 | variant: "error", 230 | message: `File upload failed: ${err.body.message || err.body.error}` 231 | }) 232 | ); 233 | this.showSpinner = false; 234 | }); 235 | } //}}} 236 | 237 | _createContentDocLink(cvId) { 238 | //{{{ 239 | createContentDocLink({ 240 | contentVersionId: cvId, 241 | recordId: this.recordId 242 | }) 243 | .then((cId) => { 244 | this.closeModal(); 245 | this.dispatchEvent( 246 | new ShowToastEvent({ 247 | variant: "success", 248 | message: `File uploaded successfully ${cId}` 249 | }) 250 | ); 251 | }) 252 | .catch((err) => { 253 | this.dispatchEvent( 254 | new ShowToastEvent({ 255 | variant: "error", 256 | message: `An error occurred: ${ 257 | err.body ? err.body.message || err.body.error : err 258 | }` 259 | }) 260 | ); 261 | }); 262 | } //}}} 263 | } 264 | -------------------------------------------------------------------------------- /force-app/main/default/lwc/filesRelatedList/filesRelatedList.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 48.0 4 | true 5 | 6 | lightning__RecordPage 7 | 8 | 9 | -------------------------------------------------------------------------------- /force-app/main/default/staticresources/nopreviewimg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surajp/lwc-files-list/30ca9874ebb96a5e7c24b267ba00cf127a7baa67/force-app/main/default/staticresources/nopreviewimg.png -------------------------------------------------------------------------------- /force-app/main/default/staticresources/nopreviewimg.resource-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Private 4 | image/png 5 | 6 | -------------------------------------------------------------------------------- /media/lwc-files.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/surajp/lwc-files-list/30ca9874ebb96a5e7c24b267ba00cf127a7baa67/media/lwc-files.gif -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "salesforce-app", 3 | "private": true, 4 | "version": "1.0.0", 5 | "description": "Salesforce App", 6 | "scripts": { 7 | "lint": "npm run lint:lwc", 8 | "lint:lwc": "eslint force-app/main/default/lwc", 9 | "test": "npm run test:unit", 10 | "test:unit": "sfdx-lwc-jest", 11 | "test:unit:watch": "sfdx-lwc-jest --watch", 12 | "test:unit:debug": "sfdx-lwc-jest --debug", 13 | "test:unit:coverage": "sfdx-lwc-jest --coverage", 14 | "prettier": "prettier --write \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"", 15 | "prettier:verify": "prettier --list-different \"**/*.{cls,cmp,component,css,html,js,json,md,page,trigger,xml,yaml,yml}\"" 16 | }, 17 | "devDependencies": { 18 | "@prettier/plugin-xml": "^0.7.0", 19 | "@salesforce/eslint-config-lwc": "^0.4.0", 20 | "@salesforce/sfdx-lwc-jest": "^0.7.0", 21 | "eslint": "^5.16.0", 22 | "eslint-plugin-import": "^2.20.2", 23 | "prettier": "^1.19.1", 24 | "prettier-plugin-apex": "^1.0.0" 25 | }, 26 | "main": "index.js", 27 | "keywords": [], 28 | "author": "", 29 | "license": "ISC" 30 | } 31 | -------------------------------------------------------------------------------- /sfdx-project.json: -------------------------------------------------------------------------------- 1 | { 2 | "packageDirectories": [ 3 | { 4 | "path": "force-app", 5 | "default": true 6 | } 7 | ], 8 | "namespace": "", 9 | "sfdcLoginUrl": "https://login.salesforce.com", 10 | "sourceApiVersion": "48.0" 11 | } 12 | --------------------------------------------------------------------------------