Lightning Page with LAC, Save, Update, Delete and upload file functionality.

In todays learning session I will explain below points
1. Create Parent and Child Component
2. Embedded Child Component into Parent Component
3. Passing Attribute from Parent Component to Child Component
4. Do iteration into child component on attribute
5. Create Component Event
6. Passing Parameter from Child component to Parent component
7. Do Save, Update and Delete operation on screen.
8. Use Lightning:Input file component to upload image into attachment object
9. Add Parent Component into Lightning Page.

You can find complete code , object , lightning page in below trails. 

Object : CRUDSFAccount: Create fields as shown in below screen City, Company, DOB, LastName, FirstName

CRUDSfLearningCmpEvent.evt : In this event you can see that I have taken Object (CRUDSFAccount__c) as attribute 

<aura:event type="COMPONENT" description="Event template" >
<aura:attribute name="CRUDSFAccountObj" type="CRUDSFAccount__c" />
</aura:event>

CRUDAcctListCmp.cmp : <aura:attribute name=”acctlist” type=”CRUDSFAccount__c[]” /> this acctlist attribute will hold attribute coming form parent component i.e. CRUDSFLearningCmp.cmp.

<aura:component controller="CRUDSFLearningController" 
implements="force:appHostable,
flexipage:availableForAllPageTypes,flexipage:availableForRecordHome,
force:hasRecordId,forceCommunity:availableForAllPageTypes,
force:lightningQuickAction" access="global" >

<aura:attribute name="acctlist" type="CRUDSFAccount__c[]" />
<aura:attribute name="CRUDSFAccountObj" type="CRUDSFAccount__c" />

<aura:registerEvent name="CRUDSFEvent" type="c:CRUDSFLearningCmpEvent" />

<table border="1" margin="2px" >
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>City Name</th>
<th>Company</th>
<th>DOB</th>
<th>Action</th>
</tr>
<aura:iteration var='ac' items="{!v.acctlist}">
<tr>
<td>{!ac.FirstName__c}</td>
<td>{!ac.LastName__c}</td>
<td>{!ac.City__c}</td>
<td>{!ac.Company__c}</td>
<td>{!ac.DOB__c}</td>
<td>
<button type="button" onclick="{!c.DeleteAccount}" id="{!ac.Id}"
class="slds-button slds-button_destructive">Delete</button>
&nbsp;
<button type="button" onclick="{!c.EditAccount}" id="{!ac.Id}"
class="slds-button slds-button_destructive">Edit</button>
</td>
</tr>
</aura:iteration>
</table>

</aura:component>

CRUDAcctListCmpController.js

({
DeleteAccount : function(component, event, helper) {
if(confirm('Are you sure?'))
{
helper.DeleteCRUDSFAccount(component,event);
}
},
EditAccount : function(component, event, helper) {

component.set("v.SaveButtonLabel","Update");
helper.LoadCRUDSFAccountById(component,event);

},
})

CRUDAcctListCmpHelper.js

({
LoadCRUDSFAccountById : function(component,event) {

var action = component.get("c.LoadCRUDAccountByIdCtrl");
action.setParams({ keyId : event.target.id });
action.setCallback(this, function(response){

var state = response.getState();
if(component.isValid() && state === "SUCCESS"){
component.set("v.CRUDSFAccountObj",response.getReturnValue());
var objCrud = component.get("v.CRUDSFAccountObj");
var evt = component.getEvent("CRUDSFEvent");
evt.setParams({
CRUDSFAccountObj : objCrud
}).fire();
}

});
$A.enqueueAction(action);


},
DeleteCRUDSFAccount : function(component,event) {

var action = component.get("c.DeleteCRUDAccountCtrl");
action.setParams({
keyId : event.target.id
});
action.setCallback(this, function(response){

var state = response.getState();
if(component.isValid() && state === "SUCCESS"){
this.LoadCRUDSFAccount(component,event);
/* component.set("v.CRUDSFAccountObj",null);
var objCrud = component.get("v.CRUDSFAccountObj");
var evt = component.getEvent("CRUDSFEvent");
evt.setParams({
CRUDSFAccountObj : objCrud
}).fire(); */
}

});
$A.enqueueAction(action);


},
LoadCRUDSFAccount : function(component,event) {

var action = component.get("c.LoadCRUDAccountCtrl");
action.setCallback(this, function(response){

var state = response.getState();
if(component.isValid() && state === "SUCCESS"){
component.set("v.acctlist",response.getReturnValue());
}

});
$A.enqueueAction(action);


},

})

CRUDSFLearningController.apxc

public class CRUDSFLearningController {


@AuraEnabled
public static Id SaveCRUDAccountCtrl( CRUDSFAccount__c CRUDSFAccountObj)
{
try{
if(CRUDSFAccountObj != null)
{
insert CRUDSFAccountObj;
}
return CRUDSFAccountObj.Id;
}
Catch(Exception ex)
{
return ex.getMessage();
}
}

@AuraEnabled
public static String UpdateCRUDAccountCtrl(CRUDSFAccount__c CRUDSFAccountObj)
{
String Res ='N';
try{
if(CRUDSFAccountObj != null)
{
upsert CRUDSFAccountObj;
Res = 'Y';
}

}
Catch(Exception ex)
{
if(ex.getMessage().contains('ENTITY_IS_DELETED'))
{
CRUDSFAccountObj.Id=null;
insert CRUDSFAccountObj;
Res='Y';
}

}
return Res;
}

@AuraEnabled
public static List<CRUDSFAccount__c> LoadCRUDAccountCtrl()
{

return [Select Id,FirstName__c,LastName__c,City__c, Company__c, DOB__c from CRUDSFAccount__c order by LastModifiedDate Desc];
}

@AuraEnabled
public static CRUDSFAccount__c LoadCRUDAccountByIdCtrl(String keyId)
{
return [Select Id,FirstName__c,LastName__c,City__c,Company__c, DOB__c,(Select Id from Attachments order by LastModifiedDate desc limit 1) from CRUDSFAccount__c where Id =: keyId];
}


@AuraEnabled
public static void DeleteCRUDAccountCtrl(String keyId)
{
delete [Select Id from CRUDSFAccount__c where Id =: keyId];
delete [Select Id from Attachment where parentId =: keyId];
}

@AuraEnabled
public static Id saveFile(Id parentId, String fileName, String base64Data, String contentType, String fileId) {
// check if fileId id ''(Always blank in first chunk), then call the saveTheFile method,
// which is save the check data and return the attachemnt Id after insert,
// next time (in else) we are call the appentTOFile() method
// for update the attachment with remains chunks
if (fileId == '') {
fileId = saveTheFile(parentId, fileName, base64Data, contentType);
} else {
appendToFile(fileId, base64Data);
}

return Id.valueOf(fileId);
}

public static Id saveTheFile(Id parentId, String fileName, String base64Data, String contentType) {
base64Data = EncodingUtil.urlDecode(base64Data, 'UTF-8');

Attachment oAttachment = new Attachment();
oAttachment.parentId = parentId;

oAttachment.Body = EncodingUtil.base64Decode(base64Data);
oAttachment.Name = fileName;
oAttachment.ContentType = contentType;

insert oAttachment;
return oAttachment.Id;
}

private static void appendToFile(Id fileId, String base64Data) {
base64Data = EncodingUtil.urlDecode(base64Data, 'UTF-8');
Attachment a = [
SELECT Id, Body
FROM Attachment
WHERE Id = : fileId
];

String existingBody = EncodingUtil.base64Encode(a.Body);
a.Body = EncodingUtil.base64Decode(existingBody + base64Data);
update a;
}
}

CRUDSFLearningCmp.cmp

<aura:component controller="CRUDSFLearningController"

implements="force:appHostable,flexipage:availableForAllPageTypes,
flexipage:availableForRecordHome,force:hasRecordId,
forceCommunity:availableForAllPageTypes,force:lightningQuickAction"
access="global" >
<aura:attribute name="SaveButtonLabel" type="String" Default="Save" />
<aura:attribute name="Id" type="String" />
<aura:attribute name="CRUDSFAccount" type="CRUDSFAccount__c[]" />
<aura:attribute name="CRUDSFAccountObj" type="CRUDSFAccount__c"
default="{sobjectType:'CRUDSFAccount__c'}" />
<aura:attribute name="ImageId" type="String" />
<aura:attribute name="showLoadingSpinner" type="boolean" default="false" />
<aura:attribute name="fileName" type="String" default="No File Selected.." />
<aura:attribute name="FilesUploaded" type="List" />

