At Silverline, my role is a Solution Architect/Senior Business Analyst and I specialize in declarative builds. A couple months back, I received a request to build a custom Visualforce related-lists component for Silverline’s internal org. Though custom related lists for the Salesforce Classic UI have always been a common ask, there was a twist: The list(s) would be placed on a tabbed component – with each tab having its own list.
Still, I was pretty quick to take on the challenge and underestimated the level of effort. On the surface, we know Visualforce provides all the components needed out of the box, and it would just be a matter of putting them together… right? Easy, yes?
Well, between some of the quirks of Visualforce and some exuberance to build something reusable, I turned a moderate task into a frustrating fulfilling learning experience that left a permanent bruise on my forehead us with a nifty, object-agnostic custom related list component.
Requirements
Provide a related list on a detail page of a parent object. In this case, we have a Project object (parent) and a Project Log object (child).
The related list should have the following functionality:
- Inline Editing
- ”New” button to add new records
- Edit and Delete links
- Pagination
These related lists should be displayed in tabs, with each tab dedicated to a records type for the Project Log records.
Solution
Inline Editing
Visualforce provides <apex:inlineEditSupport> which allow various components, including <apex:pageBlockTable> and <apex:outputField>, to become double-click in-line editable.
Pagination and Sorting
I recalled a previous project that also involved a custom related list, one where there were thousands of records to sort and filter. One of our talented developers had used the Datatables Javascript plugin. This turned out to be the ideal solution for a myriad of reasons:
- Simplified controller logic
- Better performance
- Sleeker styling
- Replaces Tablesorter
- Adds search functionality
However, there was lag between the table rendering and datatable initialization, this was remedied by simply toggling the table’s visibility during datatable initialization:
type="text/javascript"> jQuery(document).ready applyDataTable() ); function applyDataTable(){ jQuery("[id$='relatedTable']").DataTable({ "lengthMenu": [[5,10, 25, -1], [5,10, 25, "All"]] }); jQuery("[id$='dkPbTable']").show(); } function hideTable(){ jQuery("[id$='dkPbTable']").hide(); }
Trouble with Tabs – Multiple Instances of Datatables
After adding the component to each tab, I ran across a conflict: It turns out Datatable cannot be initialized more than once. As the components are all rendered when the tabbed page loads, all the tables are instantiated with each attempting to apply Datatables.
The solution here was to create or destroy the table on each tab with <apex:dynamicComponent> based on whether the tab is active:
<apex:tab label="Issues" name="issues" id="tabIssues" ontabenter="enableTab(1)"> <apex:outputPanel id="IssuePanel"> <apex:form > <apex:dynamicComponent id="issuesListComponent" componentValue="{!IssuesList}"/> <apex:actionFunction name="tab1" action="{!switchTabs}" rerender="ActionItemPanel,RiskPanel,decisionPanel,IssuePanel"> <apex:param name="tab1Param" assignTo="{!currentTab}" value="tab1"/> </apex:actionFunction> </apex:form> </apex:outputPanel> </apex:tab>
The New Button
Here were the requirements for the “New” button:
- The edit page for the new record should have the Project lookup field (the parent object) already populated.
- The RecordTypeId for the new record should be pre-populated based on the tab where the list is rendered (since each tab is dedicated to a record type).
There are only two options here::
- Customize a record creation page
- URL hacking ( sigh…)
I chose option 2 due to time constraints, but this required additional attributes to be passed to the component for:
- Parent record’s name
- Record type Id that the edit page for the new record should have pre-populated
- The field Id of the lookup field to the parent record(!)
I placed the field Id in a custom label that would be updated with each deployment.
Here’s the markup for the “New” button:
<apex:commandButton value="New" onclick="newRecordWindow( '/{!objectPrefix}/e?RecordType={!recordTypeId}&CF{!parentLookupFieldId}_lkid={!parentId}&CF{!parentLookupFieldId}={!parentName}&retURL=%2F{!parentId}')" rerender="dkPbTable" oncomplete="applyDataTable()"/>
*Note here that the custom label populates the parentLookupFieldId in the controller. This could’ve been done directly on the VF page.
Re-rendering with “Save” and “Cancel”
There was one last challenge: clicking the “Save” or “Cancel” buttons would refresh the table even if there weren’t any changes pending.
In order to disable the buttons when no in-line edits were made to the table, I used the MutationObserver web API to detect for style changes in the table. Specifically, when an inline edit is made, styling of the corresponding HTML is updated to the ‘inlineEditModified’ class.
*Note: It’s been pointed out to me by one of our awesome TAs, Kyle Lawson, that a much simpler solution would have been to use an actionSupport element to disable the buttons via controller.
Using jQuery with MutationObserver, I was able to toggle the disabled attribute of the buttons:
function observerStart(){ var observer = new MutationObserver( function(mutationList){ if(jQuery(".inlineEditModified").length != 0){ //turn off the observer to avoid recursion observer.disconnect(); jQuery(".toggleable").prop('disabled',false); observer.observe(jQuery(".listPageBlock").get(0), {attributes: true, childList: false, subtree: true}); } else{ observer.disconnect(); jQuery(".toggleable").prop('disabled',true); observer.observe(jQuery(".listPageBlock").get(0), {attributes: true, childList: false, subtree: true}); } } ) observer.observe(jQuery(".listPageBlock").get(0), {attributes: true, childList: false, subtree: true}); }
One important thing to note is that the MutationObserver needs to be turned off prior to updating any properties. Not doing so will lead to recursion (seems so obvious now) and cause the browser tab to crash.
All I can say after building this is: thank goodness for Lightning (components).
You can access the full project code here and comment below if you have any questions.
Note: Credit to this post by Dave Helgerson titled ‘In-line Editing Visualforce Component for Any Object’ for providing some inspiration and guidance.