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
parkerAPTparkerAPT 

Bug in custom components with custom Controllers and attribute binding

The following code illustrates several bugs in how attributes get bound to a custom component's controller.  From what I read in the discussion (http://community.salesforce.com/sforce/board/message?board.id=Visualforce&message.id=5964) on assignTo, I understand that the attributes will not be available in the controller's constructor.  However,

I've include a test bench that illustrates the following problems:
  1. Default values do not get assigned to the variables.  They are Null when the initial page renders.
  2. Required attributes do get properly bound to the controller's Property but cannot be updated by actions.
  3. If we override the custom getter to ensure that its not null when returned, it initially is rendered as null but after an update is bound but does not increment as you would expect
  4. (What works): Properties that that are only present in the controller and do not get anything assigned to them.
The following 3 source files make up the test bench:
demoComponentBinding.page
BuggedComponentBinding.component
BuggedBindingController.cls

Code:
<apex:page >

<h1>The following demonstrates several bugs in using the assignTo attributes with a custom component</h1>
<ol>
 <li> The convention when I'm assigning values is the following:  Those in the hundreds are assigned via component attributes, while those less than 100 are assigned by the controller</li>
 <li>Default values do not get assigned to the variables.  They are Null.</li>
 <li>Required attributes do get properly bound but cannot be updated by actions.</li>
 <li>If we override the custom getter to ensure that its not null when returned, it initially is rendered as null but after an update is bound but does not increment as you would expect</li>
 <li>(What works): Un assigned attributes that are only present in the controller.</li>
</ol>

<c:BuggedComponentBinding demoTitle="Only Specifying required Attributes, default will not get bound properly"
 RequiredBound="300"/>
<c:BuggedComponentBinding demoTitle="Specifying both required and one default attributes, default will get bound but cannot be updated"
 NotRequiredDefaultBound="100"
 RequiredBound="300"/>
<c:BuggedComponentBinding demoTitle="Only specifying default attribute with custom getter"
 DefaultWithCustomGetter="200"
 RequiredBound="300"/>
<c:BuggedComponentBinding demoTitle="Specifying required and both default attributes, default will get bound but cannot be updated"
 NotRequiredDefaultBound="100"
 DefaultWithCustomGetter="200"
 RequiredBound="300"/>

</apex:page>

 
Code:
<apex:component controller="BuggedBindingController">
 <apex:attribute name="NotRequiredDefaultBound" default="11" assignTo="{!DefaultAssignedAttribute}" type="Integer"
  description="The default attribute does not get bound to the appropriate property of the controller" />
 <apex:attribute name="DefaultWithCustomGetter" default="22" assignTo="{!DefaultAssignedAttributeCustomGetter}" type="Integer"
  description="The default attribute does not get bound to the appropriate property of the controller" />
 <apex:attribute name="RequiredBound" assignTo="{!RequiredAttributeAssigned}" type="Integer" required="true"
  description="This required attribute gets bound, but cannot be updated via ajax calls" />
 <apex:attribute name="DemoTitle" type="string" description="Title of this demo"/>
  
