+ Start a Discussion
garybgaryb 

Rerender not working as expected when components are used.

Apologies in advance for the lengthy post, but if you can help, I'd be massively grateful!

 

I'm trying to build a number of forms (each on its own page) in Visualforce that are made up of sections that I want to have implemented as components, as some sections are reused across multiple forms.

For the sake of discussion, this page is split into two parts, "Top" and "Bottom". In Top are radio buttons that control what is displayed in Bottom. In Bottom is another set of radio butttons that control what is displayed beneath the radio buttons. Here's the Visualforce for a page, let's call it TestComponentUse:

 

 

<apex:page controller="APageController" showHeader="false">
    <apex:outputPanel id="TopPanel">
        <apex:form >
            <apex:selectRadio value="{!topRadio}" layout="pageDirection" >
                <apex:selectOptions value="{!topRadioOptions}" />
                <apex:actionSupport event="onclick" rerender="BottomPanel" />
            </apex:selectRadio>
        </apex:form>
    </apex:outputPanel> <!-- End TopPanel-->
    
    <apex:outputPanel id="BottomPanel">
        <apex:outputPanel rendered="{!topRadio == 'option1'}">
            <apex:form id="paymentForm">
                <c:mycustomcomponent></c:mycustomcomponent>
            </apex:form>
        </apex:outputPanel>
        <apex:outputPanel rendered="{!topRadio == 'option2'}">
            Other Content
        </apex:outputPanel>
    </apex:outputPanel>
</apex:page>

 

And the controller for the page:

 

 

public class APageController {
    public String topRadio {get; set;}
    public List<SelectOption> topRadioOptions {get; set;}

    public APageController() {
        topRadioOptions = new List<SelectOption>();
        topRadioOptions.add(new SelectOption('option1', 'Option 1'));
        topRadioOptions.add(new SelectOption('option2','Option 2'));
        topRadio = 'option1';
    }
}

 

 

The component mark up (called myCustomComponent, and used in the page above):

 

 

<apex:component controller="MyCustomComponentController">                    
    <apex:actionRegion >
        <apex:selectRadio value="{!bottomRadio}"  layout="pageDirection" >
              <apex:selectOptions value="{!bottomRadioOptions}" />
              <apex:actionSupport event="onclick" rerender="belowBottomRadio"/>
        </apex:selectRadio>
    </apex:actionRegion>
    
    <apex:outputPanel id="belowBottomRadio">

        <apex:outputPanel rendered="{!panelARendered}" id="bottomPanelA">
        Content A   
        </apex:outputPanel> <!-- End panelA -->
                         
        <apex:outputPanel rendered="{!panelBRendered}" id="bottomPanelB">
        Content B
        </apex:outputPanel> <!-- End panelB -->
    </apex:outputPanel> <!-- End belowBottomRadio -->
</apex:component>

 

 

And the controller for the component:

 

 

public class MyCustomComponentController {
    public String bottomRadio {get; set;}
    public List<SelectOption> bottomRadioOptions{get;set;}
    public Boolean panelARendered {get{return bottomRadio == 'optionA';} set;}
    public Boolean panelBRendered {get{return bottomRadio == 'optionB';} set;}
    
    public MyCustomComponentController() {
        bottomRadioOptions = new List<SelectOption>();
        bottomRadioOptions.add(new SelectOption('optionA', 'Option A'));
        bottomRadioOptions .add(new SelectOption('optionB', 'Option B'));
        bottomRadio = 'optionA';
    }
}

 

 

So, loading the page in a browser, selecting from the radio button in the top of the page, the bottom part changes as needed. Option 1 shows the two radio button labelled A and B and "content A"; selecting Option 2 shows "Other Content" in place of the bottom set of radio buttons. That part works OK!

 

However, when option 1 is selected and the second set of radio buttons are shown (Options A and B), selecting the radio buttons doesn't change the content beneath that bottom set of radio buttons. For some reason the rerender doesn't seem to be working. If you select Option B, then Option 2 then back to Option 1, then the correct content is displayed, however it does not dynamically rerender as I believe it should.

 

I'm sure I'm misunderstanding /something/ that I'm using, so what am I doing wrong?

 

Many, many thanks in advance...

 

sforce2009sforce2009

is the code is exactly what you are trying. It seems action attribute is missing in your actionSupport

garybgaryb

The "action" attribute isn't a required attribute for action support...

SteveBowerSteveBower

Hi, it's late and I'm tired, but for what it's worth, I don't see anything obviously wrong.  Your approach seems sound.

 

I might experiment with "immediate=true" on the actionSupport, but I'm not sure that would solve anything.  I also might try blowing away the actionRegion tags and just sending everything to see if that solves it.

 

