+ Start a Discussion
SteveEthosSteveEthos 

The timing of passing in Attribute data to a Controller of a Component is not working properly.

We have a VF page that uses multiple instances of a custom component, when we pass information in through the Component attributes, and try to get them to the Component Controller, they are not setting before a apex:dataTable reads from the controller.
 
Example:
 
----------------------------
Page has this code: (this segment is inside of a PageBlockSection)
 
<tr>
<td><c:MedComp cnId="{!cn__c.id}" medGrp="ACONV"/></td>
<td><c:MedComp cnId="{!cn__c.id}" medGrp="STIM"/></td>
</tr>
----------------------------
Component has this code:
 
<apex:component controller="MedCompController">
    <apex:attribute name="cnId" type="String" required="true" description="Cn ID" assignTo="{!cnID}"></apex:attribute>
    <apex:attribute name="medGrp" type="String" required="true" description="Med Group" assignTo="{!medGrp}"></apex:attribute>
    <apex:dataTable value="{!meds}" var="med">
        <apex:column >
            <apex:facet name="header">Trade Name</apex:facet>
            <span style="{!med.tradeStyle}">{!med.tradeName}</span>
        </apex:column>   
        <apex:column >
            <apex:facet name="header">Generic Name</apex:facet>
            <span style="{!med.genericStyle}">{!med.genericName}</span>
        </apex:column>   
        <apex:column >
            <apex:facet name="header">Sensitivity</apex:facet>
            <span style="{!med.sensitivityStyle}">{!med.sensitivity}</span>
        </apex:column>   
    </apex:dataTable>   
</apex:component>
 
----------------------------
Component Controller has this code:
 
public class MedCompController
{       
    List<MedSensitivity> meds;
       
    private String cnId;
    private String medGrp;
               
    public void setCnId(String s)
    {
        cnId = s;
    }  
    public String getCnId()
    {
        return cnId;
    }
       
    public void setMedGrp(String s)
    {
        medGrp = s;
    }
    public String getMedGrp()
    {
        return medGrp;
    }
               
    public List<MedSensitivity> getMeds()
    {  
        if (meds == null )
        {
            meds = new List<MedSensitivity>();
           
            for(med__c medRow : [select trade__c, generic__c, sensitivity__c, sub_group_name__c, sub_group_sensitivity__c
                from med__c
                where
                drug_class__c = :medGrp AND cn_id__c = :cnId])
            {
                MedSensitivity medItem = new MedSensitivity();
                medItem.Setup( medRow);                                
                meds.Add( medItem);
            }      
        }
       
        return meds;
    }  
}
 
---------------------------------------------------------------
The CnID and Med groups are passed into the Component through the Attributes at the top of the component.  We have set the {assignTo} attributes, so they should call the Setter methods on the Controller, and the values should be stored on the Component.
 
The problem is that it appears as though the apex:dataTable is accessing the {meds} attribute on the Controller prior to the setter values from the attributes being set.  
 
How can we pass data into the Component Controller from the calling page, and have it availble for the building of the Data table?   There appears to be no way for this to work.
 
All the examples for the SQL in the Controller show using the Current Page parameters.  However, we will have mutliple instances of the Components on a page, and need to send different values to each one.
 
This appears to be a bug in Visual Force.
 
Thanks,
Steve
dchasmandchasman
Interesting - my team will take a look at this - what you are describing does sound like a bug in VF - something I believe I have already fixed in Winter '09. I do not think this is a timing issue as much as it is a controller scope issue. Something that most people do not yet realize is that component attributes are implemented using expression pass-by-reference semantics and not pass-by-value. We actually take the {!} expression an inject it into the component - including for assignTo and the trick is making sure that when the expression is evaluated we do so with the right controller scope (think call stack/stack frame in a method call world) and if that scope is the wrong one funny things can happen.

I have opened a bug for myself to track this one - I will verify that this has been fixed in the next release and let you know.


Message Edited by dchasman on 08-01-2008 03:28 PM
SteveEthosSteveEthos

Do you have any workarounds?

I have 4 instances of the Component on the same page.  We need to send different values to each one. 

I considered adding parameters to the page, and reading them inside of the Component.  However, all of the Component instances will read the same values, so that does not work. 

The problem is the timing with the dataTable, it reads the value and tries to run the SQL before I can find a way to get some populated attribtues to it.

Are there any ways to pass values into the Component Controller before the dataTable starts its initialization?  I have to set 2 different values in the SQL when it runs.

It seemed as though the Attributes were the best way to get values into the Component and to the Controller.  Are there any other ways?

Thanks,

Steve

 

 

dchasmandchasman
Attributes are exactly the right way to go, I do not have a workaround as yet - have to had time to even try out your repro case yet (we are in the final fury of getting to feature freeze for the next release and are very very busy right now, sorry).
MyGodItsColdMyGodItsCold
Would it help to set up something like a 'semaphore' object you would write to & read from?
SteveEthosSteveEthos
This is a serious flaw in the ability to create encapulsated Components.  We should be able to just pass in a few parameters (through the attributes) and have the Component be able to query any data it requries, and then render the results.
 
