About

all about SFDC

Saturday, 9 December 2017

Salesforce Lightning Compent to Insert Multiple Attachments

11:05

Hello there.

This is about a Lighting Component for uploading multiple Attachments for any Object.

It would work for all objects and can also be re usable in SF communities. To get an easy understanding i've used the component in Quick Action / lightning action. Thanks to Manoj for actual idea and support.







The Attachment would be inserted simultaneously  when selected a file and also deleteing would deleted from database.

Component:
<aura:component implements="force:hasRecordId,force:lightningQuickActionWithoutHeader" controller="AttachmentUploadCon">
<aura:attribute name="rows" type="List" default="[]" />
<aura:attribute name="objAPIName" type="string" default="Account" access="GLOBAL"/>
<aura:attribute name="RecID" type="string" access="GLOBAL"/>
<aura:handler name="init" value="{!this}" action="{!c.initComp}"/>
<div style="width:100%;">
<div style="float:left;width:70%;font-size: 20px;"><b>Upload Attachments</b></div>
<div style="float:left;width:30%;text-align:center;"><lightning:button iconPosition="left" iconName="action:new" variant="brand" onclick="{!c.addRow}" /></div>
<div style="clear:both;"></div>
</div>
<style>
.slds-modal__container{max-width: 55rem !important;width:75%;}
</style>
<div style="overflow-y:auto;height:440px;" >
<table class="slds-table slds-table_bordered slds-table_cell-buffer" style="margin: 2% auto;border: 1px solid #ddd;">
<tbody>
<aura:iteration items="{!v.rows}" var="row">
<tr>
<td scope="row" data-label="Row Number">
{!row.rowNum}.
</td>
<td data-label="Attachment">
<lightning:input class="{!row.rowNum - 1}" aura:id="fileId" onchange="{!c.handleFilesChange}" type="file" name="{!row.fileId}" label=" " multiple="false"/>
{!row.fileName}
<!--use aura:if for show-hide the loading spinner image-->
</td>
<td data-label="Action" class="slds-align_absolute-center action-col">
<aura:if isTrue="{!row.showLoadingSpinner}">
<div style="position: absolute;margin-left: -38%;" class="slds-text-body_small slds-text-color_error">
<img src="/auraFW/resources/aura/images/spinner.gif" class="spinner-img" alt="Loading"/>'
</div>
</aura:if>
<lightning:button iconPosition="left" iconName="action:delete" variant="destructive" name="{!row.rowNum - 1}" class="{!'atcID_'+row.fileId}" onclick="{!c.deleteRow}" />
</td>
</tr>
</aura:iteration>
</tbody>
</table>
</div>
</aura:component>


Controller JS:
({
initComp: function(component, event, helper) {
helper.getAttachments(component, event);
},
deleteRow: function(component, event, helper) {
var index = event.getSource().get('v.name');
var clsName = event.getSource().get('v.class');
console.log('recId@ delete', clsName.split('atcID_'));
var clasAray = !$A.util.isEmpty(clsName) ? clsName.split('atcID_') : [];
var recId = !$A.util.isEmpty(clasAray) ? clasAray[1] : '';
if(!$A.util.isEmpty(recId))
{
var action = component.get("c.deleteAttachment");
action.setParams({"attchId": recId});
action.setCallback(this, function(response) {
var state = response.getState();
console.log('state..!',state);
if (state === "SUCCESS"){
helper.getAttachments(component, event);
}
else
{
alert('Something went wrong.');
}
});
$A.enqueueAction(action);
}
},
clearError : function(component, event, helper)
{
var rows = component.get('v.rows');
var rowIndex = event.getSource().get("v.labelClass");
rows[rowIndex].cDocError = '';
component.set('v.rows',rows);
},
handleFilesChange: function(component, event, helper) {
//alert();
if (event.getSource().get("v.files").length > 0) {
console.log('---@handleFilesChange---');
helper.uploadHelper(component, event);
}
},
addRow : function(component, event, helper) {
var rows = component.get("v.rows");
var newRow = rows.length+1;
rows.push({'rowNum':rows.length+1,'fileId':'','cDocError':'','parentId':'','fileName':'No File Selected..','showLoadingSpinner':false});
component.set("v.rows",rows);
}
})