If not, I'd probably try to take the Component page and see if it works if it were a "main" VF page of it's own, just to be sure that the code is right, but I see nothing obvious.

 

Sorry, Best, Steve.

 

p.s. This is probably not the issue, but I have sometimes found that code like:

Boolean x = (y == z); doesn't put the correct values into x as one might expect, and sometimes I've needed to do:

if (y == z) { x = true; } else { x = false;)

Now I *know* this sounds asinine, and I may have been hallucinating, but I've done that once or twice in the past to get things working.  I might try expanding out the getter.  Yes, I know....

sforce2009sforce2009

Use the same controller class for both page and the component.

It worked for me like this.

Page

<apex:page controller="Test" showHeader="false">
    <apex:outputPanel id="TopPanel">
        <apex:form >
            <apex:selectRadio value="{!topRadio}" layout="pageDirection" >
                <apex:selectOptions value="{!topRadioOptions}" />
                <apex:actionSupport event="onclick" rerender="BottomPanel" />
            </apex:selectRadio>
        </apex:form>
    </apex:outputPanel> <!-- End TopPanel-->
    
    <apex:outputPanel id="BottomPanel">
        <apex:outputPanel rendered="{!topRadio == 'option1'}">
            <apex:form id="paymentForm">
                <c:mycustomcomponent></c:mycustomcomponent>
            </apex:form>
        </apex:outputPanel>
        <apex:outputPanel rendered="{!topRadio == 'option2'}">
            Other Content
        </apex:outputPanel>
    </apex:outputPanel>
</apex:page>

 

Component

<apex:component id="cd" controller="Test">                    
    <apex:actionRegion >
        <apex:selectRadio value="{!bottomRadio}"  layout="pageDirection" >
              <apex:selectOptions value="{!bottomRadioOptions}" />
              <apex:actionSupport event="onclick" rerender="belowBottomRadio"/>
        </apex:selectRadio>
    </apex:actionRegion>
    
    <apex:outputPanel id="belowBottomRadio">

        <apex:outputPanel rendered="{!bottomRadio == 'optionA'}" id="bottomPanelA">
        Content A   
        </apex:outputPanel> <!-- End panelA -->
                         
        <apex:outputPanel rendered="{!bottomRadio == 'optionB'}" id="bottomPanelB">
        Content B
        </apex:outputPanel> <!-- End panelB -->
    </apex:outputPanel> <!-- End belowBottomRadio -->
</apex:component>

 

Class

public class Test
{
public String topRadio {get; set;}
    public List<SelectOption> topRadioOptions {get; set;}
public String bottomRadio {get; set;}
    public List<SelectOption> bottomRadioOptions{get;set;}
    public Boolean panelARendered {get; set;}
    public Boolean panelBRendered {get; set;}
    public Test() {
        topRadioOptions = new List<SelectOption>();
        topRadioOptions.add(new SelectOption('option1', 'Option 1'));
        topRadioOptions.add(new SelectOption('option2','Option 2'));
        topRadio = 'option1';
        bottomRadioOptions = new List<SelectOption>();
        bottomRadioOptions.add(new SelectOption('optionA', 'Option A'));
        bottomRadioOptions.add(new SelectOption('optionB', 'Option B'));
        bottomRadio = 'optionA';
    }

}

 

Let me know the result.

 

Thanks

WesNolte__cWesNolte__c

Hey

 

This bug comes up quite often, something to do with rerenders not recognising the component controller if it is within a rerendered block itself. You can do one of two things,

 

1. Using CSS styles e.g. display:none, and set these with javascript instead of using rerenders or

2. Keep your controller seperate, but establish a relationship between them. This article has an excellent example, and sounds like it might help you in other areas of re-use too.

 

Good luck,

Wes

garybgaryb

Thanks for all your speedy responses!

 

Steve - I've fiddled with immediate and actionRegion a lot with this setup but I couldn't get it to work. At least it means I wasn't completely crazy to try it :) I'm pretty sure I've experienced similar things with booleans, though from the other direction I think - I had something like "public boolean x {get {return y == 'somevalue';}}" in the controller and that didn't work, where as putting rendered="{!y == 'somevalue'}" in the page caused it to act differently.

 

Wez - Thank you, I did suspect something like this, though assumed I was missing something fundamental (this is the first time I've really played with VF). I'll try it out and accept your response as the solution should things go well. P.S. nice blog, you have another follower :)

garybgaryb

Wez, I'm trying your second option, though thinking about it, is it enough to just establish a link each way? i.e. to give the page a reference to the component controller and the component a reference to the page controller? I'm not sure if that's enough to solve the problem - from what I can tell, it hasn't solved the problem... Note that the re-render on the radio buttons in the bottom half of the page is all contained in the custom component.

WesNolte__cWesNolte__c