This is not possible because of the timing between the setting of the "assignTo" method on the Component Controller and the reading of the values for the drawing of the dataList.  No matter what I tried, it was drawing the dataList (and reading its value) before the setting of the values from the Attibutes.  Therefore the attribute values appeared to be null, but when you put a {!attrib} on the Component, they displayed properly.  I was able to nail it down by creating an independent varaible that was set on the get Method of the attribute that was used for the data list.  In this scenario, the attrib values were null (when getting the value for the dataList) but then they were populated after.
 
I tried a few different approaches:
 
a)  Created an apex:Varaible  (I thought that it might create some type of delay, and have the attribute variables set) - did not work.
 
b)  Wrap the component inside its own actionStatus, then create an actionFunction, then have the page call the javacript to trigger a re-rendering of the section.  This did not work because I have 4 instances of the Component on the parent page, and 4 different javascript methods were created with the same name.
 
 
Finally, I discovered an approach that breaks some of the tenants of encapsulation, but it works:
 
I took out the SQL From inside of the Component (where it belonged) and called it from the parent page. 
 
****************************************************************
Parent Page Controller:

public List<MedSensitivityDisp> getMedSenBB()

{

return getMedSensitivities('BB');

}

public List<MedSensitivityDisp> getMedSenACONV()

{

return getMedSensitivities('ACONV');

}

public List<MedSensitivityDisp> getMedSenADEP()

{

return getMedSensitivities('ADEP');

}

public List<MedSensitivityDisp> getMedSenSTIM()

{

return getMedSensitivities('STIM');

}

private List<MedSensitivityDisp> getMedSensitivities(String drugClass)

{

List<MedSensitivityDisp> returnList = new List<MedSensitivityDisp>();

for(med__c medRow : [select trade__c, generic__c, sensitivity__c from med__c where drug_class__c = :drugClass AND cnid__c = :cn.id])

{

MedSensitivityDisp medItem = new MedSensitivityDisp();

medItem.Setup( medRow);

returnList.Add( medItem);

}

return returnList;

}

 
****************************************************************
Then on the Parent Page I put:
 
<tr>
                        <td><c:MedComp medList="{!medSenBB}"/></td>
                        <td><c:MedComp medList="{!medSenADEP}"/></td>
                    </tr>
                    <tr>
                        <td><c:MedComp medList="{!medSenACONV}"/></td>
                        <td><c:MedComp medList="{!medSenSTIM}"/></td>
                    </tr> 
 
****************************************************************
On the Med Component I put:

<apex:component controller="MedCompController">

<apex:attribute name="medList" type="MedSensitivityDisp[]" required="true" description="data list"></apex:attribute>

<apex:dataTable value="{!medList}" var="med">

<apex:column >

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

<span style="{!med.tradeStyle}">{!med.tradeName}</span>

</apex:column>

<apex:column >

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

<span style="{!med.genericStyle}">{!med.genericName}</span>

</apex:column>

<apex:column >

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

<span style="{!med.sensitivityStyle}">{!med.sensitivity}</span>

</apex:column>

</apex:dataTable>

</apex:component>

****************************************************************

So, this works because I am passing the data list from the Attribute directly to the dataList, and not using the Component Controller.  In the documentation it says that you can pass a variety of data types (not just primitives).  I tried the List<apexObj> and it works.
 
This will work for now until the Visual Force bug is fixed and released. 
 
This is a significant weakness of using Components.  They cannot be truely standalone compoents, rather you have to feed into them the data externally in certain situations.
 
This would not have been a problem if I could perform my SQL by reading the environment variables, page parameters, or the SObject.   However, the problem is that I had 4 different instances of the same Component on 1 page. 
 
I hope that this helps someone else who might be hitting the same roadblock.
 
Good Luck!
Steve
dchasmandchasman
As I stated in a previous post this a bug in controller scope and we have just verified that the issue has been fixed in the next major release of salesforce (Winter '09).
SteveEthosSteveEthos

Excellent. 

Other than this issue, I have been happy with how the Component architecture is setup.  I like the ability to break up the logic into reusable sections that encapsulate all of their logic.  The Attributes will allow for the passing in of the necessary data.

I am looking forward to the new release!

Thanks,

Steve

RamyaSRamyaS
Hi,

I am facing a similar issue, where my controller requires value to be passes in the page thru the Custom Component. The data on the VF page when it load is null because the page renders before the data is retrieved from the sql. You solution might work for me as well, but I do not understand how you pass the "cn" parameter below. Is it thru the 'assignTo' attribute from the component. How to you pass that parameter?


for
(med__c medRow : [select trade__c, generic__c, sensitivity__c from med__c where drug_class__c = :drugClass AND cnid__c = :cn.id])


Thanks in advance.

-Ramya
SteveEthosSteveEthos

The SObject cn is loaded in the Controller for the page that contains the Component.  It is loaded and then the data is passed into the Component from there.

The key to this is that all of the data necessary is passed in directly, instead of having the Component load it itself.

Hopefully this will no longer be needed in the next release.

Good Luck,

Steve

jeremy_rossjeremy_ross
I'm unable to pass *any* values to a component through attributes.  The corresponding properties on the controller are all null.  Would this be caused by the bug described in this thread?