Helper JS:
({
MAX_FILE_SIZE: 4500000, //Max file size 4.5 MB
CHUNK_SIZE: 750000, //Chunk Max size 750Kb
uploadHelper: function(component, event) {
// start/show the loading spinner
// get the selected files using aura:id [return array of files]
var rowIndex = event.getSource().get('v.class');
var attachmentId = event.getSource().get('v.name');
var rows = component.get('v.rows');
rows[rowIndex].showLoadingSpinner = true;
component.set('v.rows',rows);
var fileInput;
if(typeof component.find("fileId")[rowIndex] == 'undefined')
fileInput = component.find("fileId").get("v.files");
else
fileInput = component.find("fileId")[rowIndex].get("v.files");
// get the first file using array index[0]
var file = fileInput[0];
var self = this;
var parentId = rows[rowIndex].parentId;
// check the selected file size, if select file size greter then MAX_FILE_SIZE,
// then show a alert msg to user,hide the loading spinner and return from function
if (file.size > self.MAX_FILE_SIZE) {
console.log('size exceeded.');
rows[rowIndex].showLoadingSpinner = false;
rows[rowIndex].fileName = 'Alert : File size cannot exceed ' + self.MAX_FILE_SIZE + ' bytes.\n' + ' Selected file size: ' + file.size;
component.set('v.rows',rows);
return;
}
// create a FileReader object
var objFileReader = new FileReader();
// set onload function of FileReader object
objFileReader.onload = $A.getCallback(function() {
console.log('--@objFileReader.onload --');
var fileContents = objFileReader.result;
var base64 = 'base64,';
var dataStart = fileContents.indexOf(base64) + base64.length;
fileContents = fileContents.substring(dataStart);
// call the uploadProcess method
self.uploadProcess(component, file, fileContents,rowIndex,attachmentId,parentId);
});
objFileReader.readAsDataURL(file);
},
uploadProcess: function(component, file, fileContents,rowIndex,attachmentId,parentId) {
// set a default size or startpostiton as 0
var startPosition = 0;
// calculate the end size or endPostion using Math.min() function which is return the min. value
var endPosition = Math.min(fileContents.length, startPosition + this.CHUNK_SIZE);
// start with the initial chunk, and set the attachId(last parameter)is null in begin
this.uploadInChunk(component, file, fileContents, startPosition, endPosition, '',rowIndex,attachmentId,parentId);
},
uploadInChunk: function(component, file, fileContents, startPosition, endPosition, attachId,rowIndex,attachmentId,parentId) {
console.log('--@uploadInChunk---');
// call the apex method 'saveChunk'
var getchunk = fileContents.substring(startPosition, endPosition);
var action = component.get("c.saveChunk");
action.setParams({
parnttId: component.get("v.recordId"),
attachmentName: file.name,
attachmentBody: encodeURIComponent(getchunk),
attachmentType: file.type,
fileId: attachId,
attachmentId:attachmentId
});
// set call back
action.setCallback(this, function(response) {
console.log('--@uploadInChunk- setCallback--');
// store the response / Attachment Id
attachId = response.getReturnValue();
var state = response.getState();
if (state === "SUCCESS") {
console.log('--@uploadInChunk- success!!');
// update the start position with end postion
startPosition = endPosition;
endPosition = Math.min(fileContents.length, startPosition + this.CHUNK_SIZE);
// check if the start postion is still less then end postion
// then call again 'uploadInChunk' method ,
// else, diaply alert msg and hide the loading spinner
if (startPosition < endPosition) {
this.uploadInChunk(component, file, fileContents, startPosition, endPosition, attachId,rowIndex);
} else {
var rows = component.get('v.rows');
rows[rowIndex].showLoadingSpinner = false;
rows[rowIndex].fileName = file.name;
rows[rowIndex].fileId = attachId;
component.set('v.rows',rows);
//alert('your File is uploaded successfully');
this.getAttachments(component, event);
}
// handel the response errors
} else if (state === "INCOMPLETE") {
alert("From server: " + response.getReturnValue());
} else if (state === "ERROR") {
var errors = response.getError();
if (errors) {
if (errors[0] && errors[0].message) {
console.log("Error message: " + errors[0].message);
}
} else {
console.log("Unknown error");
}
}
});
// enqueue the action
$A.enqueueAction(action);
},
getAttachments : function(component, event)
{
var action = component.get("c.loadAttachments");
var recID = !$A.util.isEmpty(component.get("v.recordId")) ? component.get("v.recordId") : component.get("v.RecID");
action.setParams({
parentId: recID,
sobjAPIName: component.get("v.objAPIName"),
});
action.setCallback(this, function(response) {
var state = response.getState();
console.log('state..!',state);
if (state === "SUCCESS")
{
var cAttachments = response.getReturnValue();
console.log('attachments..!',cAttachments);
var rows = [];
for(var i = 0;i<cAttachments.length;i++)
{
var row = {};
row.rowNum = i+1;
row.fileId = cAttachments[i].attachmentId;
row.fileName = cAttachments[i].attachmentName;
row.parentId = cAttachments[i].parentId;
row.cDocError = '';
row.showLoadingSpinner = false;
rows.push(row);
}
if(cAttachments.length == 0)
rows.push({'rowNum':1,fileId:'','cDocError':'','parentId':'','fileName':'No File Selected..','showLoadingSpinner':false});
component.set("v.rows",rows);
}
else
{
alert('Something went wrong.');
}
});
$A.enqueueAction(action);
}
})