<apex:form >

 <apex:outputPanel id="theTableWrapper" layout="block" title="{!DemoTitle}" style="padding: 10px; border: 1px solid black">
  <apex:panelGrid columns="4" id="theGrid" rules="all" cellpadding="5" width="50%">
   <apex:facet name="caption">
    <h2>{!DemoTitle}</h2>
   </apex:facet>
   <apex:outputText value="Property"/>
   <apex:outputText value="IsNull"/>
   <apex:outputText value="Value from Controller" />
   <apex:outputText value="Value from Component Attribute Reference" />
   
   
   <apex:outputText value="DefaultAssignedAttribute" />
   <apex:outputText value="{!ISNULL(DefaultAssignedAttribute)}" />
   <apex:outputText value="{!DefaultAssignedAttribute}" />
   <apex:outputText value="{!NotRequiredDefaultBound}" />
   
   <apex:outputText value="DefaultAssignedAttributeCustomGetter" />
   <apex:outputText value="{!ISNULL(DefaultAssignedAttributeCustomGetter)}" />
   <apex:outputText value="{!DefaultAssignedAttributeCustomGetter}" />
   <apex:outputText value="{!DefaultWithCustomGetter}" />
   
   <apex:outputText value="RequiredAttributeAssigned" />
   <apex:outputText value="{!ISNULL(RequiredAttributeAssigned)}" />
   <apex:outputText value="{!RequiredAttributeAssigned}" />
   <apex:outputText value="{!RequiredBound}" />
   
   
   <apex:outputText value="InternalAttribute" />
   <apex:outputText value="{!ISNULL(InternalAttribute)}" />
   <apex:outputText value="{!InternalAttribute}" />
   <apex:outputText value="N/A" />
   
  </apex:panelGrid>
  <apex:commandButton action="{!updateValues}" reRender="theTableWrapper" value="Update the Values"/>
 </apex:outputPanel>
</apex:form>
 
</apex:component>

 
Code:
public class BuggedBindingController {
 
 public Integer DefaultAssignedAttribute  {get; set;}
 public Integer DefaultAssignedAttributeCustomGetter  {
  get {
   if( this.DefaultAssignedAttributeCustomGetter == null){
    this.DefaultAssignedAttributeCustomGetter = 20;
   }
   return DefaultAssignedAttributeCustomGetter;
  } set;}
 public Integer RequiredAttributeAssigned  {get; set;}
 public Integer InternalAttribute   {get; set;}
 
 public BuggedBindingController(){
  InternalAttribute = 40;
 }
 
 public PageReference updateValues(){
  if(this.DefaultAssignedAttribute != null){
   this.DefaultAssignedAttribute++;
  }
  if(this.RequiredAttributeAssigned != null){
   this.RequiredAttributeAssigned++;
  }
  this.DefaultAssignedAttributeCustomGetter++;
  this.InternalAttribute++;
  return null;
 }
 
}

 




Message Edited by parkerAPT on 01-15-2009 08:16 AM
dchasmandchasman
Parker,

This looks to be more of an issue with the fact that component binding is designed to be used
with expression passing and we have not accounted for assignTo used with literal values like this.

Essentially when you us assignTo and pass a constant value exprerssion like "300" you're saying assign the value of the attribute to this property and that assignment is being evaluated quite often which is overwriting any modifications your code makes. This is something that I do not expect to change - it is working as designed - assignTo does not mean assign to once and leave it alone...

Here is a minor rework of your component to illustrate what I mean:

