1

I've created a component and a controller to be used for inserting images in Visualforce email templates. Adding <c:VFC_EmailTemplateImage developerName="logo" width="105" height="95"/>to email template code would render the image on the template.

Controller:

public without sharing class EmailTemplateImageController
{
    public String documentDeveloperName {get; set;}
    public Integer documentWidth {get; set;}
    public Integer documentHeight {get; set;}
    public String imageURL  {get; set;}

    public EmailTemplateImageController()
    {
        this.imageURL = generateImageURL();
    }

    public String generateImageURL()
    {
        Document emailTemplateImage = [SELECT Id, Name, DeveloperName, Url FROM Document WHERE DeveloperName = :documentDeveloperName].get(0);
        String imageURL = null;
        String baseURL = URL.getSalesforceBaseUrl().getHost();
        List<String> parts = baseURL.split('\\.');
        String serverInstanceName = null;
        if (parts.size() >= 2)
        {
            serverInstanceName = parts.get(1);
            String organizationId = UserInfo.getOrganizationId();
            imageURL = 'https://c.' + serverInstanceName
                    + '.content.force.com/servlet/servlet.ImageServer?id=' + emailTemplateImage.Id
                    + '&oid=' + organizationId;
        }
        return imageURL;
    }
}

Component:

<apex:component id="VFC_EmailTemplateImage" controller="EmailTemplateImageController" access="global">
    <apex:attribute type="String" name="developerName" assignTo="{!documentDeveloperName}" description="DeveloperName of Document"/>
    <apex:attribute type="Integer" name="width" assignTo="{!documentWidth}" description="Image width"/>
    <apex:attribute type="Integer" name="height" assignTo="{!documentHeight}" description="Image height"/>
    <img src="{!imageURL}" width="{!documentWidth}" height="{!documentHeight}"/>
</apex:component>

The above code does not work because the constructor is called before calling the setter fot documentDeveloperName. That's why the SOQL query does not work. I've also tried to add the setter

public void setDocumentDeveloperName(String developerName)
{
    documentDeveloperName = developerName;
    this.imageURL = generateImageURL();
} 

but it also does not work.

How to pass the imageURL value back to component after initializing documentDeveloperName?

2 Answers 2

5

This is addressed by understanding the order of execution for custom VF components

  1. Constructor called
  2. Setters called (from the component's apex:attribute assignTo
  3. Getters called

As such, you should restructure your VF Component controller to:

public without sharing class EmailTemplateImageController
{
  public String documentDeveloperName {get; set;}  // set by component assignTo
  public Integer documentWidth {get; set;} // set by component assignTo
  public Integer documentHeight {get; set;} // set by component assignTo
  public String imageURL  {
    get {
       return generateImageUrl();
    }
    private set;
  }
 public EmailTemplateImageController() {} // let getter do work


public String generateImageURL()
 {
    Document emailTemplateImage = [SELECT Id, Name, DeveloperName, Url 
         FROM Document 
         WHERE DeveloperName = :this.documentDeveloperName].get(0);
    String imageURL = null;
    String baseURL = URL.getSalesforceBaseUrl().getHost();
    List<String> parts = baseURL.split('\\.');
    String serverInstanceName = null;
    if (parts.size() >= 2)
    {
        serverInstanceName = parts.get(1);
        String organizationId = UserInfo.getOrganizationId();
        imageURL = 'https://c.' + serverInstanceName
                + '.content.force.com/servlet/servlet.ImageServer?id=' + emailTemplateImage.Id
                + '&oid=' + organizationId;
    }
    return imageURL;
  }
}
0
0

I faced a similar issue. What i did ,

create one tempVariable in vf apex class. Call whatever pre action you want to perform before constructor load . call that method from temvariable getter method and return empty string.

simply add the value in the page after page tag.

<apex:page>
{!tempVariable}<!-- Keep this var on top of all tag-->
  <c: component developerName = {!developerNameVar}
</apex:Page>

apex class would be

  public class test
 {
     public string developerNameVar{get;set;}
     public static stringtempVariable{ 
      get{
            developerNameVar = getDeveloperNameFromMethod();
         } 
       } 
     public string getDeveloperNameFromMethod()
    {
        // Do process
         return passingString
    }
 }
4
  • Thanks for the reply, bharath. There is a problem that I cannot use another controller because I'm using component inside email template, not in Visualforce page
    – justasd
    Commented Dec 19, 2016 at 11:11
  • No, I'm passing it directly from Visualforce Email Template using <c:VFC_EmailTemplateImage developerName="logo" width="105" height="95"/> The email template does not have it's controller.
    – justasd
    Commented Dec 19, 2016 at 11:29
  • If you passing developerName='logo'. then it should work.
    – bharath
    Commented Dec 19, 2016 at 11:31
  • I'm not allowed to add {!tempVariable} just before calling component, I get error. But I can add it to the component adding {!tempVariable} just before apex:attribute tags. But then how to receive passingString in component's controller without autom atically calling the setter?
    – justasd
    Commented Dec 19, 2016 at 11:44

You must log in to answer this question.

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