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());
}