I did some research online and could not find any helpful resources like how others overcome this issue. I could just find out that the idea was posted and waiting for Salesforce to get it delivered.
Here is the idea in the success community to provide the Add Google Doc button in Lightning. Please upvote it.
Solution
Ideally my goal is to figure it out whether attaching google docs is supported in Lightning interface or not. Is Salesforce just hiding the button or the whole functionality it self is supported or not.
I started with building a lightning component to fetch the google docs for a given record using apex controller to see if it actually retrieves data or not.
Let us first see the below apex controller. It accepts the String parameter and fetches the google docs available for the given record.
Below is the Lightning Component which uses Lightning Data Table to display the retrieved docs.Let us first see the below apex controller. It accepts the String parameter and fetches the google docs available for the given record.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class LCCGoogleDocController { | |
// method to fetch existing google docs for given record | |
@auraEnabled | |
public static List<GoogleDoc> getGoogleDocs(String parentId){ | |
try{ | |
List<GoogleDoc> docList = new List<GoogleDoc>(); | |
docList = [SELECT Id, Name, Url, ParentId FROM GoogleDoc WHERE ParentId =: parentId]; | |
return docList; | |
}Catch(Exception e){ | |
throw new AuraHandledException(e.getMessage()); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<aura:component implements="flexipage:availableForRecordHome,force:hasRecordId" access="global" controller="LCCGoogleDocController"> | |
<aura:handler name="init" value="{!this}" action="{!c.doInit}"/> | |
<aura:attribute name="gdocs" type="object[]" description="variable to hold list of existing google docs"/> | |
<aura:attribute name="gdocName" type="string" description="input variable to hold name of the document"/> | |
<aura:attribute name="gdocUrl" type="string" description="input variable to hold url of the document"/> | |
<aura:attribute name="columns" type="List" description="variable to hold columns name to display in the lightning table"/> | |
<lightning:notificationsLibrary aura:id="notifLib"/> | |
<lightning:button variant="success" label="Add Google Doc" title="Brand action" onclick="{!c.openmodal}" /><br/><br/> | |
<lightning:datatable keyField="id" data="{!v.gdocs}" columns="{!v.columns}" hideCheckboxColumn="true" wrapTextMaxLines="3"/> | |
</aura:component> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
({ | |
doInit : function(component, event, helper){ | |
helper.loadGDocs(component, event, helper); | |
} | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
({ | |
loadGDocs:function(component,event,helper){ | |
// define the actions you want to place in lightning data table | |
var actions = [ | |
{ label: 'Delete', name: 'delete' } | |
] | |
// sets the columns names ,actions and data to be displayed in lightning data table | |
component.set('v.columns', [ | |
{label: 'Name', fieldName: 'Name', type: 'text', wrapText: true}, | |
{label: 'Url', fieldName: 'Url', type: 'url', typeAttributes: { target: '_self'}, wrapText: true}, | |
{ type: 'action', typeAttributes: { rowActions: actions } } | |
]); | |
// fetch the existing google docs for the given record id | |
var action = component.get("c.getGoogleDocs"); | |
console.log(component.get("{!v.recordId}")); | |
action.setParams({ | |
"parentId" : component.get("{!v.recordId}") | |
}); | |
action.setCallback(this,function(response){ | |
var status = response.getState(); | |
if(status === 'SUCCESS'){ | |
component.set("{!v.gdocs}",response.getReturnValue()); | |
}else{ | |
// display alert stating some error happend and contact your sys admin | |
// error handling | |
let errors = response.getError(); | |
let msg = ''; | |
// Retrieve the error message sent by the server | |
if (errors && Array.isArray(errors) && errors.length > 0) { | |
for (var i = 0; i < errors.length; i++) { | |
msg = msg +'Error'+ i + ':' + errors[i].message; | |
} | |
} | |
// Display the message | |
console.error(msg); | |
component.find('notifLib').showToast({ | |
"variant": "error", | |
"title": "Error", | |
"message": msg, | |
"mode":"sticky" | |
}); | |
} | |
}); | |
$A.enqueueAction(action); | |
} | |
}) |
At this point of time when I placed this component on the Account record page and was expecting an error message which says "Thanks for all your efforts but google docs is not supported in Lightning" , but I was shocked to see the below result. As you can see in the below screenshot I was able to get the existing docs for the record and was able to display them in the Lightning Interface.
So it became very clear to me that it was just actually the button was missing and not the whole functionality and we can custom code it.
So in below snippets you can find the complete code to display, add and delete the existing records in Lightning Interface with out users ever need to switch back to classic interface.
Apex Class
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* @author Sunil Sirangi | |
* @date 03/02/2020 | |
* @description this class holds the logic to fetch and insert google docs in lightning interface | |
*/ | |
public class LCCGoogleDocController { | |
// method to fetch existing google docs for given record | |
@auraEnabled | |
public static List<GoogleDoc> getGoogleDocs(String parentId){ | |
try{ | |
List<GoogleDoc> docList = new List<GoogleDoc>(); | |
docList = [SELECT Id, Name, Url, ParentId FROM GoogleDoc WHERE ParentId =: parentId]; | |
return docList; | |
}Catch(Exception e){ | |
throw new AuraHandledException(e.getMessage()); | |
} | |
} | |
@auraEnabled | |
public static String deleteGoogleDoc(String recordId){ | |
try{ | |
List<GoogleDoc> docList = new List<GoogleDoc>(); | |
GoogleDoc gdoc = new GoogleDoc(Id=recordId); | |
docList.add(gdoc); | |
delete docList; | |
return 'Document Deleted Successfully.'; | |
}Catch(Exception e){ | |
throw new AuraHandledException(e.getMessage()); | |
} | |
} | |
// method to handle google document insertion and returning the status of insertion | |
@auraEnabled | |
public static String saveGoogleDoc(String parentId,String name,String url){ | |
try{ | |
List<GoogleDoc> docList = new List<GoogleDoc>(); | |
GoogleDoc gdoc = new GoogleDoc(); | |
gdoc.Name = name; | |
gdoc.Url = url; | |
gdoc.OwnerId = Userinfo.getUserId(); | |
gdoc.ParentId = parentId; | |
docList.add(gdoc); | |
insert docList; | |
return 'Document Attached Successfully.'; | |
}Catch(Exception e){ | |
throw new AuraHandledException(e.getMessage()); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@isTest | |
private class LCCGoogleDocController_Test { | |
@testSetup | |
private static void createTestData(){ | |
Account acc = new Account(Name = 'Test1'); | |
insert acc; | |
List<GoogleDoc> gdocs = new List<GoogleDoc>(); | |
gdocs.add(new GoogleDoc(Name = 'RTest1', Url = 'https://docs.google.com/presentation/d/1I1fNkH-Au-tTQFR3-YCaFY1OL4GeggwsIM92zzW40vE/edit#slide=id.p', ParentId = acc.Id)); | |
insert gdocs; | |
} | |
private testMethod static void test1(){ | |
List<Account> accList = [SELECT Id FROM Account LIMIT 1]; | |
LCCGoogleDocController.getGoogleDocs(accList[0].Id); | |
LCCGoogleDocController.saveGoogleDoc(accList[0].Id,'RTest2','https://docs.google.com/presentation/d/1I1fNkH-Au-tTQFR3-YCaFY1OL4GeggwsIM92zzW40vE/edit#slide=id.s'); | |
LCCGoogleDocController.deleteGoogleDoc([SELECT Id FROM GoogleDoc WHERE ParentId = :accList[0].Id LIMIT 1].Id); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<aura:component implements="flexipage:availableForRecordHome,force:hasRecordId" access="global" controller="LCCGoogleDocController"> | |
<aura:handler name="init" value="{!this}" action="{!c.doInit}"/> | |
<aura:attribute name="gdocs" type="object[]" description="variable to hold list of existing google docs"/> | |
<aura:attribute name="gdocName" type="string" description="input variable to hold name of the document"/> | |
<aura:attribute name="gdocUrl" type="string" description="input variable to hold url of the document"/> | |
<aura:attribute name="columns" type="List" description="variable to hold columns name to display in the lightning table"/> | |
<lightning:notificationsLibrary aura:id="notifLib"/> | |
<lightning:button variant="success" label="Add Google Doc" title="Brand action" onclick="{!c.openmodal}" /><br/><br/> | |
<lightning:datatable keyField="id" data="{!v.gdocs}" columns="{!v.columns}" hideCheckboxColumn="true" wrapTextMaxLines="3" | |
onrowaction="{! c.handleRowAction }"/> | |
<div role="dialog" tabindex="-1" aria-labelledby="header43" aura:id="Modalbox" class="slds-modal"> | |
<div class="slds-modal__container"> | |
<div class="slds-modal__header"> | |
<lightning:buttonIcon alternativeText="Close" onclick="{!c.closeModal}" iconName="utility:close" variant="bare" size="large" class="slds-modal__close slds-button_icon-inverse" /> | |
<h2 id="header43" class="slds-text-heading--medium">Add Existing Google Doc</h2> | |
</div> | |
<div class="slds-modal__content slds-p-around--medium"> | |
<div> | |
<lightning:input aura:id="gdocName" name="gdocName" required="true" value="{!v.gdocName}" label="Google Doc Name" maxlength="80" /> | |
<lightning:input aura:id="gdocURL" name="gdocURL" type="url" required="true" value="{!v.gdocUrl}" label="Google Doc URL" maxlength="255"/> | |
</div> | |
</div> | |
<div class="slds-modal__footer"> | |
<lightning:button variant="destructive" label="Cancel" title="Cancel" onclick="{! c.handleCancel }"/> | |
<lightning:button variant="brand" label="Save" title="Brand action" onclick="{!c.handleSave }" /> | |
</div> | |
</div> | |
</div> | |
<div class="slds-backdrop" aura:id="Modalbackdrop"></div> | |
</aura:component> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
({ | |
doInit : function(component, event, helper){ | |
helper.loadGDocs(component, event, helper); | |
}, | |
closeModal:function(component,event,helper){ //logic to close the modal | |
helper.closeModal(component,event,helper); | |
}, | |
openmodal: function(component,event,helper){ // logic to open modal | |
helper.openmodal(component,event,helper); | |
}, | |
handleCancel:function(component,event,helper){ //logic to close the modal | |
helper.closeModal(component,event,helper); | |
}, | |
handleSave:function(component,event,helper){ // logic to handle save the document and refresh the page. | |
helper.handleSave(component,event,helper); | |
}, | |
showSpinner:function(component,event,helper){ // logic to show spinner | |
helper.showSpinner(component,event,helper); | |
}, | |
hideSpinner:function(component,event,helper){ // logic to hide spinner | |
helper.hideSpinner(component,event,helper); | |
}, | |
handleRowAction:function(component,event,helper){ // logic to hide spinner | |
helper.handleRowAction(component,event,helper); | |
} | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
({ | |
loadGDocs:function(component,event,helper){ | |
// define the actions you want to place in lightning data table | |
var actions = [ | |
{ label: 'Delete', name: 'delete' } | |
] | |
// sets the columns names ,actions and data to be displayed in lightning data table | |
component.set('v.columns', [ | |
{label: 'Name', fieldName: 'Name', type: 'text', wrapText: true}, | |
{label: 'Url', fieldName: 'Url', type: 'url', typeAttributes: { target: '_self'}, wrapText: true}, | |
{ type: 'action', typeAttributes: { rowActions: actions } } | |
]); | |
// fetch the existing google docs for the given record id | |
var action = component.get("c.getGoogleDocs"); | |
console.log(component.get("{!v.recordId}")); | |
action.setParams({ | |
"parentId" : component.get("{!v.recordId}") | |
}); | |
action.setCallback(this,function(response){ | |
var status = response.getState(); | |
if(status === 'SUCCESS'){ | |
component.set("{!v.gdocs}",response.getReturnValue()); | |
}else{ | |
// display alert stating some error happend and contact your sys admin | |
// error handling | |
let errors = response.getError(); | |
let msg = ''; | |
// Retrieve the error message sent by the server | |
if (errors && Array.isArray(errors) && errors.length > 0) { | |
for (var i = 0; i < errors.length; i++) { | |
msg = msg +'Error'+ i + ':' + errors[i].message; | |
} | |
} | |
// Display the message | |
console.error(msg); | |
component.find('notifLib').showToast({ | |
"variant": "error", | |
"title": "Error", | |
"message": msg, | |
"mode":"sticky" | |
}); | |
} | |
}); | |
$A.enqueueAction(action); | |
}, | |
closeModal:function(component,event,helper){ //logic to close the modal | |
var cmpTarget = component.find('Modalbox'); | |
var cmpBack = component.find('Modalbackdrop'); | |
$A.util.removeClass(cmpBack,'slds-backdrop--open'); | |
$A.util.removeClass(cmpTarget, 'slds-fade-in-open'); | |
}, | |
openmodal: function(component,event,helper) { // logic to open modal | |
var cmpTarget = component.find('Modalbox'); | |
var cmpBack = component.find('Modalbackdrop'); | |
$A.util.addClass(cmpTarget, 'slds-fade-in-open'); | |
$A.util.addClass(cmpBack, 'slds-backdrop--open'); | |
}, | |
// function automatic called by aura:waiting event | |
showSpinner: function(component, event, helper) { | |
// remove slds-hide class from mySpinner | |
var spinner = component.find("mySpinner"); | |
$A.util.removeClass(spinner, "slds-hide"); | |
}, | |
// function automatic called by aura:doneWaiting event | |
hideSpinner : function(component,event,helper){ | |
// add slds-hide class from mySpinner | |
var spinner = component.find("mySpinner"); | |
$A.util.addClass(spinner, "slds-hide"); | |
}, | |
// function to handle row level actions for data table | |
handleRowAction : function(component,event,helper){ | |
var action = event.getParam('action'); | |
var row = event.getParam('row'); | |
switch (action.name) { | |
case 'delete': | |
this.deleteGDoc(component, event, helper, row); | |
break; | |
} | |
}, | |
// function to handle delete selected google document | |
deleteGDoc : function(component, event, helper, row){ | |
var rows = component.get('v.gdocs'); | |
var rowIndex = rows.indexOf(row); | |
console.log('row:'+row.Id); // gets the record id of the selected google document to delete | |
// pas the row id to the deleteGoogleDoc method | |
var action = component.get("c.deleteGoogleDoc"); | |
action.setParams({ | |
"recordId" : row.Id | |
}); | |
action.setCallback(this, function(response){ | |
var status = response.getState(); | |
var msg = ''; | |
if(status === 'SUCCESS'){ | |
msg = response.getReturnValue(); // store the success message to display to user | |
component.find('notifLib').showToast({ | |
"variant": "success", | |
"title": "Success", | |
"message": msg, | |
"mode":"sticky" | |
}); | |
this.loadGDocs(component,event,helper); // refetch the data | |
} | |
else{ | |
// error handling | |
let errors = response.getError(); | |
// Retrieve the error message sent by the server | |
if (errors && Array.isArray(errors) && errors.length > 0) { | |
for (var i = 0; i < errors.length; i++) { | |
msg = msg +'Error'+ i + ':' + errors[i].message; | |
} | |
} | |
// Display the message | |
console.error(msg); | |
component.find('notifLib').showToast({ | |
"variant": "error", | |
"title": "Error", | |
"message": msg, | |
"mode":"sticky" | |
}); | |
} | |
}); | |
$A.enqueueAction(action); | |
}, | |
handleSave: function(component,event,helper) { | |
console.log('Name:'+component.get("{!v.gdocName}")); | |
console.log('Url:'+component.get("{!v.gdocUrl}")); | |
var gdocName = component.get("{!v.gdocName}"); //document name attribute | |
var gdocURL = component.get("{!v.gdocUrl}"); // document url attribute | |
// null check for gdocName | |
var gdocName1 = component.find("gdocName"); | |
// is input valid text? | |
if (typeof gdocName === 'undefined') { | |
gdocName1.setCustomValidity("Complete this field."); | |
} else { | |
gdocName1.setCustomValidity(""); // if there was a custom error before, reset it | |
} | |
gdocName1.reportValidity(); // Tells lightning:input to show the error right away without needing interaction | |
// end of null check for gdocName | |
// null check for gdocURL | |
var gdocURL1 = component.find("gdocURL"); | |
// is input valid text? | |
if (typeof gdocURL === 'undefined') { | |
gdocURL1.setCustomValidity("Complete this field."); | |
} else { | |
gdocURL1.setCustomValidity(""); // if there was a custom error before, reset it | |
} | |
gdocURL1.reportValidity(); // Tells lightning:input to show the error right away without needing interaction | |
// end of null check for gdocURL | |
if(typeof gdocName !== 'undefined' && typeof gdocURL !== 'undefined'){ // null checks | |
console.log('testing works'); | |
var action = component.get("c.saveGoogleDoc"); | |
console.log(component.get("{!v.recordId}")); | |
action.setParams({ | |
"parentId" : component.get("{!v.recordId}"), | |
"name" : gdocName, | |
"url" : gdocURL | |
}); | |
action.setCallback(this,function(response){ | |
var status = response.getState(); | |
var msg = ''; | |
// logic to close the modal | |
helper.closeModal(component,event,helper); | |
if(status === 'SUCCESS'){ | |
msg = response.getReturnValue(); // store the success message to display to user | |
component.find('notifLib').showToast({ | |
"variant": "success", | |
"title": "Success", | |
"message": msg, | |
"mode":"sticky" | |
}); | |
this.loadGDocs(component,event,helper); // refetch the data | |
component.set("{!v.gdocName}",''); // reset the field value | |
component.set("{!v.gdocUrl}",''); // reset the field value | |
}else{ | |
// error handling | |
let errors = response.getError(); | |
// Retrieve the error message sent by the server | |
if (errors && Array.isArray(errors) && errors.length > 0) { | |
for (var i = 0; i < errors.length; i++) { | |
msg = msg +'Error'+ i + ':' + errors[i].message; | |
} | |
} | |
// Display the message | |
console.error(msg); | |
component.find('notifLib').showToast({ | |
"variant": "error", | |
"title": "Error", | |
"message": msg, | |
"mode":"sticky" | |
}); | |
} | |
}); | |
$A.enqueueAction(action); | |
} | |
} | |
}) |
And a couple of screenshots to show how it looks:
If this post really helped you to fix your issue please let me know in the comments section. Also please share it with your friends who can benefit from similar situation.
P.S: This is my very first blog post, so if you see anything that can be improved in the post, please do suggest in the comments section. Thanks.