Sorry about that buddy, I made some assumptions (which as a developer is always a bad idea). I dug around a bit and seemed to have found a solution, although it requires that you violate the SF guideline that,

 

You should only include one form per page, and split that form using actionregions.

 

Oh well. Perhaps you could log a case now that we know the details of the problem, anyway.. if you remove the actionregion from your component it all seems to be hunky-dorey.

 

 

<apex:component controller="MyCustomComponentController">
    <apex:attribute name="pageController" 
      type="PageControllerBase" 
      assignTo="{!pageController}" 
      required="true" 
      description="The controller for the page." />                    
 
        <apex:selectRadio value="{!bottomRadio}"  layout="pageDirection" >
              <apex:selectOptions value="{!bottomRadioOptions}">

              </apex:selectOptions>
                                <apex:actionSupport event="onclick" rerender="belowBottomRadio" status="status"/>
        </apex:selectRadio>
    
    
        <apex:outputpanel id="belowBottomRadio">
            {!bottomRadio}
            {!panelARendered}
            {!panelBRendered}
            <apex:outputpanel rendered="{!panelARendered}" id="bottomPanelA">
            Content A   
            </apex:outputpanel> <!-- End panelA -->
                             
            <apex:outputpanel rendered="{!panelBRendered}" id="bottomPanelB">
            Content B
            </apex:outputpanel> <!-- End panelB -->
        </apex:outputpanel> <!-- End belowBottomRadio -->

</apex:component>

 

<apex:page controller="APageController" showHeader="false">
    <apex:form >
        <apex:outputPanel id="TopPanel">
    
            <apex:actionRegion >
                <apex:selectRadio value="{!topRadio}" layout="pageDirection" >
                    <apex:selectOptions value="{!topRadioOptions}">
    
                    </apex:selectOptions>
                        <apex:actionSupport event="onclick" rerender="BottomPanel" status="status"/>                
                </apex:selectRadio>
            </apex:actionRegion>
        </apex:outputPanel> <!-- End TopPanel-->
        <apex:actionStatus id="status" startText="WOrking"></apex:actionStatus>
        <apex:outputPanel id="BottomPanel">
            <apex:outputPanel rendered="{!topRadio == 'option1'}">
                <c:mycustomcomponent ></c:mycustomcomponent>
            </apex:outputPanel>
            <apex:outputPanel rendered="{!topRadio == 'option2'}">
                Other Content
            </apex:outputPanel>
        </apex:outputPanel>
    </apex:form>
</apex:page>

 

 

 

I'm not sure if down the road you're giong to encounter some side-effects since your component code doesn't contain and actionregion (perhaps you could wrap the component call, ugly I know). Anyway, sorry for the red-herring, hope I've made it up to you ;)

 

Wes

garybgaryb

Wes, thanks for getting back to me :) No worries about the red herring, as there's something definitely not right there. Besides, I said that it was probably something fundamental I was missing - I did not know about the one-form-per-page recommendation. Sounds like I need to read up on action regions!

 

The reason for the action region was that the bottom radio button would control what fields were displayed to the user; some of these fields had their "required" attribute set to true. Without an action region, toggling between the two forms was not possible as trying to move between them, you would receive "Value is required" error messages.

 

I think I need to go back to the drawing board, but at least I'm not banging my head against a brick wall for now. Thanks again for your help.

 

2 quick things - 1. do you have a link to somewhere with the "one form per page" advice? 2. You said you found a solution that violated that principle, but the updated markup you posted doesn't violate the principle from what I can see - again, any chance you could post a link? Thanks!

WesNolte__cWesNolte__c

I will happily link to the article as it's in my blog :D It was some advice I got from Jill Wetzler, one of the Salesforce Developers, and I tried to create an article that detailed the solution, it can be found here,

 

http://th3silverlining.com/2009/10/19/multiple-forms-in-a-single-visualforce-page/

 

Strictly speaking I'm not violating the guideline SF has put in place, I've used one form, but the input area of the component isn't wrapped in it's own actionRegion so I'm sure that under some circumstances you're going to get some unexpected behaviour (maybe ping Jill, she'll tell you if I'm relaying BS or not :).

 

As I mentioned above perhaps you could just wrap the 'component-call' with an ActionRegion? Or - and I'm not sure about this one - do the inputs you're trying to use have an 'immediate' attribute? Setting these to true will bypass any validation.

 

Oh and sorry about the tardy replies, seems the subscription feature of the boards is a bit broken right now.

 

Wes

garybgaryb

Thanks Wes, no need to apologise. I tried the actionregion thing before you posted, didn't work, and I don't think I have the immediate option. I think for now I'm going to have to raise a case with Salesforce and in the meantime make smaller components and control whether they're drawn via the "master" page.

 

Thanks for everybody's input, if I find any solution, I'll post back!