+ Start a Discussion
Luke@TWSLuke@TWS 

Accessing Component Attributes with AssignTo

I have a VF Page with a custom controller. It contains a string with get and set methods. This value is passed as a String to a component. The attribute has an assignTo referencing a public string with {get;set;) in the components controller. The value is not available (is null) when acessed in the components constructor but is available shortly after within a command button action. I need the value within the constructor. Any ideas?
Best Answer chosen by Admin (Salesforce Developers) 
Ralph CallawayRalph Callaway

@Doug, when can we get the 'action' attribute???!!! That would be a huge help for this issue. Everyone please vote up this idea if this an issue for you: https://sites.secure.force.com/ideaexchange/ideaView?c=09a30000000D9xt&id=08730000000H6PMAA0&mc=0


I've been able to work around this by hooking initialization into a setter method. Based on the documentation (http://www.salesforce.com/us/developer/docs/pages/Content/pages_controller_lifecycle_example.htm) for lifecycle I believe we can count on the setters for any assignTos to be evaluated prior to any getters being called.


In my example I needed to parse a sub query of an object to populate other objects. When this parsing took place in the constructor it failed as the assignTo hadn't been evaluated yet.


public with sharing class ThisWontWork {

  public Opportunity opportunityWithSubRecords { get; set; }

  public Forecast_History__c subObject1 { get; private set; }
  public Forecast_History__c subObject2 { get; private set; }

  public ThisWontWork() {
    parseSubObjects(); // <- This fails because opportunityWithSubRecords is null when the constructor is called
  }

  private void paresSubObjects() {
    for(Forecast_History__c history : cOpportunity.Forecast_Histories__r) {
      if('Type 1' == history.revenue_type__c)
        subObject1 = history;
      else if('Type 2' == history.revenue_type__c)
        subObject2 = history;
     }
  }
}

Ideally I would just call parseSubObjects from an action attribute on the component, but as that feature is not here yet I opted to use a setter side effect instead.

public with sharing class ThisWillWork {

  public Opportunity opportunityWithSubRecords {
    get {
      return opportunityWithSubRecords;
     }
     set {
       opportunityWithSubRecords = value;
       parsePriorForecastSnapshots();
     }
  }

  public Forecast_History__c subObject1 { get; private set; }
  public Forecast_History__c subObject2 { get; private set; }

  private void paresSubObjects() {
    for(Forecast_History__c history : cOpportunity.Forecast_Histories__r) {
      if('Type 1' == history.revenue_type__c)
        subObject1 = history;
      else if('Type 2' == history.revenue_type__c)
        subObject2 = history;
    }
  }

}

This has been working so far for me.

All Answers

Sam.arjSam.arj
It seems that the values of attributes are assigned to their corresponding assignTo after the Constructor is called. I played with this issue yesterday for hours!

This is a bug and should be fixed.
Also I do not seem to be getting any success with the "default"  property of the component's attribute either.
Regardless of the Component state (Constructor, Postback event, Property get or set) the default value is not passed down to the Component Controller!

dchasmandchasman
This is working as designed - we actually need to create an instance (requires running your constructor) of your controller before we can invoke any methods, including setters, on it right? What you are describing is closer to dependency injection concept typically called constructor injection (exactly what we do with extensions BTW) and for a number of reasons this is unlikely to be added to VF.

Another approach would be something like we already have in <apex:page> - the action attribute can be used to handle this very issue (among others). We have not exposed the same concept for <apex:component> yet and to date you are the first person to ask for it externally.

This is exactly the kind of thing that IdeaExchange is great for - why not post the idea and get some community support for it to help move it up in priority?


Message Edited by dchasman on 10-09-2008 02:31 PM
Sam.arjSam.arj

I believe what Salesforce is doing behind the scene with what developers require to develop business applications are two different things.
 
For a visualforce developer constructor of a controller is the only place to initialize values. Unless Salesforce would provide a full life-cycle of events like ASP.NET and free up programmers to develop full-scale apps.

Such events are:
constructor
onInit
onLoad
onPreRender
onRender

At least a "Load" event could be really nice.


dchasmandchasman
The component controller lifecycle events you mention are already part of a feature under development called Component Binding and are basically defined in a system interface (ApexPages.Component) but we're a couple of releases away from rolling this functionality out. In addition to the event you listed that are useful for read only/passive component development there will also be a small set of events geared toward the get provisional values from the request/validate values/apply values/etc.

As I mentioned in my previous post <apex:page action> is already in place at the page level for this type of server side onload event you can wire up to your apex code method of choice and what we are currently missing is <apex:component action>. This approach is more likely to show up earlier than component binding because it is a direct analog to an existing concept.


