0

I am trying to preview the email template inside a Visualforce page, and for this purpose, I query for the EmailTemplate record's body inside JavaScript, and then I let the standardcontroller do rest of the job by merging the referenced fields in email template - at least that's my intention.

The email template body is: Flight No: {!Flight__c.Name}

The visualforce page is (embedded into a publisher action):

<apex:page standardController="Flight__c">
    
    <apex:includeScript value="/soap/ajax/35.0/connection.js"/> 
    
    <apex:form id="myForm">
        <apex:pageBlock id="myPageblock">
            
            <apex:outputText>
                <script type="text/javascript">
                sforce.connection.sessionId='{!GETSESSIONID()}';
                var query = sforce.connection.query("SELECT Id, Name, Body FROM EmailTemplate WHERE Name = 'myTemplate'");
                var records = query.getArray("records"); 
                alert(records);
                var body = '';
                body = records[0].Body;
                document.write(body);
                </script>       
            </apex:outputText><br/>
            <apex:outputText>
                <script>
                document.write('Flight No: {!Flight__c.Name}');
                </script>
            </apex:outputText>
        </apex:pageBlock>
    </apex:form>  
    
</apex:page>

And the rendered result is:

{!Flight__c.Name}

What am I doing wrong? It seems like the {!Flight__c...} statements need to be present as hardcoded inside VF for standardcontroller to merge them. Maybe I should follow a different path there, looking forward to your insights.

2 Answers 2

1

This is correct behavior.

  • Your first line in the above shows that when you try to output the Body of an EmailTemplate, the merge fields are still literal text.
  • Your second line shows that you can use merge fields in Javascript.

It is probably more straightforward to do a manual merge in Apex. The regex approach isn't terribly complicated, though if you want to support cross-object fields or you merge in data from more than one object, you need to get more fancy.

Here is a basic outline of the steps you can take:

Getting the merge fields

// create a helper class so you can return more complex information
// each data point contains the merge field to find (e.g. {!Object__c.Field__c})
// and also the field itself to get from the record (e.g. Field__c)
class MergeField
{
    final String template, field;
    MergeField(String template, String field)
    {
        this.template = template;
        this.field = field;
    }
}

// accept the template body as input
// yield the merge fields as output
static List<MergeField> getMergeFields(String templateBody)
{
    List<MergeField> mergeFields = new List<MergeField>();
    Matcher matcher = Pattern.compile('\\{!\\w*\\.\\w*\\}').matcher(templateBody);
    while (matcher.find())
    {
        String template = matcher.group();
        String field = template.substringBetween('.', '}');
        mergeFields.add(new MergeField(template, field));
    }
    return mergeFields;
}

Helpers

// you need to avoid NullPointerException if the field has no value
// you can also make sure return type is String
static String safeGet(SObject record, String field)
{
    Object value = record.get(field);
    return (value == null) ? '' : String.valueOf(value);
}

// there are two reasons you would get SObjectException
// 1. the merge field is for a different type of SObject
// 2. the merge field has not been queried for
// the latter should be completely avoidable
static String safeMerge(SObject record, MergeField mergeField, String body)
{
    try
    {
        return body.replace(
            mergeField.template,
            safeGet(record, mergeField.field)
        );
    }
    catch (SObjectException s) { return body; }
}

Putting it all together

public with sharing class TemplatePreview
{
    final List<MergeField> mergeFields;
    public String body { get; private set; }
    public TemplatePreview(String developername)
    {
        this.body = [
            SELECT Body FROM EmailTemplate
            WHERE DeveloperName = :developerName
        ].Body;
        this.mergeFields = getMergeFields(body);
    }
    public String mergeTemplate(SObject record)
    {
        for (MergeField mergeField : mergeFields)
            body = safeMerge(record, mergeField, body);
        return body;
    }

    // you do need one more helper to use in your extension
    public List<String> getFields(SObjectType toQuery)
    {
        String comparison = String.valueOf(toQuery);
        for (MergeField mergeField : mergeFields)
        {
            if (mergeField.template.substringBetween('{!', '.') == comparison)
                fields.add(mergeField.field);
        }
        return fields;
    }

    // helpers from above
}

Using it in an extension

public String body { get; private set; }
public MyExtension(ApexPages.StandardController controller)
{
    TemplatePreview preview = new TemplatePreview('MyTemplate');
    // one of the only remaining use cases for checking if you are in a test
    // you cannot call addFields from a testMethod :(
    if (!Test.isRunningTest())
        controller.addFields(preview.getFields(Flight__c.sObjectType));
    this.body = preview.mergeTemplate(controller.getRecord());
}
7
  • doesn't new sfdc apex method developer.salesforce.com/docs/atlas.en-us.apexcode.meta/… help?
    – cropredy
    Commented Jun 13, 2016 at 16:35
  • Hmm, I haven't looked into it. I'll investigate and see what I find. @crop
    – Adrian Larson
    Commented Jun 13, 2016 at 16:48
  • I don't think any of those methods would work. The only way this can be done is using the pattern and matcher as shown by @AdrianLarson. I was just thinking, what exactly happens when we use the merge fields on page. I mean how and when does SFDC convert them to actual values. Any documentation link? Commented Jun 13, 2016 at 16:53
  • @ShaileshPatil Who's that question for?
    – Adrian Larson
    Commented Jun 13, 2016 at 16:57
  • It was for you @AdrianLarson. And may be for those who are reading the comment. :) Commented Jun 13, 2016 at 17:04
0

You can take the below approach to preview the email template. The email body wrapped in a VF component(TestEmailPreviewComponent) used in both Email template and preview VF page. I have used a VF component to avoid email body duplication. So that when you can update the email body at single place without updating the preview page. Hope this helps.

VF page to preview Email Template

<apex:page standardController="Lead" showHeader="false" standardStylesheets="false">
    <c:TestEmailPreviewComponent record="{!Lead}"></c:TestEmailPreviewComponent>
</apex:page>

Email Template

<messaging:emailTemplate subject="Test Preview Subject" recipientType="Contact" relatedToType="Lead">
    <messaging:htmlEmailBody >
        <c:TestEmailPreviewComponent record="{!relatedTo}"></c:TestEmailPreviewComponent>
    </messaging:htmlEmailBody>
</messaging:emailTemplate>

Apex component to hold the email body (TestEmailPreviewComponent)

<apex:component access="Global">
    <apex:attribute name="record" description="The type of record we are viewing." type="Lead"/>

    <h1>Email Template Data of Lead : {!record.Name}</h1> 

</apex:component>

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .