I’ve written this post thanks to the help of my colleague Ivano Guerini who has found the solution to the problem I’m going to expose and written down the code you’ll see in this post.
This post is about correctly handling the viewstate when dealing with Dynamic Visualforce Components.
For those of you that have never worked with dynamic components, they are a way to create visualforce components using Apex.
For example the following piece of code returns a Page Block Section with 2 input fields on the Account record:
public Account record{get;set;} public ApexPages.Component getDynamicComponent() { Component.Apex.PageBlockSection result = new Component.Apex.PageBlockSection(); result.columns = 1; List sectionItems = new List(); Component.Apex.InputField input = new Component.Apex.InputField(); input.expressions.value = '{!record.Name}'; result.childComponents.add(input); input = new Component.Apex.InputField(); input.expressions.value = '{!record.Website}'; result.childComponents.add(input); }
The visualforce should be called as follows:
<apex:pageBlock> <apex:dynamicComponent componentValue="{!dynamicComponent}" /> </apex:pageBlock>
The page prints out a page block with a page block section with 2 input fields for the Name and Website Account’s fields.
The following code has been packed up in this Github repository for further testing and improvements.
If you wanna know more about viewstate this article is a good starting point.
Generally speaking, the viewstate is an input hidden field inside your Visualforce forms that contains all page data and that is used to post your form back to the server and forth to the page.
Too many informations, are you still there?
The problem comes when you try to use dynamic components (that can vary between post backs) and the Visualforce block your post back because of (for instance) required fields: in this specific case the dynamic components does not maintain the viewstate because the postback is blocked just before arriving on the server, so the components are created again and their status is lost.
You can test this behavior by calling the /apex/DynamicViewStateExample?id=[ACCOUNT_ID]&skip=1 (the skip parameters calls the page without the fix shown at the end of this post):
To fix this problem we need to replicate the viewstate component to re-apply the values to the different input fields after the postback has been done.
In your page we have added an apex:inputHidden component called viewstate:
<apex:dynamicComponent componentValue="{!DynamicComponent}" invokeAfterAction="true" /> <apex:inputHidden value="{!viewState}" id="viewState" />
The trick is to use Javascript to serialize/deserialize the view states.
We use the Serialize jQuery plugin to serialize form data but we had to write our own deserialize method (see the jQuery_Deserialize static resource for more details).
This part of the code is called when there is a post back:
var closestForm = jQuery('[id$="{!$Component.viewState}"]').closest("form"); closestForm.on("submit", function(event) { //serialize esch component except the "viewState" component to avoid recursion jQuery('[id$="{!$Component.viewState}"]').val(jQuery(this).find(':not([id$=viewState])').serialize()); });
And this part is the code that actually deserializes the “custom” viewstate and put it in the form fields:
if(closestForm.find('[id$=viewState]').val()) { closestForm.deserialize(jQuery('[id$="{!$Component.viewState}"]').val()); }
This few lines of Javascript actually to the magic!
For more details on the implementation, have a look at the DynamicViewStateExmaple page and the DynamicViewStateCmp component.
Leave a Reply