7

I want to create a page like this with 2 components:

<apex:page standardController="Campaign">
         <c:import campaign="{!campaign}" />
          <c:message campaign="{!campaign}" />
    </apex:page>

and for example in the component I want to render something like this:

<apex:component controller="ImportCampaignController" allowDML="true">   
  <apex:attribute name="campaign" description="This is the value of the campaign" type="Campaign" 
        assignTo="{!currentCampaign}" required="true"/>  

  <apex:pageBlock title="Sync status" >
  <apex:form >
    <apex:outputPanel rendered="{!currentCampaign.Sync__c == false}">
      <apex:commandButton value="Import" action="{!importCampaignMember}" />
    </apex:outputPanel>
  </apex:form> 
</apex:pageBlock></apex:component>

But obviously the visualforce complains about: SObject row was retrieved via SOQL without querying the requested field: Campaign.Sync__c

One solution might be to add the required fields on the visualforce page with some apex elements not rendered but it's not a clean solution. There is the addFields method on standardController but here is useless...

3 Answers 3

7

You only get "one bite at the cherry" with the Standard Controller. But If you can afford to put the markup in the entry point page, this may be worth noting:

  • apex:include targets cannot traverse standard controllers
  • but apex:composition defines can traverse included pages

Fields can only be queried automatically via a StandardController on an apex:page. Even though apex:include is unavilable in apex:component, you can cheat by moving your component body to a page, then doing a poor-man's include (parameterized PageReference.getContent) from the component controller.

Then, communicate the Campaign Id into your component so it can be requeried. Here's an example:

  • the Campaign Name is queried from the Page
  • the Campaign Status is queried from the Component

poor man's include

CustomCampaign.page

<apex:page standardController="Campaign">
  <apex:sectionHeader title="{!Campaign.Name}" />
  <c:importCampaign campaignId="{!Id}" />
</apex:page>

ImportCampaign.component

<apex:component controller="ImportCampaignController">
  <apex:attribute name="campaignId" type="Id" description="Id" assignTo="{!IdParam}" />
  <apex:outputText escape="false" value="{!Include}" />
</apex:component>

ImportCampaignController.cls

public class ImportCampaignController {

  public Id IdParam {get; set;}

  public String getInclude() {
    System.PageReference pageReference = Page.ImportCampaignBody;
    pageReference.getParameters().put('id', this.IdParam);
    return pageReference.getContent().toString();
  }

}

ImportCampaignBody.page

<apex:page standardController="Campaign">
  <apex:sectionHeader title="Status: {!Campaign.Status}" />
</apex:page>

(special thanks to Andrew Fawcett for sanity checking and analyzing this and multiple other approaches)

0
1

So if a solution with add these fields on the VF page is not clear and otherwise it does not work - only solution here would be to add an extra Campaign object which has been read from the database:

Main Page:

<apex:page standardController="Campaign">
    <!-- No need to add any parameters here because of standard controller -->    
    <c:import/>
</apex:page>

Controller:

public Campaign currentCampaign { get; set; }

public ImportCampaignController(ApexPages.StandardController controller){
    String myid = controller.getId();
    currentCampaign = [Select id, name, Sync__c From Campaign where id = :myid];
}

Conponent:

<apex:component controller="ImportCampaignController" allowDML="true">  
<apex:pageBlock title="Sync status" >
    <apex:form >
        <apex:outputPanel rendered="{!currentCampaign.Sync__c == false}">
            <apex:commandButton value="Import" action="{!importCampaignMember}" />
        </apex:outputPanel>
    </apex:form> 
</apex:pageBlock>
</apex:component>
0

It is not ideal, but the best way that I know of would be to just add those fields as part of your original Visualforce page. Something like:

<apex:page standardController="Campaign">
    <c:import campaign="{!campaign}"/>
    <c:message campaign="{!campaign}"/>
    <div style="display:none;" id="fieldsToLoad">
        <apex:outputField value="${!Campaign.Sync__c}" rendered="false"/>
    </div>
</apex:page>

I realize you said you want to avoid doing this, but I believe you have to do this to load those fields. Another option would be to add a controller extension in which all you do is add the fields in the constructor. Something like:

<apex:page standardController="Campaign" extension="CampaignLoaderExtension">
    <c:import campaign="{!campaign}"/>
    <c:message campaign="{!campaign}"/>
</apex:page>


public class CampaignLoaderExtension{
    public Campaign campaign {get; set;};

    public myControllerExtension(ApexPages.StandardController stdController) {
        List<String> fields = new List<String>();
        fields.add('Sync__c');
        stdController.addFields(fields);
        this.campaign = (Campaign)stdController.getRecord();
    }
}

I believe that should work but I haven't tested running that code. I am just going off the documentation. Neither solution just works like you would hope though, but it does get the job done.

You must log in to answer this question.

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