<aura:handler name="init" value="{!this}" action="{!c.doInit}"/>
<aura:handler name="CRUDSFEvent" event="c:CRUDSFLearningCmpEvent" action="{!c.SetData}" />
<div style="padding:20px;">
<article class="slds-card">
<div class="slds-card__header slds-grid">
<header class="slds-media slds-media_center slds-has-flexi-truncate">
<div class="slds-media__figure">
<span class="slds-icon_container slds-icon-standard-account" title="account">

<span class="slds-assistive-text">SF Account</span>
</span>
</div>
<div class="slds-media__body">
<h2 class="slds-card__header-title">
<a href="#" class="slds-card__header-link slds-truncate" title="Accounts">
<span>SF Account</span>
</a>
</h2>
</div>
<div class="slds-no-flex">
<lightning:button variant="brand" label="{!v.SaveButtonLabel}" title="Save action" onclick="{! c.SaveData }" />
<lightning:button variant="brand" label="Cancel" title="Cancel action" onclick="{! c.CancelData }"/>

</div>
</header>
</div>
<div class="slds-card__body slds-card__body_inner">
<lightning:input name="FirstName" value="{!v.CRUDSFAccountObj.FirstName__c }" placeholder="FirstName" label="First Name" required="true" />
<lightning:input name="LastName" value="{!v.CRUDSFAccountObj.LastName__c }" placeholder="LastName" label="Last Name" />
<lightning:input name="City" value="{!v.CRUDSFAccountObj.City__c }" placeholder="CityName" label="City" />
<lightning:input name="Company" value="{!v.CRUDSFAccountObj.Company__c }" placeholder="Company" label="Company" />
<lightning:input type="date" value="{!v.CRUDSFAccountObj.DOB__c }" name="DOB" label="Date Of Birth" />
<div class="row">
<div class="slds-grid slds-gutters">
<div class="slds-col">
<lightning:input aura:id="fileId" onchange="{!c.handleFilesChange}" type="file" name="file" label="" multiple="true"/>
<div class="slds-text-body_medium slds-text-color_error">{!v.fileName} </div>
<!--use aura:if for show-hide the loading spinner image-->
<aura:if isTrue="{!v.showLoadingSpinner}">
<div class="slds-text-body_small slds-text-color_error">Uploading...
<lightning:spinner alternativeText="Loading" size="medium" />
</div>
</aura:if>
</div>
<div class="slds-col">
<br/>

<img src="{!'https://mysms-dev-ed--c.ap6.content.force.com/servlet/servlet.FileDownload?file='+v.ImageId}" width="100" height="100"/>

</div>
</div>
</div>

</div>
<footer class="slds-card__footer">
<c:CRUDAcctListCmp acctlist="{!v.CRUDSFAccount}" />
</footer>
</article>
</div>

</aura:component>

CRUDSFLearningController.js

({
doInit : function(component, event, helper) {
helper.LoadCRUDSFAccount(component,event);
},
SetData : function(component, event, helper) {
component.set("v.CRUDSFAccountObj", event.getParam("CRUDSFAccountObj"));
var objData = component.get("v.CRUDSFAccountObj");

if(objData != null)
{
if(objData.Id != null)
{
component.set("v.SaveButtonLabel","Update");
}
if( objData.Attachments != null)
{
component.set("v.ImageId", objData.Attachments[0].Id);
}
else
{
component.set("v.ImageId",null);
}
}
else
{
component.set("v.SaveButtonLabel","Save");
}
},

SaveData : function(component, event, helper) {
var ObjCRUDSFAccount = component.get("v.CRUDSFAccountObj");
if($A.util.isUndefinedOrNull(ObjCRUDSFAccount))
{
let CRUDSFAccountObj = {"sobjectType": "CRUDSFAccount__c"};
component.set("v.CRUDSFAccountObj", CRUDSFAccountObj);
}

var crudSFAccnt = component.get("v.CRUDSFAccountObj");
if($A.util.isUndefinedOrNull(crudSFAccnt.Id))
{
helper.SaveCRUDSFAccount(component,event);
}
else
{
helper.UpdateCRUDSFAccount(component,event);
}
},

CancelData : function(component, event, helper) {
component.set("v.CRUDSFAccountObj", null);
let CRUDSFAccountObj = {"sobjectType": "CRUDSFAccount__c"};
component.set("v.CRUDSFAccountObj", CRUDSFAccountObj);

component.set("v.SaveButtonLabel","Save");
component.set("v.ImageId",null);
component.set("v.fileName",null);

},

handleFilesChange: function(component, event, helper) {
var fileName = "No File Selected..";
var fileCount=component.find("fileId").get("v.files").length;
var files='';
if (fileCount > 0) {
for (var i = 0; i < fileCount; i++)
{
fileName = component.find("fileId").get("v.files")[i]["name"];
files=files+','+fileName;
}
}
else
{
files=fileName;
}
component.set("v.fileName", files);
},

uploadFiles: function(component, event, helper) {
if(component.find("fileId").get("v.files")==undefined)
{
helper.showMessage('Select files',false);
return;
}
if (component.find("fileId").get("v.files").length > 0) {
var s = component.get("v.FilesUploaded");
var fileName = "";
var fileType = "";
var fileCount=component.find("fileId").get("v.files").length;
if (fileCount > 0) {
for (var i = 0; i < fileCount; i++)
{
helper.uploadHelper(component, event,component.find("fileId").get("v.files")[i]);
}
}
} else {
helper.showMessage("Please Select a Valid File",false);
}
}

})

