function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Tolga SunarTolga Sunar 

Visualforce - Standardcontroller components are not rendering when called from JS

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 VF page is:

renderedVFresult
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.

Thanks in advance.
Best Answer chosen by Tolga Sunar
NagendraNagendra (Salesforce Developers) 
Hi Tolga Sunar,

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());
}
Please mark this post as solved so that it gets removed from the unanswered queue which results in helping others who are really in need of it.

Regards,
Nagendra.P