Styles:
.THIS .action-col button{
margin:21%;
cursor:pointer;
}
.THIS .error-msg{
color:#a94442;
}
.THIS .Name-td input{
border-left: 5px solid #a94442;
}


Apex cls:
public with sharing class AttachmentUploadCon
{
@AuraEnabled
public static list<attachmentWrapper> loadAttachments(Id parentId, string sobjAPIName)
{
list<attachmentWrapper> awList = new list<attachmentWrapper>();
string qryStr = 'Select Id,Name,(Select Id,Name FROM Attachments) FROM '+sobjAPIName+' where id=:parentId';
list<sobject> records = DataBase.Query(qryStr);
for(Sobject sobj : records)
{
for(Attachment att : sobj.getSObjects('Attachments'))
{
attachmentWrapper aw = new attachmentWrapper();
aw.parentId = (string)sobj.get('Id');
aw.attachmentId = att.Id;
aw.attachmentName = att.Name;
awList.add(aw);
}
}
return awList;
}
public class attachmentWrapper
{
@AuraEnabled public String parentId;
@AuraEnabled public String attachmentId;
@AuraEnabled public String attachmentName;
}
@AuraEnabled
public static void deleteAttachment(string attchId)
{
system.debug('attchId '+attchId);
if(string.isNotBlank(attchId)){
list<Attachment> atchList = [select id from Attachment WHERE ID=: attchId];
system.debug('--atchList --'+atchList.size());
if(atchList.size()>0){
delete atchList;
}
}
}
@AuraEnabled
public static Id saveChunk(Id parnttId, String attachmentName, String attachmentBody, String attachmentType,String attachmentId) {
attachmentBody = EncodingUtil.urlDecode(attachmentBody, 'UTF-8');
Attachment oAttachment;
if(String.isNotBlank(attachmentId))
{
oAttachment = [Select Id,parentId FROM Attachment WHERE Id=:attachmentId];
}
else
{
oAttachment = new Attachment();
oAttachment.parentId = parnttId;
}
oAttachment.Body = EncodingUtil.base64Decode(attachmentBody);
oAttachment.Name = attachmentName;
oAttachment.ContentType = attachmentType;
upsert oAttachment;
return oAttachment.Id;
}
}
Powered by Blogger.

Visualforce

Apex

Lightening UI

SF Admin