You need to sign in to do that
Don't have an account?
Charles Thompson
I have come up with a way to replace Classic related lists on a page layout, so you can get past some of the limitations of page layouts. I hope this post will be helpful to our global ohana.
Why would you want to do this?
Related Lists can only have up to 10 columns. My customer has a related custom object with 14 fields that should be displayed on a single line: Year, Jan ... Dec, and Year Total. Each monthly value is a currency field, and multi-currency is on for the Org, meaning that the currency ISO code is displayed for each of these fields. So we also want to display the values without the ISO code.
What did I do to get around this?
My solution includes two Visualforce (VF) pages and a controller extension.
Apex Controller Extension (BudgetExt.apxc):
View VF page (BudgetView.vfp):
Edit VF page (BudgetEdit.vfp):
I hope this helps someone in the future, even if Lightning Experience makes this solution somewhat useless.
How to replace Related Lists (Classic) to get past some platform limitations
I have come up with a way to replace Classic related lists on a page layout, so you can get past some of the limitations of page layouts. I hope this post will be helpful to our global ohana.
Why would you want to do this?
Related Lists can only have up to 10 columns. My customer has a related custom object with 14 fields that should be displayed on a single line: Year, Jan ... Dec, and Year Total. Each monthly value is a currency field, and multi-currency is on for the Org, meaning that the currency ISO code is displayed for each of these fields. So we also want to display the values without the ISO code.
What did I do to get around this?
My solution includes two Visualforce (VF) pages and a controller extension.
- View page: A page thatdisplays the fields in a table, as read-only. On this page is a single button, [Edit] to switch to Edit mode.
- Edit page: A page that displays the fields in a table, as editable fields. It has three buttons: [Add] [Save] [Cancel]
- Apex controller extension. I used an extension because it adds to the functionality of the standard controller, which could be helpful in more complex use cases.
Apex Controller Extension (BudgetExt.apxc):
public with sharing class BudgetExt { /************************************************* * Controller extension to provide functionality required for the Budgets * section of Contact detail page. The business requirement was to allow * the Budgets related list on Contact to display 14 columns (year * + 12 months + total). The end result is an embedded VF page component on the Contact * page layout that displays the Budget related rows in view mode * (read only), with an edit button local to the component. Clicking Edit * switches the component to Edit Mode by changing the PageReference to * the related Edit mode VF page. On that page are three buttons (Add Row, * Save and Cancel) plus a Delete button on each row. Hitting Save or Cancel * brings you back to the View mode page. * * Related VF pages: * - BudgetEdit.vfp * - BudgetView.vfp * * Author: Charles Thompson, Salesforce, Nov 2017 * *************************************************/ private final Id contactID; // store the parent (Contact) ID public Integer index {get;set;} // store the current row being deleted // List to represent each row of the pageBlockTable of Budgets // related to the parent Sub Project public List<Budget__c> budgets {get; set;} // List to hold those rows being deleted public List<Budget__c> budgetsToDel = new List<Budget__c>(); // Variable to hold the Sub Project (parent) record public Contact theContact {get; set;} // Initial load of the pageBlockTable public BudgetExt(ApexPages.StandardController stdController){ contactID = stdController.getId(); this.index = 0; theContact = [SELECT Id FROM Contact WHERE Id = :contactID LIMIT 1 ]; budgets = [SELECT Id, Year__c, Jan__c, Feb__c, Mar__c, Apr__c, May__c, Jun__c, Jul__c, Aug__c, Sep__c, Oct__c, Nov__c, Dec__c, Year_Total__c FROM Budget__c WHERE Contact__c = :contactID ORDER BY Year__c ]; } public String getIndex(){ return String.valueOf(index); } public void setIndex(String indexIn){ this.index = Integer.valueOf(indexIn); } // Add an empty budget row to the table // (not saved to the database until saveBuds() is called) public void addRow(){ Budget__c row = new Budget__c(); row.Contact__c = contactID; budgets.add(row); } // delete a forecast from the table (not saved to the db until later) public void delRow(){ Integer i = this.index-1; // Rows start at one, Lists start at zero // If the row is an existing row then add it to the list // to delete from the database if (budgets[i].Id != null){ budgetsToDel.add(budgets[i]); } //Remove the forecast from the table budgets.remove(i); } public PageReference saveBuds(){ // update and insert forecasts as per the current list upsert budgets; // delete any rows in the list of deletions if ((budgetsToDel != null) && (budgetsToDel.size() > 0)){ delete budgetsToDel; } return cancelEdit(); } public PageReference editBuds(){ // switch to Edit mode PageReference editPage = new PageReference('/apex/BudgetEdit?id=' + contactID); return editPage; } public PageReference cancelEdit(){ // switch back to View mode without saving anything PageReference viewPage = new PageReference('/apex/BudgetView?id=' + contactID); viewPage.setRedirect(true); return viewPage; } }
View VF page (BudgetView.vfp):
<apex:page standardController="Contact" extensions="BudgetExt"> <!------------------------------------------------------------------------ This page is designed to fit as a component on the detail page of the Contact page layout. This is the View Mode version of the page, containing output fields and a button for Edit. Note that the currency ISO code doesn't appear and is assumed to be carried through from the parent record. The related Edit Mode page is called "BudgetEdit". Author: Charles Thompson, Salesforce Singapore, Nov 2017 --------------------------------------------------------------------------> <apex:form > <apex:pageBlock mode="maindetail"> <table width="100%"> <tr> <td style="text-align:center"> <!-- Button... Edit (switch to Edit Mode) --> <apex:commandButton value="Edit" action="{!editBuds}" id="editButton" /> </td> </tr> </table> <!-- Table displaying the Annual Forecast related records --> <apex:pageBlockTable value="{!budgets}" var="row" id="budgetTbl" > <apex:column headerValue="Year"> <apex:outputText id="Year" value="{!row.Year__c}" style="width:40px" /> </apex:column> <!-- Currency fields from Budget (formatted as number without currency) --> <apex:column headerValue="Jan" style="text-align:right" headerClass="CurrencyElement"> <apex:outputText id="Jan" value="{0, number, ###,###,##0}" > <apex:param value="{!row.Jan__c}"/> </apex:outputText> </apex:column> <apex:column headerValue="Feb" style="text-align:right" headerClass="CurrencyElement"> <apex:outputText id="Feb" value="{0, number, ###,###,##0}" > <apex:param value="{!row.Feb__c}"/> </apex:outputText> </apex:column> <apex:column headerValue="Mar" style="text-align:right" headerClass="CurrencyElement"> <apex:outputText id="Mar" value="{0, number, ###,###,##0}" > <apex:param value="{!row.Mar__c}"/> </apex:outputText> </apex:column> <apex:column headerValue="Apr" style="text-align:right" headerClass="CurrencyElement"> <apex:outputText id="Apr" value="{0, number, ###,###,##0}" > <apex:param value="{!row.Apr__c}"/> </apex:outputText> </apex:column> <apex:column headerValue="May" style="text-align:right" headerClass="CurrencyElement"> <apex:outputText id="May" value="{0, number, ###,###,##0}" > <apex:param value="{!row.May__c}"/> </apex:outputText> </apex:column> <apex:column headerValue="Jun" style="text-align:right" headerClass="CurrencyElement"> <apex:outputText id="Jun" value="{0, number, ###,###,##0}" > <apex:param value="{!row.Jun__c}"/> </apex:outputText> </apex:column> <apex:column headerValue="Jul" style="text-align:right" headerClass="CurrencyElement"> <apex:outputText id="Jul" value="{0, number, ###,###,##0}" > <apex:param value="{!row.Jul__c}"/> </apex:outputText> </apex:column> <apex:column headerValue="Aug" style="text-align:right" headerClass="CurrencyElement"> <apex:outputText id="Aug" value="{0, number, ###,###,##0}" > <apex:param value="{!row.Aug__c}"/> </apex:outputText> </apex:column> <apex:column headerValue="Sep" style="text-align:right" headerClass="CurrencyElement"> <apex:outputText id="Sep" value="{0, number, ###,###,##0}" > <apex:param value="{!row.Sep__c}"/> </apex:outputText> </apex:column> <apex:column headerValue="Oct" style="text-align:right" headerClass="CurrencyElement"> <apex:outputText id="Oct" value="{0, number, ###,###,##0}" > <apex:param value="{!row.Oct__c}"/> </apex:outputText> </apex:column> <apex:column headerValue="Nov" style="text-align:right" headerClass="CurrencyElement"> <apex:outputText id="Nov" value="{0, number, ###,###,##0}" > <apex:param value="{!row.Nov__c}"/> </apex:outputText> </apex:column> <apex:column headerValue="Dec" style="text-align:right" headerClass="CurrencyElement"> <apex:outputText id="Dec" value="{0, number, ###,###,##0}" > <apex:param value="{!row.Dec__c}"/> </apex:outputText> </apex:column> <!-- Total (sum) for the year --> <apex:column headerValue="Total" style="text-align:right" headerClass="CurrencyElement" > <apex:outputText id="yearTotal" value="{0, number, ###,###,##0}" > <apex:param value="{!row.Year_Total__c}"/> </apex:outputText> </apex:column> </apex:pageBlockTable> </apex:pageBlock> </apex:form> </apex:page>
Edit VF page (BudgetEdit.vfp):
<apex:page id="thepage" standardController="Contact" extensions="BudgetExt"> <!------------------------------------------------------------------------ This page is designed to fit as a component on the detail page of the Contact page layout. This is the Edit Mode version of the page, containing input fields and buttons for Add Row, Save, Cancel and Delete. Note that the currency ISO code doesn't appear and is assumed to be carried through from the parent record. The related View Mode page is called "BudgetView". Author: Charles Thompson, Salesforce Singapore, Nov 2017 --------------------------------------------------------------------------> <apex:form> <apex:pageBlock mode="maindetail"> <table width="100%"> <tr> <td style="text-align:center"> <!-- Buttons... Add Row / Save / Cancel --> <apex:commandButton value="Add Row" action="{!addRow}" id="addButton" /> <apex:commandButton value="Save" action="{!saveBuds}" id="saveButton" /> <apex:commandButton value="Cancel" action="{!cancelEdit}" id="cancelButton" /> </td> </tr> </table> <!-- Table displaying the related records --> <apex:pageBlockTable value="{!budgets}" var="row" id="budgets" > <!-- Counter variable to store an index value for each line --> <apex:variable var="i" value="{!0}" /> <!-- Delete button --> <apex:column > <apex:commandButton value="X" style="color: red" action="{!delRow}" reRender="budgets" immediate="true" > <!-- populate Apex index variable --> <apex:param name="index" value="{!i}" assignTo="{!index}"/> </apex:commandButton> <apex:variable var="i" value="{!i + 1}" /> </apex:column> <apex:column headerValue="Year" title="Enter the forecast by month for this year and next year"> <apex:inputField value="{!row.Year__c}" style="width:60px" /> </apex:column> <!-- Currency fields from Annual Forecast (the currency indicator doesn't appear) --> <apex:column headerValue="Jan" style="text-align:right" headerClass="CurrencyElement"> <apex:inputField value="{!row.Jan__c}" style="text-align:right; width:65px" /> </apex:column> <apex:column headerValue="Feb" style="text-align:right" headerClass="CurrencyElement"> <apex:inputField value="{!row.Feb__c}" style="text-align:right; width:65px" /> </apex:column> <apex:column headerValue="Mar" style="text-align:right" headerClass="CurrencyElement"> <apex:inputField value="{!row.Mar__c}" style="text-align:right; width:65px" /> </apex:column> <apex:column headerValue="Apr" style="text-align:right" headerClass="CurrencyElement"> <apex:inputField value="{!row.Apr__c}" style="text-align:right; width:65px" /> </apex:column> <apex:column headerValue="May" style="text-align:right" headerClass="CurrencyElement"> <apex:inputField value="{!row.May__c}" style="text-align:right; width:65px" /> </apex:column> <apex:column headerValue="Jun" style="text-align:right" headerClass="CurrencyElement"> <apex:inputField value="{!row.Jun__c}" style="text-align:right; width:65px" /> </apex:column> <apex:column headerValue="Jul" style="text-align:right" headerClass="CurrencyElement"> <apex:inputField value="{!row.Jul__c}" style="text-align:right; width:65px" /> </apex:column> <apex:column headerValue="Aug" style="text-align:right" headerClass="CurrencyElement"> <apex:inputField value="{!row.Aug__c}" style="text-align:right; width:65px" /> </apex:column> <apex:column headerValue="Sep" style="text-align:right" headerClass="CurrencyElement"> <apex:inputField value="{!row.Sep__c}" style="text-align:right; width:65px" /> </apex:column> <apex:column headerValue="Oct" style="text-align:right" headerClass="CurrencyElement"> <apex:inputField value="{!row.Oct__c}" style="text-align:right; width:65px" /> </apex:column> <apex:column headerValue="Nov" style="text-align:right" headerClass="CurrencyElement"> <apex:inputField value="{!row.Nov__c}" style="text-align:right; width:65px" /> </apex:column> <apex:column headerValue="Dec" style="text-align:right" headerClass="CurrencyElement"> <apex:inputField value="{!row.Dec__c}" style="text-align:right; width:65px" /> </apex:column> <!-- Total (sum) for the year --> <apex:column headerValue="Total" style="text-align:right" headerClass="CurrencyElement" > <apex:outputText id="yearTotal" value="{0, number, ###,###,##0}" > <apex:param value="{!row.Year_Total__c}"/> </apex:outputText> </apex:column> </apex:pageBlockTable> </apex:pageBlock> </apex:form> </apex:page>
I hope this helps someone in the future, even if Lightning Experience makes this solution somewhat useless.
In that case, I wanted four fields side-by-side on a row. Instead of using a pageBlockTable, I used a pageBlockSection to display the fields as I needed. Otherwise, the same View/Edit mode logic works fine.
Update: I encountered a further use case. The code above will only refresh the VF component within the parent page layout. What if you have rollup summary fields or formulas on the parent record? They won't be recalculated until you refresh the whole page.
In this case, I have added a couple more items to the code.
VF View page: no changes required
VF Edit page: The code for the Save button needs to change (you only need to refresh the whole page when you save the child records). The apex:commandButton doesn't change, but we need to add a new object to the page that will execute a short Javascript function. New code:
<apex:commandButton value="Save" action="{!saveBuds}"
id="saveButton" />
<!-- on save, rerender the whole page -->
<apex:outputPanel rendered="{!refreshPage}" >
<script>
window.top.location='/{!opportunity.Id}';
</script>
</apex:outputPanel>
Apex controller extension: The save method needs to return a pagereference as follows (add this to the end of the saveBuds() method):
// switch back to View mode and refresh the whole page
refreshPage = true;
sc.save(); // save the Contact page to ensure formulas are updated
PageReference fullPage = new PageReference('/' + contactID);
return fullPage;
Kudos for this to Bob Buzzard Blog from May 2011:
https://bobbuzzard.blogspot.sg/2011/05/refreshing-record-detail-from-embedded.html#comment-form
One last thing: I have tested this behaviour in Lightning and it works. Although the embedded VF pages aren't implemented as Lightning components, nor do they follow the Lightning Design System (SLDS), they still appear the same in either Classic or Lightning Experience.