Insert Visualforce Content and Troubleshoot Templates
Learning Objectives
After completing this unit, you’ll be able to:
- Add a custom template content based on Apex and Visualforce.
- Use premade content to create common proposal sections.
- Troubleshoot issues with broken quote templates.
Custom-Coded Content
So far you’ve seen how to add HTML content and customizable line item tables into your quote template. These tools can bring you far, but sometimes you hit a limit to what they can do. For example, you may want to show a bulleted list or table that grows depending on what products are on the quote. Simple, static HTML content can’t handle dynamic sizes of elements. For these special cases there’s a very technical, but very powerful solution that involves Apex and Visualforce. These coding tools can take quote data and process it almost any way you need it.
We won’t assume that you’re a coding expert, so for this example we give you the code and focus on the mechanics of the setup. Using our code, you create a dynamic table for Consumption Schedules, which show tiered usage rates for things like mobile phone data plans. These rates can vary depending on the product and the customer. So let’s see what it takes to use Visualforce to create a rate table positioned below the line items. You start by making an Apex class.
- Click and click Setup. The Setup page opens in a new tab.
- Enter
apex
in the Quick Find box. - Click Apex Classes.
- Click New.
- Paste the following code into the Apex code editor:
public class CustomTemplateController{ public string quoteId {get; set;} public string templateId {get; set;} public List<SBQQ__QuoteLine__c> lines {get; set;} public List<SBQQ__QuoteLineConsumptionSchedule__c> linesWithConsumptionSchedules {get; set;} public List<SBQQ__QuoteLineConsumptionRate__c> consumptionRates {get; set;} public List<Id> lineIDsWithCS {get; set;} public SBQQ__QuoteTemplate__c template {get;set;} public boolean showConsumptionSchedule {get;set;} public CustomTemplateController(){ quoteId = ApexPages.currentPage().getParameters().get('qid'); templateId = ApexPages.currentPage().getParameters().get('tid'); lineIDsWithCS = new List<Id>{}; consumptionRates = new List<SBQQ__QuoteLineConsumptionRate__c>{}; showConsumptionSchedule = false; this.populateTemplate(); this.populateLineData(); this.populateCSData(); } public void populateTemplate(){ template = [SELECT Id, SBQQ__FontFamily__c, SBQQ__FontSize__c, SBQQ__ShadingColor__c FROM SBQQ__QuoteTemplate__c WHERE Id = :templateId]; } public void populateLineData(){ lines = [SELECT Id, SBQQ__HasConsumptionSchedule__c FROM SBQQ__QuoteLine__c WHERE SBQQ__Quote__c = :quoteId ORDER BY SBQQ__Number__c ASC]; for(SBQQ__QuoteLine__c line : lines){ if(line.SBQQ__HasConsumptionSchedule__c){ lineIDsWithCS.add(line.Id); showConsumptionSchedule = TRUE; } } } public void populateCSData(){ linesWithConsumptionSchedules = [SELECT Id, SBQQ__QuoteLine__r.SBQQ__ProductName__c, Name, SBQQ__UnitOfMeasure__c, (SELECT Id, SBQQ__LowerBound__c, SBQQ__UpperBound__c, SBQQ__Price__c, SBQQ__QuoteLineConsumptionSchedule__r.SBQQ__QuoteLine__c FROM SBQQ__QuoteLineConsumptionRates__r ORDER BY SBQQ__ProcessingOrder__c) FROM SBQQ__QuoteLineConsumptionSchedule__c WHERE SBQQ__QuoteLine__r.Id IN :lineIDsWithCS]; for(SBQQ__QuoteLineConsumptionSchedule__c schedule : linesWithConsumptionSchedules){ for(SBQQ__QuoteLineConsumptionRate__c rate : schedule.SBQQ__QuoteLineConsumptionRates__r){ consumptionRates.add(rate); } } } }
- Click Save.
Next, you create a Visualforce page that references the Apex class to dynamically create a block of HTML.
- Enter
visual
in the Quick Find box. - Click Visualforce Pages.
- Click New.
- For Label, enter
ConsumptionSchedulesVFP
.
This could be named anything, as long as you remember it for later. - Delete the contents of the Markup window, then paste in the following code:
<apex:page contentType="text/xml" showHeader="false" sidebar="false" controller="CustomTemplateController" rendered="{!showConsumptionSchedule}"> <block font-size="{!template.SBQQ__FontSize__c}" font-family="{!template.SBQQ__FontFamily__c}" > <table width="75%"> <table-column column-width="60%"/> <table-column column-width="40%"/> <table-header> <table-row font-weight="bold" background-color="#{!template.SBQQ__ShadingColor__c}"> <table-cell padding="3px" border-style="solid" border-width="1px" text-align="left"> <block> Consumption Schedules </block> </table-cell> <table-cell padding="3px" border-style="solid" border-width="1px" text-align="center"> <block> Rates </block> </table-cell> </table-row> </table-header> <table-body> <apex:repeat id="schedulerepeat" value="{!linesWithConsumptionSchedules}" var="schedule"> <table-row keep-together.within-page="always"> <table-cell padding="3px" border-style="solid" border-width="1px" text-align="left"> <block> {!schedule.SBQQ__QuoteLine__r.SBQQ__ProductName__c} - {!schedule.Name} ({!schedule.SBQQ__UnitOfMeasure__c}) </block> </table-cell> <table-cell border-style="solid" border-width="0px" text-align="left"> <table border-collapse="collapse" > <table-column column-width="50%"/> <table-column column-width="50%"/> <table-body> <apex:repeat id="raterepeat" value="{!consumptionRates}" var="rate"> <apex:outputText rendered="{!rate.SBQQ__QuoteLineConsumptionSchedule__r.Id = schedule.Id}"> <table-row keep-together.within-page="always"> <table-cell padding="3px" border-style="solid" border-width="1px" text-align="center"> <apex:outputText rendered="{!NOT(ISNULL(rate.SBQQ__UpperBound__c))}"> <block> {!rate.SBQQ__LowerBound__c} - {!rate.SBQQ__UpperBound__c - 1} </block> </apex:outputText> <apex:outputText rendered="{!ISNULL(rate.SBQQ__UpperBound__c)}"> <block> {!rate.SBQQ__LowerBound__c}+ </block> </apex:outputText> </table-cell> <table-cell padding="3px" border-style="solid" border-width="1px" text-align="right"> <apex:outputText > <block> <apex:outputText value="{0,number,currency}"> <apex:param value="{!rate.SBQQ__Price__c}"/> </apex:outputText> </block> </apex:outputText> </table-cell> </table-row> </apex:outputText> </apex:repeat> </table-body> </table> </table-cell> </table-row> </apex:repeat> </table-body> </table> <block> </block> </block> </apex:page>
- Click Save.
Good, you can close the setup tab. The last step is to reference the Visualforce page in custom content, and put that into your quote template.
- In the navigation bar, click Template Content.
- Click New.
- Choose Custom.
- Click Continue.
- For Content Name, enter
AW Consumption Schedules
. - For Custom Source, enter
/apex/c__ConsumptionSchedulesVFP
. - Click Save.
Now, put it into your quote template. - In the navigation bar, click Quote Templates.
- Click AW Proposal.
- Click the Related tab.
- In the Sections related list, click New.
- For Section Name, enter
Consumption Schedules
. - For Content, find and select AW Consumption Schedules.
- For Top Margin, enter
0.25
. - For Display Order, enter
40
. - Click Save.
That wasn’t so bad, was it? The code we supplied has logic built in so that nothing appears unless the quote includes a product that has consumption schedules. Quote Q-00053 has such a product, the Smartphone Standard Plan. So when you preview the proposal, you get a nice little table that breaks down per-unit pricing.
Visualforce is a great way to include complex content, as long as you are comfortable maintaining code.
Drop In Premade Content
Almost every quote template needs some kind of content at the beginning to display basic information like quote number and contact information. Also, most quotes have a signature block at the bottom. Salesforce CPQ makes it easy to create template content for these two critical pieces. You already took care of the beginning of the template, but let’s let CPQ help make the ending portion. You start by making a special kind of template content.
- In the navigation bar, click Template Content.
- Click New.
- Choose Template Bottom.
- Click Continue.
- For Content Name, enter
AW Signatures
.
The Markup editor already has some text, tables, and merge fields, ready for you to use. But we want to tweak a few things before saving. - Highlight and delete everything above the signature table.
- Click Save.
As usual, you create a template section for the content to appear in. - In the navigation bar, click Quote Templates.
- Click AW Proposal.
- Click the Related tab.
- In the Sections related list, click New.
- For Section Name, enter
Signatures
. - For Content, find and select AW Signatures.
- For Top Margin, enter
0.5
. - For Display Order, enter
50
. - Click Save.
Now when you preview the proposal for Q-00053, you find that the bottom has a basic, but functional signature block, as well as a thank-you message.
Similarly, the Template Top content type gives you a basic starting point that you can modify as needed. It would look like this if used on Q-00053.
Troubleshooting Quote Templates
Although HTML and Visualforce content gives you a lot of flexibility for how your proposals look, you must treat them carefully. Malformed code will probably break your template, which results in a user-side error message.
Unfortunately, the message that appears doesn’t always help identify what’s wrong with the template. In that situation you must become a detective to find the piece of content that’s the root of the problem. Here’s how we recommend you do that.
- First, clone the quote template using the Clone with Related button.
- Second, clear the Header Content and Footer Content fields of the cloned template.
- Third, preview the cloned proposal to check if it renders properly.
- If it does, you know the problem is in the header or footer content.
- If it doesn’t, delete one of the remaining template sections from the cloned template and try previewing again.
If you keep deleting sections you will eventually find which one is causing the problem. Then you have to look carefully at the code to clean up anything amiss. This is a good reason to have multiple smaller pieces of template content versus one gigantic piece. It’ll be easier to find the problem. It’s also a good reason to test your work often when building your template for the first time.
Finally, one last thing to keep in mind is that there are several places throughout quote templates where API names are used. A mistyped API name can cause the template to break too. Since there’s no validation that ensures you’ve typed API names correctly, we recommend copying and pasting them whenever possible. Use the same clone-delete-preview method of identifying a mistyped API name among your template sections.
OK, a few more touches and your quote template will be ready to deploy.