Message Edited by dchasman on 10-09-2008 08:16 PM
Sam.arjSam.arj

What about Component's interaction with its parent page? Any plans to address that?

This part is also very crucial and I wonder why it is absent. If I have a Component that has controller of its own then the Component can not return any values (through attributes) back to the page!

See this post:

http://community.salesforce.com/sforce/board/message?board.id=Visualforce&thread.id=5791

http://community.salesforce.com/sforce/board/message?board.id=Visualforce&thread.id=5791

dchasmandchasman
Not true - attributes are implemented using expression passing which is similar to pass-by-reference semantics with the addition of support for action methods as "objects" too (this allows you to pass a ref to any action method into a component and then be able to invoke it or bind it into any contained component's attributes that expect an action method).

Basically you can inject an object (apex object or sobject) from the page into a component and the component can most definitely change state, invoke methods, etc. This is extremely powerful and something we do all the time in our own components and pages.

Code /apex/expressionPassingDemo:
<apex:page controller="ExpressionPassingDemo">
    <apex:form id="theForm">
        <h2>ExpressionPassingDemo.demoAction() has been invoked {!count} times [{!now()}]</h2><br/><br/>
        <c:expressionpassingdemo someAction="{!demoAction}" someValue="{!demoValue}" someDescription="{!description}" rerenderOnUpdate="theForm"/>        
    </apex:form>
</apex:page>


Code /apexcomponent/expressionPassingDemo:
<apex:component>
    <apex:attribute name="someDescription" type="String" description="TODO: Describe me"/>
    <apex:attribute name="someValue" type="String" description="TODO: Describe me"/>
    <apex:attribute name="someAction" type="ApexPages.Action" description="TODO: Describe me"/>
    <apex:attribute name="rerenderOnUpdate" type="string" description="TODO: Describe me"/>
    
    <apex:pageBlock title="{!someDescription}">
        <apex:inputText value="{!someValue}"/><br/>
        <apex:commandLink action="{!someAction}" value="Update" rerender="{!rerenderOnUpdate}"/>
    </apex:pageBlock>
    
    <!-- Ignore this - temp workaround for a known issue with auto inclusion of ajax library -->
    <apex:commandLink rerender=""/ rendered="false">        
</apex:component>

 
Code:
public class ExpressionPassingDemo {
    public ExpressionPassingDemo() {
        demoValue = 'Initial Demo Value';
        count = 0;
    }

    public String getDescription() {
        return 'The current value: ' + demoValue;
    }
    
    public String demoValue { get; set; }

    public void demoAction() {
        System.debug('Invoked ExpressionPassingDemo.demoAction()!');
        count++;
    }
    
    public integer count { get; private set; }
}

 



Message Edited by dchasman on 10-10-2008 10:44 AM
Luke@TWSLuke@TWS
I've made some modofications to my code so that the attribute is used when the get method is called for the control that needs the data. This has meant adding 25 more lines of code but it works now.

I'm having another problem though. How do I get the value back to the main page?

I'm using the attribute value perfectly fine. It's been assigned to a variable in the component controller.. Every time I make a change to the data within the control I am updating the component controller variable but it doesn't seem to be making it back to the main page code.


Message Edited by Luke@TWS on 10-13-2008 07:07 AM
dchasmandchasman
You need to inject/pass an object in from the page into the controller - not a scalar type like String or Number. Scalars are passed by value in much the same way they are in languages like Java and Apex Code.

Ok, so here is an example of injecting the entire controller into a component via an attribute and then injecting that into the component's controller via assignTo.

NOTE: I am specifically not worrying about providing a clean separation of concerns in order to keep this example small. Normally when I do this I define and interface in apex code that I sue as the type of the attribute and then create a class that implements the interface in order to keep things loosely coupled. once you have this in place you can easily add additional capabilities to your injected object/interface.


Code /apex/injectionDemo:
<apex:page controller="InjectionDemo" showHeader="false">
    <apex:pageBlock>
        <apex:form>
            <c:injectiondemo value="{!theController}" rerender="theList"/>
        </apex:form>
    
        <apex:dataList value="{!someProperty}" var="v" id="theList">
            {!v}
        </apex:dataList>
    </apex:pageBlock>
</apex:page>

public class InjectionDemo {
public InjectionDemo() {
someProperty = new String[] { 'Initial value' };
}

public InjectionDemo getTheController() {
return this;
}

public List<String> someProperty { get; set; }
}

 
Code /apexcomponent/injectiondemo:
<apex:component controller="InjectionComponentController">
    <apex:attribute name="value" type="InjectionDemo" description="TODO: Describe me" assignTo="{!thing}" required="true"/>
    <apex:attribute name="rerender" type="String" description="TODO: Describe me" required="true"/>

    <apex:commandButton value="Update Thing" action="{!updateThing}" rerender="{!rerender}"/>
    
    <!-- Ignore this - temp workaround for a known issue with auto inclusion of ajax library -->
    <apex:commandLink rerender=""/ rendered="false">          
</apex:component>


public class InjectionComponentController {
    public InjectionDemo thing { get; set; }

    public void updateThing() {
        thing.someProperty.add('Updated ' + System.now());
    }
}




Message Edited by dchasman on 10-16-2008 02:46 PM
Luke@TWSLuke@TWS
Thanks Doug that works great!
SteveBowerSteveBower
Passing the controller itself into a component's controller works, but it's somewhat non-obvious.  This thread would be worth passing to someone to write up for wider dissemination.   Thanks Steve.

Gino BassoGino Basso

Commenting on the opening line below:


dchasman wrote:
You need to inject/pass an object in from the page into the controller - not a scalar type like String or Number. Scalars are passed by value in much the same way they are in languages like Java and Apex Code.
...
Message Edited by dchasman on 10-16-2008 02:46 PM

The demoValue property in ExpressionPassingDemo appears to assume the value one enters.

 

Perhaps something changed in subsequent releases?

 

adreameradreamer

Hi Doug,

 

I have a question on a slight variant of what has been discussed here.

 

I am looking at the bit that deals with not having yet the <apex:component action>.

 

My component needs to display a list of checkboxes that is dynamic in the sense that its number and the label for each checkbox comes from the database. So in principle I would make the queries and intialize the list of checkboxes (I am using <apex:selectCheckboxes>) within the component controller. But we know this dos not work yet.

 

What would you suggest we use in this case ?. I mean, which combination of apex standard components and component controller methods combination ?.

 

Thanks very much in advance.

 

Regards,

Fernando

JPClarkJPClark

Wow, kidding me?

This is not an obvious work-around.

 

Is there anything being planned to fix this?

 

JPC

Ralph CallawayRalph Callaway

@Doug, when can we get the 'action' attribute???!!! That would be a huge help for this issue. Everyone please vote up this idea if this an issue for you: https://sites.secure.force.com/ideaexchange/ideaView?c=09a30000000D9xt&id=08730000000H6PMAA0&mc=0


I've been able to work around this by hooking initialization into a setter method. Based on the documentation (http://www.salesforce.com/us/developer/docs/pages/Content/pages_controller_lifecycle_example.htm) for lifecycle I believe we can count on the setters for any assignTos to be evaluated prior to any getters being called.


In my example I needed to parse a sub query of an object to populate other objects. When this parsing took place in the constructor it failed as the assignTo hadn't been evaluated yet.


public with sharing class ThisWontWork {

  public Opportunity opportunityWithSubRecords { get; set; }

  public Forecast_History__c subObject1 { get; private set; }
  public Forecast_History__c subObject2 { get; private set; }

  public ThisWontWork() {
    parseSubObjects(); // <- This fails because opportunityWithSubRecords is null when the constructor is called
  }

  private void paresSubObjects() {
    for(Forecast_History__c history : cOpportunity.Forecast_Histories__r) {
      if('Type 1' == history.revenue_type__c)
        subObject1 = history;
      else if('Type 2' == history.revenue_type__c)
        subObject2 = history;
     }
  }
}

Ideally I would just call parseSubObjects from an action attribute on the component, but as that feature is not here yet I opted to use a setter side effect instead.

public with sharing class ThisWillWork {

  public Opportunity opportunityWithSubRecords {
    get {
      return opportunityWithSubRecords;
     }
     set {
       opportunityWithSubRecords = value;
       parsePriorForecastSnapshots();
     }
  }

  public Forecast_History__c subObject1 { get; private set; }
  public Forecast_History__c subObject2 { get; private set; }

  private void paresSubObjects() {
    for(Forecast_History__c history : cOpportunity.Forecast_Histories__r) {
      if('Type 1' == history.revenue_type__c)
        subObject1 = history;
      else if('Type 2' == history.revenue_type__c)
        subObject2 = history;
    }
  }

}

This has been working so far for me.

This was selected as the best answer
colemabcolemab

Are parsePriorForecastSnapshots and parseSubObjects the same method?

 

EDIT: Nevermind.  I see that this is just a typo and these should be the same method.