[SalesForce] Passing values from Visualforce component to controller and back

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?

Best Answer

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