CRUDSfLearningHelper.js

({
MAX_FILE_SIZE: 4500000, //Max file size 4.5 MB
CHUNK_SIZE: 750000, //Chunk Max size 750Kb

SaveCRUDSFAccount : function(component,event) {

var action = component.get("c.SaveCRUDAccountCtrl");
action.setParams({
CRUDSFAccountObj : component.get("v.CRUDSFAccountObj")
});
action.setCallback(this, function(response){

var state = response.getState();

if(component.isValid() && state === "SUCCESS"){
var res = response.getReturnValue();
if(res.length<=18)
{
component.set("v.Id",res);
this.showSuccess(component,event,'Record has been save successfully');
this.clearAllData(component,event);
this.LoadCRUDSFAccount(component,event);
this.uploadFiles(component,event);
}
else
{
this.showError(component,event,res);
}

}
else if(state === "ERROR"){
var errors = response.getError();
if(errors) {
if(errors[0] && errors[0].message){
console.log("Error message: " + errors[0].message);
this.showError(component,event,errors[0].message);
}
else{
console.log("unknown error");
}
}
}
else if(state === "INCOMPLETE"){
console.log("Incomplete action. The server might be down or the client might be offline.");

}


});
$A.enqueueAction(action);


},

UpdateCRUDSFAccount : function(component,event) {

var action = component.get("c.UpdateCRUDAccountCtrl");
action.setParams({
CRUDSFAccountObj : component.get("v.CRUDSFAccountObj")
});
action.setCallback(this, function(response){

var state = response.getState();
if(component.isValid() && state === "SUCCESS"){
var res = response.getReturnValue();
if(res=="Y")
{
component.set("v.Id",component.get("v.CRUDSFAccountObj.Id"));
this.showSuccess(component,event,'Record has been updated successfully');
this.clearAllData(component,event);
this.LoadCRUDSFAccount(component,event);
this.uploadFiles(component,event);

}
else
{
this.showError(component,event,res);
}

}
else if(state === "ERROR"){
var errors = response.getError();
if(errors) {
if(errors[0] && errors[0].message){
console.log("Error message: " + errors[0].message);
this.showError(component,event,errors[0].message);
}
else{
console.log("unknown error");
}
}
}
else if(state === "INCOMPLETE"){
console.log("Incomplete action. The server might be down or the client might be offline.");

}


});
$A.enqueueAction(action);


},

LoadCRUDSFAccount : function(component,event) {

var action = component.get("c.LoadCRUDAccountCtrl");
action.setCallback(this, function(response){

var state = response.getState();
if(component.isValid() && state === "SUCCESS"){
component.set("v.CRUDSFAccount",response.getReturnValue());
}

});
$A.enqueueAction(action);


},

showInfo : function(component, event) {
var toastEvent = $A.get("e.force:showToast");
toastEvent.setParams({
title : 'Info',
message: 'This is an information message.',
duration:' 5000',
key: 'info_alt',
type: 'info',
mode: 'dismissible'
});
toastEvent.fire();
},

showSuccess : function(component, event,msg) {
var toastEvent = $A.get("e.force:showToast");
toastEvent.setParams({
title : 'Success',
message: msg,
duration:' 5000',
key: 'info_alt',
type: 'success',
mode: 'dismissible'
});
toastEvent.fire();
},

showError : function(component, event, msg) {
var toastEvent = $A.get("e.force:showToast");
toastEvent.setParams({
title : 'Error',
message: msg,
duration:' 5000',
key: 'info_alt',
type: 'error',
mode: 'pester'
});
toastEvent.fire();
},

showWarning : function(component, event) {
var toastEvent = $A.get("e.force:showToast");
toastEvent.setParams({
title : 'Warning',
message: 'This is a warning message.',
duration:' 5000',
key: 'info_alt',
type: 'warning',
mode: 'sticky'
});
toastEvent.fire();
},

clearAllData : function(component,event) {
component.set("v.CRUDSFAccountObj", null);
let CRUDSFAccountObj = {"sobjectType": "CRUDSFAccount__c"};
component.set("v.CRUDSFAccountObj", CRUDSFAccountObj);
component.set("v.SaveButtonLabel","Save");
component.set("v.ImageId",null);
component.set("v.fileName",null);

},

uploadFiles: function(component, event) {
if(component.find("fileId").get("v.files")!==null)
{
//this.showMessage('Select files',false);
//return;

if (component.find("fileId").get("v.files").length > 0) {
var s = component.get("v.FilesUploaded");
var fileName = "";
var fileType = "";
var fileCount=component.find("fileId").get("v.files").length;
if (fileCount > 0) {
for (var i = 0; i < fileCount; i++)
{
this.uploadHelper(component, event,component.find("fileId").get("v.files")[i]);
}
}
} else {
helper.showMessage("Please Select a Valid File",false);
}
}
},

uploadHelper: function(component, event,f) {
component.set("v.showLoadingSpinner", true);
var file = f;
var self = this;
// 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) {
component.set("v.showLoadingSpinner", false);
component.set("v.fileName", 'Alert : File size cannot exceed ' + self.MAX_FILE_SIZE + ' bytes.\n' + ' Selected file size: ' + file.size);
return;
}

// Convert file content in Base64
var objFileReader = new FileReader();
objFileReader.onload = $A.getCallback(function() {
var fileContents = objFileReader.result;
var base64 = 'base64,';
var dataStart = fileContents.indexOf(base64) + base64.length;
fileContents = fileContents.substring(dataStart);
self.uploadProcess(component, file, fileContents);
});

objFileReader.readAsDataURL(file);
},

uploadProcess: function(component, file, fileContents) {
// 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, '');
},

uploadInChunk: function(component, file, fileContents, startPosition, endPosition, attachId) {
// call the apex method 'saveFile'
var parId = component.get("v.Id");
var getchunk = fileContents.substring(startPosition, endPosition);
var action = component.get("c.saveFile");
action.setParams({
// Take current object's opened record. You can set dynamic values here as well
parentId: parId,
fileName: file.name,
base64Data: encodeURIComponent(getchunk),
contentType: file.type,
fileId: attachId
});

// set call back
action.setCallback(this, function(response) {
attachId = response.getReturnValue();
var state = response.getState();
if (state === "SUCCESS") {
// update the start position with end postion
startPosition = endPosition;
endPosition = Math.min(fileContents.length, startPosition + this.CHUNK_SIZE);
if (startPosition < endPosition) {
this.uploadInChunk(component, file, fileContents, startPosition, endPosition, attachId);
} else {
this.showMessage('your File is uploaded successfully',true);
component.set("v.showLoadingSpinner", false);
this.clearAllData(component);
}
// handel the response errors
} else if (state === "INCOMPLETE") {
helper.showMessage("From server: " + response.getReturnValue(),false);
} 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");
}
}
});
$A.enqueueAction(action);
},

showMessage : function(message,isSuccess) {
var toastEvent = $A.get("e.force:showToast");
toastEvent.setParams({
"title": isSuccess?"Success!":"Error!",
"type":isSuccess?"success":"error",
"message": message
});
toastEvent.fire();
}
})

Lightning App Builder:


Attachment Object:

4 thoughts on “Lightning Page with LAC, Save, Update, Delete and upload file functionality.

  1. Hi there! This is my first comment here so I just wanted to give a quick shout out and say I really enjoy reading through your posts. Can you suggest any other blogs/websites/forums that go over the same topics? Thanks a lot!

  2. With havin so much written content do you ever run into any issues of plagorism or copyright violation? My site has a lot of completely unique content I’ve either written myself or outsourced but it appears a lot of it is popping it up all over the web without my agreement. Do you know any ways to help prevent content from being stolen? I’d genuinely appreciate it.

Comments are closed.