You need to sign in to do that
Don't have an account?
Order of execution during AJAX postback and child-component rerendering
I'm struggling with the order-of-execution during a VisualForce AJAX postback request and the appropriate way to nest-components that might need to rerender each other.
The documentation (http://www.salesforce.com/us/developer/docs/pages/Content/pages_controller_postback_request.htm ) has 4 key steps:
- View Deserialization
- Evaluate expressions and all method calls including those on custom component controllers
- Evaluate the action that triggered the postback
- Render view -> HTML sent to browser
I'm getting frustrated by the second step, specifically all method calls. From what it appears, any public methods (specifically a getter) that are used in a view are called before the action method gets call even if these methods are not binding anything in the View State.
The example below illustrates that getDisplayedList method gets called immediately after view state deserialization and then again during the rendering of the view. In my toy example, I lazy-load a list of strings based on the iCount member variable. In my real-world application I'm actually using a SOQL query to dynamically fetch a list of S-Objects to display to the user.
<apex:page controller="LazyLoadController"> <apex:form id="theWrapper"> <apex:repeat value="{!DisplayedList}" var="s"> {!s} </apex:repeat> <apex:commandLink value="refresh" rerender="theWrapper" /> <div> iCount : <b>{!iCount}</b> </div> </apex:form> </apex:page> public with sharing class LazyLoadController { public LazyLoadController(){ this.iCount = 5; } public Integer iCount {get; set;} private Transient List<String> t_theList; // I want to be able to gurantee getDisplayedList() only gets executed during the rendering of the Component and not during "Postback" validation public List<String> getDisplayedList(){ if(this.t_theList == null){ // The real use case would Query the Database for the appropriate objects. this.t_theList = new List<String>(); for(Integer i=0 ; i< this.iCount; i++){ this.t_theList.add('[' + String.valueOf(i) + ']'); } } iCount++; return this.t_theList; } public PageReference refresh(){ // In theory I want to be able to execute code here that is guranteed to be executed before getDisplayedList return null; } }
Unfortuntaely, because getDisplayedList gets called before I execute any code in my post-back method, I do not have the opportunity to do any additional logic before the Transient list of strings ( t_theList) gets initalized.
One quick and obvious solution would be to set the Transient list to null in the postback method and force the list to be regenerated or remove the lazy-loading indirection and initalize the list directly.
I typically would do this except for the fact I want this controller to be a component-controller that can be easily dynamically rerendered. I've outlined the at the high-level what I want to do below.
<apex:page controller="ParentController"> <!-- Code that will create a new Task and re-render the component that should display a list containing the newly created task --> <apex:commandLink value="addNewTaskAndRefreshList" rerender="componentWrapper" /> <apex:outputPanel id="componentWrapper"> <c:lazyLoadComponent countOfTasks=5/> </c:lazyLoadComponent> </apex:outputPanel> </apex:page>
public with sharing class ParentController { public PageReference addNewTaskAndRefreshList(){ Task t = new Task(); //... insert t; return null; } }
Pretend for example, that my LazyLoadController is now displaying a list of tasks that are dynamically loaded from the database. I'd like to be able to put this component onto my page and re-render it so that it dynamically refreshes the list of Tasks. For example, in the ParentController I want to insert a new task, refresh the child-component and display the new task. Currently, the Lazy-Loading happens in Step 2 (method calls) of the order of execution before Step 3 (Invoke PostBack method).
Does anyone have a suggestion for how I can re-design my code so that I can achieve the above behavior?
Thanks
Parker
Hey,
I think I've understood. One way to do it - that isn't elegant but works - is to use a boolean flag that's "false" until it's set to true by your refresh() method. If you then wrap your repeat in an outputPanel for example and set "rendered={!flag}" then the Apex:Repeat statement will not load at all and your getter won't be called.
There's another more complex but more elegant solution where the Component Controller and Page Controller can communicate directly with eachother but it's probably overkill for this problem.
Wes
Thanks Wes.
You're right, one option that works fairy well is adding a Transient Boolean to the ChildController. In the getItems method we can reload the items if this boolean is set to true. If I add a default attribute to the component file and set this boolean on the controller we can force the controller to reload the items during rendering. Because I believe we are guaranteed that the attributes / assignTo for a component get setup before the rendering of the page occur, we can use this flag to effectively notify the controller that the child-component is starting to render and we should re-query or regenerate the list.(I can easily modify the example below to have these changes if I'm not being clear)
I'm providing a more complete example that illustrates my challenges so that others can basically copy and paste into the appropriate controllers and components and see my problem.
This example highlights the following oddities:
I'd love to see a "Salesforce Design Pattern" Cookbook be released that discusses how to appropriate handle parent-child components, especially involving rerendering these components and message-passing between Child & Parent controllers.
Can anyone shed more light on the above example and provide feedback on how I'm handling child-parent component re-rendering?
Does anyone know why some getters (getDisplayedList) are called during the PostBack execution but others (getDisplayedItemCount ) are only called during rendering?
Sorry I have two accounts logged in on different computers, but it's still me :)
I hate to say this but there are bugs when combining components and reRendering. I've seen them a few times myself, and especially when some type of iterating component is used. I've had strange situations where methods are called twice when a component is loaded even if it make one appearance in a page. I've logged cases for these situations but they've never been resolved. All I can do is offer some information that might be able to help you pinpoint your problem.
1. You shouldn't be using more than 1 form in a page, it screws with the viewstate. The solution is to use actionRegions as shown here: http://www.tgerm.com/2010/09/visualforce-actionregion-deep-dive.html
2. You can use inheritance to get a page and component's controllers to be directly aware of one another:
http://th3silverlining.com/2009/10/01/oop-in-the-cloud-recaptcha-revisited/
Cheers,
Wes