10

Getting trouble in Rerendering a form/output panel through VisualForce components.

Visualforce component:

<apex:component controller="Component_cls">
    <apex:attribute name="first" type="string" description="Descp" required="true" assignTo="{!controllerValue}" />
    <apex:outputPanel id="frm1">
    {!first}
    {!controllerValue}
    <apex:commandButton value="toUpperCase" action="{!cmdAction}"  reRender="frm1"  />        
    </apex:outputPanel>
</apex:component>

Component Class:

public with sharing class Component_cls {

    public void cmdAction() {
        controllerValue = controllerValue.toUpperCase();  
        system.debug('controllerValue '+controllerValue);         
    }
  public String controllerValue{get;set;}
}

VisualForce Page:

<apex:page controller="component_test_cls" >
<apex:form >
<apex:outputPanel id="frm">
<c:testCompnent first="{!firstParam}" ></c:testCompnent>
</apex:outputPanel>
</apex:form>
</apex:page>

In apex class, i'm passing value for firstParam as 'lowercase'

When i pressed the command button, i got the log for the action which has changed the value to uppercase. But, in VF page data is not changing.

Can you help me with this?

21
+100

AssignTo, as the name suggests, only assigns a value (it's write-only). So, each time the page refreshes, the parent's controller value will reassert itself.

You need to establish a two-way communication channel between the page's controller and the component controller. Here's one way you can do that:

public class component_test_cls {
    public component_test_cls getSelf() {
        return this;
    }
    public component_test_cls() {
        firstParam = 'hello world';
    }
    public String firstParam { get; set; }
}

Which changes your component controller.

public with sharing class Component_cls {
    public component_test_cls parent { get; set; }
    public controllerValue { get { return parent.firstParam; } 
                             set { parent.firstParam = value; } }    
    public void cmdAction() {
        controllerValue = controllerValue.toUpperCase();  
        system.debug('controllerValue '+ controllerValue);
    }
}

Finally, of course, you need to change the assignment in controller and page.

Component

<apex:attribute name="first" type="component_test_cls" description="Descp" required="true" assignTo="{!parent}" />

Page

<c:testCompnent first="{!self}" />

NOTE: Do not assign "name" the same value as the variable name, or Bad Things may happen (this is actually prohibited in later API versions).


The reason why all this goes down has to do with how variables are handled in Apex Code/Visualforce. When you pass a value from one location to another, you actually pass a reference to the variable, not literally the value itself.

So, let's analyze the original situation of your code. First, in component_test_cls, you presumably set a value. In memory, something like this happens:

firstParam = 'hello world';

// Heap Address       Heap Value
// 1234               "hello world"

// Local Variable      Refers To
// firstParam          1234

Next, when assignTo passes the value to your component:

public class Component_cls {
    public String controllerValue { get; set; }
    // assignTo is invoked automatically
    // Local Variable   Refers To
    // controllerValue  1234
}

So, "hello world" is referenced in two places, in component_test_cls and Component_cls.

However, once your action method runs:

public void cmdAction() {
    controllerValue = controllerValue.toUpperCase();
    // Heap Address       Heap Value
    // 1234               "hello world"
    // 2345               "HELLO WORLD"

    // Local Variable                 Refers To
    // component_test_cls.firstParam  1234
    // Component_cls.controllerValue  2345
}

Later, assignTo is run again, and resets controllerValue back to "reference 1234." This means that your work is essentially overwritten.

With the modified version, the primary reference links the parent controller to the child controller, so we can freely modify the variables. Let's look at this:

public class component_test_cls {
    public component_test_cls() {
        firstParam = 'hello world';
        // Heap Address      Value
        // 2345              'hello world'

        // Local Variable    Refers To
        // firstParam        2345
    }
    public component_test_cls getSelf() {
        return this;
        // Heap Address      Value
        // 1234              component_test_cls(1)
        // 2345              'hello world'

        // Local Variable    Refers To
        // firstParam        2345
    }
}

So, we now have a place we can store the reference:

public void cmdAction() {
    controllerValue = controllerValue.toUpperCase();
    // Heap Address       Heap Value
    // 1234               component_test_cls(1)
    // 2345               "hello world"
    // 3456               "HELLO WORLD"

    // Local Variable                 Refers To
    // component_cls.parent           1234
    // component_test_cls.firstParam  3456

    // We use a custom getter/setter, so it effectively references
    // another variable
    // Component_cls.controllerValue  component_test_cls.firstParam
}

After this method returns, assignTo performs its duty and applies "reference 1234" to "component_cls.parent", which it already was, and "reference 2345" has no references left, and is therefore garbage collected (removed from heap/view state).

So, the moral of the story is that if you need to assign a value once, you must either establish parent-child communication, or use a different backing variable.

As an alternative, you could use a write-once assignment:

public with sharing class Component_cls {
    Boolean firstWrite = false;
    public void setFirstValue(String value) {
        if(firstWrite) {
            firstWrite = true;
            controllerValue = value;
        }
    }
    public void cmdAction() {
        controllerValue = controllerValue.toUpperCase();  
        system.debug('controllerValue '+controllerValue);         
    }
    public String controllerValue{get;set;}
}

This performs a once-only write, so you'd adjust your component attribute.

<apex:attribute name="first" type="String" description="Descp" required="true" assignTo="{!firstValue}" />

As you can see, though, if you have many values you want to set, using a wrapper or the parent controller class is more efficient, and allows you to communicate back up to the page controller, or even between different components that need to communicate to each other.

Finally, you could also use just a generic wrapper object so that you can pass references around.

1

This will work

public with sharing class Component_cls {
     public String controllerValue{get;set;}
     public string componentValue{ get; set; }

     public void cmdAction() {
        controllerValue = componentValue.toUpperCase();  
        system.debug('controllerValue '+controllerValue); 
    }
}

<apex:attribute name="first" type="string" description="Descp" required="true" 
           assignTo="{!componentValue}" />
1

SFDCFox's answer is fantastic so go read that one.

I'm only adding the solution that we settled on when we ran into the exact same behavior. I was debating on doing one of the answering your own question things to get this information out there so I'm super glad you asked the question.

We found an article from SFDC that explains the issue but I can't find it for the life of me to link it for you. But I do remember one quote from it that drives me crazy to this day:

Visuaforce does not define how many times or in what order the setters are called.

The solution we settled on was to unbind the variable manually. Something like this:

private String oldQuery; // stores the query at the time of binding
// Stores the value passed to the controller via the controllers attribute
public String queryAttr {
    get;

    set {
        if (value != oldQuery) {
            query = value;
            oldQuery = value;
        }
    }
}

// Stores the query used internally for most operations.
private String query {
    get;

    set {
         query = value;
         // Reset the standard set controller with the new query.
         setCon = new ApexPages.StandardSetController(Database.getQueryLocator(query));
    }
}

In our case we were passing a string query to the component that would then instantiate a standardSetController. We needed to be able to change the query at the page level as well as manipulate the query internally to the component to change the sort field/order and add a LIKE clause for filtering.

This approach allowed us to update the standard set controller by changing the query as well as by passing a new query and then re-redering the component.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.