Unable to rerender in visualforce Component
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.