Code:
<apex:component controller="BuggedBindingController">
    <apex:attribute name="NotRequiredDefaultBound" default="11" assignTo="{!DefaultAssignedAttribute}" type="Integer"
        description="The default attribute does not get bound to the appropriate property of the controller" />
    <apex:attribute name="DefaultWithCustomGetter" default="22" assignTo="{!DefaultAssignedAttributeCustomGetter}" type="Integer"
        description="The default attribute does not get bound to the appropriate property of the controller" />
    <apex:attribute name="RequiredBound" assignTo="{!RequiredAttributeAssigned}" type="Integer" required="true"
        description="This required attribute gets bound, but cannot be updated via ajax calls" />
    <apex:attribute name="DemoTitle" type="string" description="Title of this demo"/>
   
    <apex:form >
        <apex:outputPanel id="theTableWrapper" layout="block" title="{!DemoTitle}" style="padding: 10px; border: 1px solid black">
            <apex:panelGrid columns="5" id="theGrid" rules="all" cellpadding="5" width="50%">
                <apex:facet name="caption">
                <h2>{!DemoTitle}</h2>
                </apex:facet>
                <apex:outputText value="Property"/>
                <apex:outputText value="IsNull"/>
                <apex:outputText value="Value from Controller" />
                <apex:outputText value="Value from Component Attribute Reference" />               
                <apex:outputText value="Value from Controller (Indirect)" />  
                
                <apex:outputText value="DefaultAssignedAttribute" />
                <apex:outputText value="{!ISNULL(DefaultAssignedAttribute)}" />
                <apex:outputText value="{!DefaultAssignedAttribute}" />
                <apex:outputText value="{!NotRequiredDefaultBound}" />
                <apex:outputText value="N/A" />    
                
                <apex:outputText value="DefaultAssignedAttributeCustomGetter" />
                <apex:outputText value="{!ISNULL(DefaultAssignedAttributeCustomGetter)}" />
                <apex:outputText value="{!DefaultAssignedAttributeCustomGetter}" />
                <apex:outputText value="{!DefaultWithCustomGetter}" />
                <apex:outputText value="N/A" />    
                
                <apex:outputText value="RequiredAttributeAssigned" />
                <apex:outputText value="{!ISNULL(RequiredAttributeAssigned)}" />
                <apex:outputText value="{!RequiredAttributeAssigned}" />
                <apex:outputText value="{!RequiredBound}" />
                <apex:outputText value="{!requiredAttributeAssignedPublic}"/>           
                
                <apex:outputText value="InternalAttribute" />
                <apex:outputText value="{!ISNULL(InternalAttribute)}" />
                <apex:outputText value="{!InternalAttribute}" />
                <apex:outputText value="N/A" />
                <apex:outputText value="N/A" />            
            </apex:panelGrid>
      
            <apex:commandButton action="{!updateValues}" reRender="theTableWrapper" value="Update the Values"/>
        </apex:outputPanel>
    </apex:form> 
</apex:component>


public class BuggedBindingController {
public Integer defaultAssignedAttribute { get; set; }

public Integer defaultAssignedAttributeCustomGetter {
get {
if (defaultAssignedAttributeCustomGetter == null) {
defaultAssignedAttributeCustomGetter = 20;
}

return defaultAssignedAttributeCustomGetter;
}

set;
}

public Integer requiredAttributeAssigned { get; set; }
public Integer requiredAttributeAssignedPublic {
get {
if (requiredAttributeAssignedPublic == null) {
requiredAttributeAssignedPublic = requiredAttributeAssigned;
}

return requiredAttributeAssignedPublic;
}

private set;
}

public Integer internalAttribute { get; set; }

public BuggedBindingController(){
internalAttribute = 40;
}

public void updateValues() {
if (defaultAssignedAttribute != null) {
defaultAssignedAttribute++;
}

if (requiredAttributeAssignedPublic != null){
requiredAttributeAssignedPublic++;
}

defaultAssignedAttributeCustomGetter++;
internalAttribute++;
}
}

 



Message Edited by dchasman on 01-15-2009 02:14 PM
crmguy.ax226crmguy.ax226

Doug, are you sure that facets are supported in custom components?

 

This works beautifully:

 

<apex:column >

    <apex:facet name="header">Name</apex:facet>

    <apex:outputField value="{!account.Name}"/>

</apex:column>

 

However, this prints nothing in the column header:

 

<!-- Component:HeaderComponent -->

<apex:component >

    <apex:facet name="header">Name</apex:facet></apex:component>

 

<apex:column >

    <c:headerComponent/>

    <apex:outputField value="{!account.Name}"/>

</apex:column>

 

crmguy.ax226crmguy.ax226

(sorry, posting again with code entered correctly)

 

Doug, are you sure that facets are supported in custom components?

 

This works beautifully:

 

 

<apex:column > <apex:facet name="header">Name</apex:facet> <apex:outputField value="{!account.Name}"/> </apex:column>

 

 

However, this prints nothing in the column header:

 

 

<!-- Component:HeaderComponent --> <apex:component > <apex:facet name="header">Name</apex:facet> </apex:component>

 

 

<apex:column > <c:headerComponent/> <apex:outputField value="{!account.Name}"/> </apex:column>