0

I have an apex:tabPanel with 2 tabs. Each of these tabs include a Visualforce Component. Each component has its own controller. The parent page has its own controller, from which I am instantiating the child controllers. The child controllers while being instantiated get a reference of the parent controller.

Parent Page:

<apex:page docType="html-5.0" controller="MyRootController">
<apex:tabPanel switchType="client" selectedTab="{!selectedTab}" id="theTabPanel">
<apex:tab label="tb1" name="tb1" id="tb1">
<c:MyComponent1 Controller="{!myController1}"/>
</apex:tab>
<apex:tab label="tb2" name="tb2" id="tb2">
<c:MyComponent2 Controller="{!myController2}"/>
</apex:tab>
</apex:tabPanel>
</apex:page>

Root Controller:

public class MyRootController {

public MyController1 myController1 = null;
public MyController2 myController2 = null;
public MyController1 getMyController1 (){
    return myController1;
}
public MyController2 getMyController2 (){
    return myController2;
}
public MyRootController() {
    myController1 = new MyController1(this);
    myController1 = new MyController2(this);
}
public String selectedTab { get; set; }

}

Now the issue is that whenever I encounter a validation error (simple Apex validation, field missing), my tab jumps to the first tab. I have to manually click the second tab (if validation error was on the second tab) to see the pageMessages. This doesn't work and everytime I land on the first tab.

To fix this I am setting the rootController's selectedTab variable from the error situations in the child controllers and my action method returns void.

Child Controller action method:

public void doSomethingFromTab1() {
    if (validateInput() == false) {
        //stay on current tab
        rootController.selectedTab = 'tb1';
        System.debug('rootController.selectedTab set to:' + rootController.selectedTab);
        return;
    }
    //do something
}

Child component which triggers the error:

MyComponent1:

<apex:component >
<apex:attribute name="Controller" type="MyController1" description="The controller to pass to this component" required="true"/>
<apex:form id="myform1">
<apex:pagemessages id="messages"/>
<apex:pageBlock >
      ....
<apex:commandButton action="{!Controller.doSomethingFromTab1}" value="Tab 1 action!" id="btn1" />
      ....      
</apex:pageBlock>
</apex:form>
</apex:component>

I would really appreciate any help/insights on this...

Apologies for such a long post but I didn't see how I could have made it shorter.

1

I've had similar issues in the past with working with components that need to communicate with their parent. I forget exactly what I did to solve it as I don't have access to the code (it's with my old employer). Your solution is intriguing though, I'd have never thought about it. It's also worth noting that lightning components solve this with events and simplify everything pretty drastically so maybe you should consider making the switch.

Looking at your code, I think the issue you're having is that you need to re-render the tab panel on the parent page in order for your change to take effect.

Try something like this:

In your component, add the following attribute:

<apex:attribute name="tabPanelId" type="String" description="The tab panel to re-render"/>

In your parent page add something like this:

<apex:tabPanel id="tabPanel" other attributes..>
    <apex:tab>
        <c:customComponent tabPanelId="{!$Component.tabPanel}" other attributes...>
    <apex:tab/>
</apex:TabPanel>

Then in your component on your button:

<apex:commandButton  rerender="{!tabPanelId}" other attributes../>

Information on the $Component global variable. The one thing I want to point out is that in my experience, this variable is extremely picky. Every <apex:...> tag that can have an ID should have an id as you'll have to build the full path from the top of your page to your tabPanel in order for it to work. Test it by displaying the value straight to the page and making sure that it actually returns something. If it returns a blank string, you're missing an ID somewhere.

  • Thanks a lot for your reply. It pointed me in the right direction. I did all that you mentioned, re-rendered the root tab panel, but to no avail.. Then I just re-rendered the pageMessages and suddenly it all started working. I started investigation further, removed the logic of setting selectedTab from error conditions, and even then it worked! So now I read the docs and I think I was going the wrong way altogether ... selectedTab is only for DEFAULT selected tab, it is the "value" attribute of apex:tabPanel that sets the tab dynamically. I must say they are very inappropriately named. – SFDC Dev 27 Jan 4 '18 at 8:28
1

I have got this working, though parts of it are still unclear (as to why it is working). Still, I am posting an answer.

I have only made this change:

<apex:commandButton action="{!Controller.doSomethingFromTab1}" value="Tab 1 action!" id="btn1" />

to

<apex:commandButton action="{!Controller.doSomethingFromTab1}" value="Tab 1 action!" id="btn1" rerender="pagemessages"/>

This apex:pageMessages is inside the component and not on the root page.

I have removed the logic to set tab on error condition from everywhere (so no need of setting the root controller selectedTab variable from child controller). Still it works. May be it is the framework that, while it refreshes the pageMessages, makes the error tab stay selected.

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.