Hey Users,
Sometimes it is painful if you have to edit multiple child records and you have to open each record and update them. This component will make Related List with Inline Edit support. You can edit multiple rows and save them instantly. Also, if any error will occur, it will be visible instantly as Toast Message.
This component supports multiple salesforce datatypes and all necessary validations are already present. Pros and cons of the component are-
Pros of Component
- Single Unit Component
- Major Exceptions Already Handled.
- Inline Edit based on field permission. If no permission, no Inline Edit.
- Auto Scroll-bar if more than 5 Child Records are present.
- Supports major SF datatypes like Text, Textarea, Picklist, Number, Percent, Currency, Date, Time, DateTime, Checkbox, etc.
Cons of Component
- Lookup, Rich Text Area, Geo-Location are yet in view mode only.
Demo GIF
Also Check, Multi-Select Lookup Lightning Component
Step-1: Create an Apex controller
Our apex controller will have all the necessary Validations. Apex class will fetch the child records and save the records. Here we are using a wrapper class to pass data to the lightning component. Go to Dev. console > File > New > Apex Class.
We need to create an Exception class first to throw the custom exception.
Create MyException.cls Apex class.
1 2 3 4 5 |
public class MyException extends Exception { } |
Create AdvancedRelatedListController.cls Apex class and paste the below code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 |
public class AdvancedRelatedListController { @AuraEnabled public static ReturnWrapper fetchRecords(String parentRecordId, String sObjectName, String fieldsList, String isEdit) { ReturnWrapper result = new ReturnWrapper(); List<FieldWrapper> fieldsWP = new List<FieldWrapper>(); try { Set<String> fields = new Set<String>(fieldsList.split(',')); Schema.SObjectType objectName = Schema.getGlobalDescribe().get(sObjectName); Map<String,Schema.SObjectField> sObjectFieldMap = objectName.getDescribe().fields.getMap(); if(objectName == null) { throw new MyException('Object API Name is Not Valid'); } Boolean childObjectFlag = false; String relationshipName = ''; for(Schema.ChildRelationship f : Id.valueof(parentRecordId).getSobjectType().getDescribe().getChildRelationships()) { if(String.valueof(f.getChildSObject()).toLowerCase() == sObjectName.toLowerCase()) { childObjectFlag = true; relationshipName = String.valueof(f.getField()); } } if(!childObjectFlag) throw new MyException('Object you selected is not a child object of this record. Object Name : ' + sObjectName); Set<String> validFields = new Set<String>(); for(String field : fields) { if(String.isNotBlank(field)) { if(!sObjectFieldMap.keySet().contains(field.trim().toLowerCase())) { throw new MyException('Fields API Name is Not Valid. Field : ' + field.trim()); } Schema.DescribeFieldResult fieldDesc = sObjectFieldMap.get(field.trim().toLowerCase()).getDescribe(); if(String.valueof(fieldDesc.getType()) == 'LOCATION') { throw new MyException('Geo Location Fields are not supported : Field ' + field.trim()); } if(String.valueof(fieldDesc.getType()) == 'PICKLIST') { fieldsWP.add(new FieldWrapper(fieldDesc.getLabel(), fieldDesc.getName(), String.valueof(fieldDesc.getType()), apexTypetoJSType.get(String.valueof(fieldDesc.getType())), fieldDesc.isUpdateable(), getPicklistValues(sObjectName, fieldDesc.getName()))); } else { fieldsWP.add(new FieldWrapper(fieldDesc.getLabel(), fieldDesc.getName(), String.valueof(fieldDesc.getType()), apexTypetoJSType.get(String.valueof(fieldDesc.getType())), fieldDesc.isUpdateable(), null)); } validFields.add(field); } } String query = 'SELECT ' + String.join(new List<String>(validFields), ','); if(!validFields.contains('id')) { query += ', id'; } query += ' FROM ' + objectName + ' WHERE '+ relationshipName + ' = \'' + parentRecordId + '\' LIMIT 49999'; result.fieldsList = fieldsWP; result.recordList = Database.query(query); return result; } catch (Exception err) { throw new AuraHandledException(err.getMessage()); } } private static List<PicklistOptions> getPicklistValues(String objectName, String fieldName) { List<PicklistOptions> picklistValues = new List<PicklistOptions>(); List<Schema.PicklistEntry> ple = Schema.getGlobalDescribe().get(objectName).getDescribe().fields.getMap().get(fieldName).getDescribe().getPicklistValues(); for(Schema.PicklistEntry pkValue : ple) { picklistValues.add( new PicklistOptions(pkValue.getLabel(), pkValue.getValue()) ); } return picklistValues; } @AuraEnabled public static String saveRecord(String objectName, String recordData) { try { Map<String, Object> fieldMap = (Map<String, Object>)JSON.deserializeUntyped(recordData); Schema.SObjectType targetType = Schema.getGlobalDescribe().get(objectName); SObject newSobject = targetType.newSObject(); for (String key : fieldMap.keySet()) { Object value = fieldMap.get(key); Schema.DisplayType valueType = targetType.getDescribe().fields.getMap().get(key).getDescribe().getType(); if (value instanceof String && valueType != Schema.DisplayType.String) { String svalue = (String)value; if (valueType == Schema.DisplayType.Date) newSobject.put(key, Date.valueOf(svalue)); else if(valueType == Schema.DisplayType.DateTime) newSobject.put(key, DateTime.valueOf(svalue)); else if (valueType == Schema.DisplayType.Percent || valueType == Schema.DisplayType.Currency) newSobject.put(key, svalue == '' ? null : Decimal.valueOf(svalue)); else if (valueType == Schema.DisplayType.Double) newSobject.put(key, svalue == '' ? null : Double.valueOf(svalue)); else if (valueType == Schema.DisplayType.Integer) newSobject.put(key, Integer.valueOf(svalue)); else if (valueType == Schema.DisplayType.Base64) newSobject.put(key, Blob.valueOf(svalue)); else if (valueType == Schema.DisplayType.Time) newSobject.put(key, Time.newInstance(Integer.valueOf(svalue.split(':')[0]), Integer.valueOf(svalue.split(':')[1]), 00, 00)); else newSobject.put(key, svalue); } else newSobject.put(key, value); } update newSobject; return 'success'; } catch(Exception err) { throw new AuraHandledException(err.getMessage()); } } public static Map<String, String> apexTypetoJSType = new Map<String, String> { 'ID' => 'id', 'PHONE' => 'tel', 'URL' => 'url', 'EMAIL' => 'email', 'ADDRESS' => 'text', 'TEXTAREA' => 'text', 'STRING' => 'text', 'REFERENCE' => 'text', 'MULTIPICKLIST' => 'text', 'PICKLIST' => 'picklist', 'BOOLEAN' => 'checkbox', 'DATE' => 'date', 'DATETIME' => 'datetime', 'TIME' => 'time', 'DOUBLE' => 'number', 'INTEGER' => 'number', 'CURRENCY' => 'number', 'PERCENT' => 'number' }; public class ReturnWrapper { @AuraEnabled public List<sObject> recordList; @AuraEnabled public List<FieldWrapper> fieldsList; } public class FieldWrapper { @AuraEnabled public String label; @AuraEnabled public String value; @AuraEnabled public String dataType; @AuraEnabled public String ltngType; @AuraEnabled public Boolean isEditable; @AuraEnabled public List<PicklistOptions> picklistValues; public FieldWrapper(String label, String value, String dataType, String ltngType, Boolean isEditable, List<PicklistOptions> picklistValues) { this.label = label; this.value = value; this.dataType = dataType; this.ltngType = ltngType; this.isEditable = isEditable; this.picklistValues = picklistValues; } } public class PicklistOptions { @AuraEnabled public String label; @AuraEnabled public String value; public PicklistOptions(String label, String value) { this.label = label; this.value = value; } } } /* Code by CafeForce.com | support@cafeforce.com DO NOT REMOVE THIS FOOTER FOR FREE CODE USAGE */ |
Step-2: Create the AdvancedRelatedList Lightning Component.
For creating a new Lightning Component, go to dev. console > File > New > Lightning Component.
Now open the AdvancedRelatedList.cmp file and paste the below code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
<aura:component controller="AdvancedRelatedListController" implements="flexipage:availableForAllPageTypes,force:hasRecordId" > <!-- Attributes --> <aura:attribute name="objectName" type="String" required="true" access="global"/> <aura:attribute name="fieldsName" type="String" required="true" access="global"/> <aura:attribute name="headerName" type="String" required="true" access="global"/> <aura:attribute name="isEditable" type="Boolean" access="global" default="false" /> <aura:attribute name="iconName" type="String" access="global" default="standard:contact" /> <aura:attribute name="records" type="List" access="private" /> <aura:attribute name="recordsCopy" type="List" access="private" /> <aura:attribute name="fields" type="List" access="private" /> <!-- Events --> <aura:handler name="init" action="{!c.doInit}" value="{!this}"/> <div><div class="ARL"> <div class="headerDiv slds-clearfix"> <div class="slds-float_left slds-p-around_xx-small"> <span> <lightning:icon iconName="{!v.iconName}" alternativeText="icon" size="small" /> </span> <span class="slds-p-around_small">{!v.headerName} ({!v.records.length})</span> </div> <div class="slds-float_right"> <lightning:buttonIcon iconName="utility:refresh" alternativeText="refresh" onclick="{!c.doInit}"/> </div> </div> <div class="childTable"> <table class="customDataTable slds-table slds-table_bordered slds-table_edit slds-table_fixed-layout" role="grid"> <thead> <tr class="slds-line-height_reset"> <aura:iteration items="{!v.fields}" var="field"> <th> <span>{!field.label}</span> </th> </aura:iteration> <th></th> </tr> </thead> <tbody> <aura:iteration items="{!v.records}" var="rcrd" indexVar="index"> <tr> <aura:iteration items="{!rcrd.record}" var="data"> <td class="{!if(and(rcrd.editMode, data.isEdit), '', 'slds-truncate')}"> <aura:if isTrue="{!and(data.ltngType ne 'picklist', data.ltngType ne 'checkbox')}"> <aura:if isTrue="{!and(rcrd.editMode, data.isEdit)}"> <lightning:input type="{!data.ltngType}" variant="label-hidden" value="{!data.value}" name="{!rcrd.Id}" validity="false"/> <aura:set attribute="else"> <aura:if isTrue="{!data.ltngType eq 'text'}"> <span>{!data.value}</span> </aura:if> <aura:if isTrue="{!data.ltngType eq 'id'}"> <span>{!data.value}</span> </aura:if> <aura:if isTrue="{!data.ltngType eq 'tel'}"> <lightning:formattedPhone value="{!data.value}" /> </aura:if> <aura:if isTrue="{!data.ltngType eq 'email'}"> <lightning:formattedEmail value="{!data.value}" hideIcon="true"/> </aura:if> <aura:if isTrue="{!data.ltngType eq 'url'}"> <lightning:formattedUrl value="{!data.value}" target="_blank" /> </aura:if> <aura:if isTrue="{!data.ltngType eq 'date'}"> <lightning:formattedDateTime value="{!data.value}"/> </aura:if> <aura:if isTrue="{!data.ltngType eq 'datetime'}"> <lightning:formattedDateTime value="{!data.value}" year="numeric" month="numeric" day="numeric" hour="2-digit" minute="2-digit" timeZoneName="short" hour12="true"/> </aura:if> <aura:if isTrue="{!data.ltngType eq 'number'}"> <lightning:formattedNumber value="{!data.value}"/> </aura:if> <aura:if isTrue="{!data.ltngType eq 'time'}"> <lightning:formattedTime value="{!data.value}" /> </aura:if> </aura:set> </aura:if> </aura:if> <aura:if isTrue="{!data.ltngType eq 'picklist'}"> <aura:if isTrue="{!and(rcrd.editMode, data.isEdit)}"> <lightning:combobox name="{!rcrd.Id}" variant="label-hidden" value="{!data.value}" options="{!data.picklistOptions}" /> <aura:set attribute="else"> <span>{!data.value}</span> </aura:set> </aura:if> </aura:if> <aura:if isTrue="{!data.ltngType eq 'checkbox'}"> <aura:if isTrue="{!and(rcrd.editMode, data.isEdit)}"> <lightning:input type="checkbox" name="{!rcrd.Id}" checked="{!data.value}" variant="label-hidden"/> <aura:set attribute="else"> <lightning:input type="checkbox" name="{!rcrd.Id}" checked="{!data.value}" disabled="true" variant="label-hidden"/> </aura:set> </aura:if> </aura:if> </td> </aura:iteration> <aura:if isTrue="{!v.isEditable}"> <aura:if isTrue="{!!rcrd.editMode}"> <td class="slds-truncate"> <lightning:buttonIcon name="{!rcrd.Id}" onclick="{!c.editRow}" iconName="utility:edit" variant="container" /> </td> <aura:set attribute="else"> <td class="slds-truncate"> <lightning:buttonIcon name="{!rcrd.Id}" class="checkIco" onclick="{!c.saveRow}" iconName="utility:check" alternativeText="Save" /> <lightning:buttonIcon name="{!rcrd.Id}" class="closeIco" onclick="{!c.resetRow}" iconName="utility:close" alternativeText="Reset" /> </td> </aura:set> </aura:if> </aura:if> </tr> </aura:iteration> </tbody> </table> </div> </div></div> </aura:component> <!-- Code by CafeForce.com | support@cafeforce.com DO NOT REMOVE THIS FOOTER FOR FREE CODE USAGE --> |
Now open the controller file AdvancedRelatedListController.js and paste the below code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
({ doInit : function(component, event, helper) { var objectName = component.get('v.objectName'); var fieldsName = component.get('v.fieldsName'); var recordId = component.get('v.recordId'); if(!$A.util.isEmpty(objectName) && !$A.util.isEmpty(fieldsName) && !$A.util.isEmpty(recordId)) { helper.doInitHelper(component, helper, recordId, objectName, fieldsName); } }, editRow: function (component, event, helper) { var rowId = event.getSource().get('v.name'); var records = component.get('v.records'); var rowIndex = records.findIndex(x => x.Id === rowId); if (rowIndex != -1) { records[rowIndex].editMode = true; } component.set('v.records', records); }, resetRow: function (component, event, helper) { var rowId = event.getSource().get('v.name'); var resetRow; var recordsCopy = component.get('v.recordsCopy'); var rowIndex = recordsCopy.findIndex(x => x.Id === rowId); if (rowIndex != -1) resetRow = JSON.parse(JSON.stringify(recordsCopy[rowIndex])); var records = component.get('v.records'); var index = records.findIndex(x => x.Id === rowId); if (index != -1) records[index] = resetRow; component.set('v.records', records); }, saveRow: function (component, event, helper) { var recordObj; var recordId = event.getSource().get('v.name'); var records = component.get('v.records'); var index = records.findIndex(x => x.Id === recordId); if (index != -1) recordObj = records[index]; for(var i = 0; i < recordObj.record.length; i++) { if(recordObj.record[i].dataType == 'DATETIME') { recordObj.record[i].value = $A.localizationService.formatDate(recordObj.record[i].value, "yyyy-MM-dd HH:mm:ss"); } } helper.saveRowHelper(component, recordId, recordObj); }, }) |
Open the helper file AdvancedRelatedListHelper.js and paste the below code.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
/* Code by CafeForce || www.cafeforce.com || support@cafeforce.com || Mandatory Header */ ({ doInitHelper : function(component, helper, recordId, objectName, fieldsName) { var isEditable = component.get('v.isEditable'); var action = component.get('c.fetchRecords'); action.setParams({ 'parentRecordId' : recordId, 'sObjectName' : objectName, 'fieldsList' : fieldsName, 'isEdit' : component.get('v.isEditable') }); action.setCallback(this,function(response){ var result = response.getReturnValue(); if(response.getState() === 'SUCCESS') { if(!$A.util.isEmpty(result)) { var records = []; var fieldToDTMap = new Map(); var fieldToLDTMap = new Map(); var fieldToEditMap = new Map(); var fieldToPicklistValMap = new Map(); for(var j = 0; j < result.fieldsList.length; j++) { fieldToEditMap.set(result.fieldsList[j]['value'], isEditable ? result.fieldsList[j]['isEditable'] : false); fieldToDTMap.set(result.fieldsList[j]['value'], result.fieldsList[j]['dataType']); fieldToLDTMap.set(result.fieldsList[j]['value'], result.fieldsList[j]['ltngType']); if(result.fieldsList[j]['ltngType'] == 'picklist') fieldToPicklistValMap.set(result.fieldsList[j]['value'], result.fieldsList[j]['picklistValues']); } for(var i = 0; i < result.recordList.length; i++) { var cell = []; for(var j = 0; j < result.fieldsList.length; j++) { var value = ''; var fieldAPI = result.fieldsList[j]['value']; for(var key in result.recordList[i]) { if(key == fieldAPI) { if(result.fieldsList[j]['dataType'] == 'TIME') { value = helper.convertTime(result.recordList[i][key]); } else { value = result.recordList[i][key]; } } } if(fieldToLDTMap.has(result.fieldsList[j]['value'])) cell.push({ 'label':fieldAPI, 'value':value,'isEdit':fieldToEditMap.get(fieldAPI), 'dataType':result.fieldsList[j]['dataType'], 'ltngType': result.fieldsList[j]['ltngType'], 'picklistOptions': fieldToPicklistValMap.get(fieldAPI) }); } records.push({'Id': result.recordList[i].Id, 'record':cell}); } component.set('v.fields', result.fieldsList); component.set('v.records', JSON.parse(JSON.stringify(records))); component.set('v.recordsCopy', JSON.parse(JSON.stringify(records))); } } else { console.log(response.getError()); var errors = response.getError(); if (errors && errors[0] && errors[0].message) { helper.showToast('error', errors[0].message); } } }); $A.enqueueAction(action); }, saveRowHelper : function(component, recordId, recordObj) { var self = this; var recordData = {}; recordData['Id'] = recordId; for(var i = 0; i < recordObj.record.length; i++) { if(recordObj.record[i].isEdit) recordData[recordObj.record[i].label] = recordObj.record[i].value; } var action = component.get('c.saveRecord'); action.setParams({ 'objectName' : component.get('v.objectName'), 'recordData' : JSON.stringify(recordData) }); action.setCallback(this,function(response) { if(response.getState() === 'SUCCESS') { self.showToast('Success', 'Record Updated Successfully'); for(var i = 0; i < recordObj.record.length; i++) { if(recordObj.record[i].dataType == 'DATETIME') { recordObj.record[i].value = $A.localizationService.formatDate(recordObj.record[i].value, "yyyy-MM-ddTHH:mm:ss.000Z"); } } var recordsCopy = component.get('v.recordsCopy'); var rowIndex = recordsCopy.findIndex(x => x.Id === recordId); if (rowIndex != -1) { recordObj.editMode = false; recordsCopy[rowIndex] = JSON.parse(JSON.stringify(recordObj)); } component.set('v.recordsCopy', recordsCopy); var records = component.get('v.records'); var index = records.findIndex(x => x.Id === recordId); if (index != -1) records[index].editMode = false; component.set('v.records', records); } else { console.log(response.getError()); var errors = response.getError(); if (errors && errors[0] && errors[0].message) { self.showToast('error', errors[0].message); } } }); $A.enqueueAction(action); }, convertTime : function(time) { var convertedTime = time/3600000; var hour = convertedTime.toString().split('.')[0]; var minute = $A.util.isEmpty(convertedTime.toString().split('.')[1]) ? '00' : (Number(convertedTime.toString().split('.')[1]) * 60 ) / 100; return ((hour.length == 1) ? '0' + hour : hour) +':'+ ((minute.toString().length == 1) ? minute + '0' : minute) +':00.000Z'; }, showToast : function(type, message) { var toastEvent = $A.get("e.force:showToast"); toastEvent.setParams({ "type": type, "message": message }).fire(); } }) /* Code by CafeForce Website: http://www.cafeforce.com DO NOT REMOVE THIS HEADER/FOOTER FOR FREE CODE USAGE */ |
Open the helper file AdvancedRelatedList.design and paste the below code.
1 2 3 4 5 6 7 8 9 |
<design:component label="Advanced Related List"> <design:attribute name="headerName" label="Header Title" /> <design:attribute name="objectName" label="Object"/> <design:attribute name="fieldsName" label="Fields(comma seprated API Names)"/> <design:attribute name="isEditable" label="Inline Editable Cells" /> <design:attribute name="iconName" label="Icon Name" /> </design:component> |
Now finally paste the CSS in the AdvancedRelatedList.css file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
.THIS .ARL { border-radius: 4px; background: #eee; border: 1px solid #ddd; } .THIS .ARL .headerDiv { padding: 18px; background-color: #F3F2F2; font-size: 16px; font-weight: 700; } .THIS .ARL .childTable { min-height: 230px; height: 230px; overflow: auto; } .THIS .ARL .slds-table_edit thead th { padding: 4px 8px; } /* For Scrolling */ .THIS ::-webkit-scrollbar { width: 7px; height: 7px; } .THIS ::-webkit-scrollbar-track { display: none !important; } .THIS ::-webkit-scrollbar-thumb { border-radius: 10px; background: rgba(0,0,0,0.4); } |
Step-3: Add the component on the record page.
Our component is ready to use. Let’s put it on the Record Detail Page. Go to your record > Click on setting Gear on top > Edit Page.
On the Lightning App Builder in the Left Pane, find Advanced Related List and drop it on the record page. Now you need to set the Header, Object API name, Fields API Name List, Icon Name, and check the checkbox if you want the Inline Edit button.
You can check the list of available icons here LDS Icons.
Hurray… All Set… Now Save and Activate the Page from the top and click on the back Icon. Your Related List component is visible now.
Also Check:
For any queries or suggestions/queries, comment below.
Cheers … Happy Coding … 🙂
Hi, I am getting a compile error when saving the apex class: Invalid type: MyException. Can you please provide some guidance on how to resolve this?
Hi Hamish,
I have added the MyException Class in the Step-1. Thanks for reminding.
absolutely great and exactly what i need. however, i have no idea what the test class for the controller should look like. can you help here?
Thanks for the Appreciation. Yes, will share the test class soon. 🙂
Any chance you have the test class ready to go?
Hi Hamish,
Test class is not ready right now. Give me some time, I will upload the same.
Any chance you’ve converted this to a Lightning Web Component?
Hi Mark,
I have not converted this still to LWC but will do it soon and let you know.
Thanks for sharing! Test class and eventually an LWC would be awesome! Looking forward to building this out and testing.
Thanks, Rocky
Yes, will share the Test class and LWC soon.
Hi, do you have the LWC version of the related list yet by any chance? We are running around looking for a solution for a relatedlist with inline editing and lookup fields.
Hi Alphy,
LWC version is not ready yet. I will soon start working on it.
Any word on that LWC? Thank you so much this is amazing and exactly what we need in our Community.
Thanks for Appreciation 🙂
LWC version will be available soon.
This is awesome, Is it possible to show more than 5 rows and can you sort a column ?
Hi Andrew! Thanks for your Appreciation.
Yes, modify the height in this CSS to show more rows.
Many thanks for the quick answer !
Welcome Andrew?
Hi Suyash, this is awesome, but I need a test class to deploy ? Do you have the code for it ?
Hi Andrew,
Not right now but will upload one soon. Thanks.
Ok great I really want to deploy soon
Anyway to get this to work with AccountContactRelation object? Trying to display any related contact while being able to edit.
Thank you!
Try passing the object name but related records won’t be visible in this component.
Suyash:
I’m wondering if you could quote for me how much you would charge to….
Hi Gretchen,
I will upload the test class here soon…. For free ?
I’ve been looking for something like this for awhile to replace some old Visualforce written to allow inline editing. Any chance you’ve written a test class for this yet that you would be willing to share?
Hi Juli, Thanks.
I don’t have test class as of now. I will soon write and share it in post.
I wrote one that gives me 88% coverage. It isn’t the prettiest thing but I’m happy to share if someone needs it.
Sure Juli. Pls share it here.
Thanks Julianne 🙂
Hi Juli,
Can you please share the test class that you have written with me?
Thank you
Akshay
HI
I am trying to add a hyperlink in the data table so it is easy to navigate to the contact record page currently this component displaying all fields as text fields. Any idea on this
Hi Pavan,
For this, you can create a URL datatype field and populate that field with Contact Url using workflow. URL fields are supported by this component.
The problem with URL fields is that click on them automatically opens new window, which quickly becomes cumbersome for end users. Fully supported formula fields (or lookups), with hyperlinks working are nicer as you can control whether or not a new window opens.
Hi Suyash,
This is working great for me. Thank you for this. It would be great if there was a test class for this. Can you also please provide the test class for this code?
Thank you
Akshay
Here is a basic account/opp test class, hopefully this helps someone out there struggling to get it going. Replace with your own specific custom fields if you need to increase code coverage.
Thanks Lee 🙂
Hi Suyash,
Quick Question – in this component default all fields showing editable fields is it possible to change selected fields as non – editable fields on component UI? I made a couple of fields as read-only in profile level still I am able to edit those fields save records from component any thoughts on this.
Thanks,
Pavan
Hi Pavan,
I have already added this check in the Apex on lines 40 and 42. Can you try debugging the Apex code?
Hi Suyash
Thanks for your quick update. I am trying to add a lock on fields to a specific profile. I have shown it in the attached image is there any way to do this?
Hi Pavan,
After lightning:input, in the else part. You can add lightning:icon to show the lock where users doesn’t have edit permission.
Hello I have field dependencies and for the controlling field it is showing all picklist values but for the dependent picklist also it is showing all the values like a normal picklist irrespective of the value selected in the controlling field. Let me know if you have any solution for this.
Thank you!
Hi Garima,
Dependent Picklist is not supported in this component right now. You have to write custom logic to fetch dependent values only. You can add your custom logic in the getPicklistValues method in the Apex class.
Any thoughts on filtering based on recordtype or anything? Also any updates on a working test class?
Hi,
You have to write custom logic for filtering. Including everything in a single component will make it heavy.
Is there a way to click the pencil icon and ALL of the records are unlocked to be edited rather than having to do it for each record?
Bradley – we accomplished this by adding a new mode called EditAllMode with it’s own pencil in the header column. That calls a new component EditAll function which loops through all the records and sets them to EditMode true. Likewise we added a new SaveAll function that also loops through and saves each record and resets the modes. Pretty easy to add and users are quite happy with the results.
Lee, Thanks for the Info. Appreciated 🙂
Hey Lee, this would be great! Do you have the code for this? I am new and trying to learn how this would be added to the above.
Jeremy, I’ll try to summarize the changes:
Here’s how you add the new EditAllMode in the component:
Also in the component, here are the new edit pencils in the header column, it goes in right after the column header label field iteration:
In the controller, here are the new functions to handle the looping:
And you also need to change the doInit function to handle the resetAll:
I think that is all of the changes, I hope this helps!
Thanks so much Lee! This is exactly what I was looking for. I really appreciate your help!
Thanks Lee.
Hello Suyash Nolkha
Thanks for this component, it’s very useful.
I need to add a formula image in my related list. Could you tell me how can i solve that?
Hi Claire,
Thanks for the Appreciation 🙂
You need to add image tag to the cmp file and need to pass the desired datatype value to the image field.
Could you tell me how to add the New button?
Thanks
You can add New button on top near refresh button but you have to write Apex and JS logic to add a new row.
I’ve implemented it, but the button is elsewhere on the screen which is slightly counterintuitive for users. It would be great if the “New” button was alongside, just like with normal Related List views. I don’t mind if it just pops up a new window.
That’s a great idea. We can redirect to the standard New record page on New button click. Thanks.
can you provide the code for new button
You can use
<lightning-button variant=“base” label=“Base” title=“Looks like a link” onclick={handleClick} ></lightning-button>
to create a new button and handle this onclick method in JS. From there you can use window.location.href method to redirect to standard new record page.
Thanks hugely for this. My bit of feedback, for people not familiar with Lightning Web Components is:
For “AdvancedRelatedListController.js”
– Go into the Developer console.
– File | Open Resources
– In “Select an item to open” put “AdvancedRelatedList”
– Double click on AdvancedRelatedList.cmp
– Click on Controller (on the right hand side)
– Paste the code and File | Save
The other elements are also on the same screen
– AdvancedRelatedListHelper.js -> Helper
– AdvancedRelatedList.design -> Design
– AdvancedRelatedList.css -> Style
Thanks, Paul. Apprecited.
How we can add a new button to create new related list records.in this example refresh button is available but how we add new records
Hi Shezi,
For that you can give a lightning-button near the refresh button and either you can redirect to standard new record form in a new tab or you can open a modal and use lightning-record-edit-form in it to create a new record.
Ref: https://developer.salesforce.com/docs/component-library/bundle/lightning-record-edit-form/documentation
I have the component visible but it is not showing the related records. Is there a setting I missed somewhere?
Hi Brandon,
Please check if you passed valid values in Lightning App Builder attributes
Hi, I am getting ‘Your entry isn’t a valid increment’ with the decimal number field, Is there any way to fix?
Hi Priya, Can you share the line number as to exactly where it is coming from or method name?
Hello! I am also getting the the “Your entry isn’t a valid increment” with my decimal number fields. After looking for answers, I have not come across a definitive answer. Has this been solved?
Hi, I was able to follow the directions, but after saving the Advanced List to the page layout I receive this error “Attempt to de-reference a null object”
For information, I am trying to load opportunities and not the contact like the example shown.
I also have NO experience with Apex, I am very, very new, so I have no idea where to start with this error.
Hi Amy,
Can you please tell me what values you are using on App builder?
There’s a chance you don’t have the API name for the fields within the Page setup. I had the same issue, I just assumed the record name on the object was just “Name”, but it turned out to be a different API name. I copied the API name from Object Manager and pasted it directly in, no further issues.
This looks great, thanks for sharing!
I am verrrrry new so following this step by step very closely. I am encountering the following errors:
First, when I save a Class, the dev console saves it with a .apxc extension, not .cls — not sure if this is expected or an actual error, but noting here in case it’s relevant.
In Step 2, when I paste the code for the AdvancedRelatedListController.cls & save, I get 85 problems, mostly unexpected tokens, Invalid types, Variable does not exist, & more. Beyond that, I’m getting the following when saving this .js files. Perhaps these exceptions are due to the initial error with the .cls file?
“FIELD_INTEGRITY_EXCEPTION:
Failed to save AdvancedRelatedListController.js: ESLINT_ERROR: {c:AdvancedRelatedList – CONTROLLER} line:col [6:45] –> Parsing error: Unexpected token ; : Source”
“FIELD_INTEGRITY_EXCEPTION:
Failed to save AdvancedRelatedListHelper.js: ESLINT_ERROR: {c:AdvancedRelatedList – HELPER} line:col [22:67] –> Parsing error: Unexpected token ; : Source”
Are there steps I’m missing? Do I need to Find & Replace something in my own text editor or should this be a direct Copy + Paste? As I said, I’m super beginner, so any advice you can give would be hugely appreciated!!
Same issue. I think we are doing something simple wrong. Any luck solving?
I found the issue. The code example above is rendered with escape characters.
Search and replace:
& with &
> with >
< with <
Hi Matty,
Thanks for highlighting the issue. I will fix this.
I got this working with the changes, but the lack of lookups is making this not workable. Is there anything I can do to add that? I experimented with swapping out:
with:
but that generated an error related to the fieldwrapper:
So I tried defining another wrapper:
but it didn’t like that either.
I also tried adding this, but that did not go anywhere either:
How can I lock this related list once a record has been submitted for approval?
Hi Jennifer,
After submit, you need to set the isEditable attribute to false. This will hide the edit pencil icon.