function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
SATHISH REDDY.SATHISH REDDY. 

Apex Tabs in Visualforce must load data only when clicked

Hello Trailblazers,

I'm trying to design a VFP with TWO apex tabs with each tab having a table with list of records within along with some custom "search" logic. Tab 1 will load the data by default on initial page load but I want tab 2 data to be loaded only when clicked(as a best practice). i also have a custom button within each tab which will just get the specific records onto the table. I'm really having hard time in figuring out how to achieve this as i tried to use the actionfunction ontabenter which didn't work. Please shed some light on what i might be doing wrong or share any examples specific to my use case.

VFP:
<apex:page controller="MarginTrackerController" sidebar="false">
    <apex:form >
        <apex:tabPanel title="Margin Tracker" id="tabpanelId" tabClass="activeTab" inactiveTabClass="inactiveTab" value="{!varTabId}">
            <apex:tab label="Summary" name="Summary" ontabenter="onClickSummaryTab();" switchType="ajax" id="Summary">

            </apex:tab>
            
            <apex:tab label="Margin" name="Margin" ontabenter="onClickMarginTab();" switchType="ajax" id="Margin">
            <div style="width:800px;float:left;">
            <!-- this is the filter used to SearchAction-->
            <apex:selectList multiselect="false" size="1" value="{!selectedTimeframe}">
                <apex:selectOptions value="{!timeframeList}"></apex:selectOptions>
            </apex:selectList>
            <apex:commandButton value="Go" action="{!searchAction}" status="status" rerender="formId,oppsPanel">
            </apex:commandButton>
            </div>
                <p><div class="oppstbldiv">
                <table class="oppstbl" id="myOppsTable">
                <thead>
                    <tr>
                        <th colspan="1">Opportunity</th>
                        <th colspan="1"> Type</th>
                        <th colspan="1"> Start</th>
                        <th colspan="1"> End</th>
                        <th colspan="1">Net Budget</th>
                        <th colspan="1">Sold Margin ($)</th>
                        <th colspan="1">Sold Margin (%)</th>
                        <th colspan="1">Booking Margin ($)</th>
                        <th colspan="1">Booking Margin (%)</th>
                        <th colspan="1">Revised Margin ($)</th>
                        <th colspan="1">Revised Margin (%)</th>
                    </tr>
                </thead>
                <tbody>
                    <apex:repeat value="{!oppList}" var="opp">
                        <tr>
                            <td style="text-align:left">{!opp.Name}</td>
                            <td>{!opp.Market__c}</td>
                            <td>
                                <apex:outputText value="{0,date,MM'/'dd'/'yyyy}">
                                    <apex:param value="{!opp.Start_Date__c}" /> 
                                </apex:outputText>
                            </td>
                            <td>
                                <apex:outputText value="{0,date,MM'/'dd'/'yyyy}">
                                    <apex:param value="{!opp.End_Date__c}" /> 
                                </apex:outputText>
                            </td>
                            <td>
                                <apex:outputText value="{0, number, currency}">
                                    <apex:param value="{!opp.Net_Budget__c}" />
                                </apex:outputText>
                            </td>
                            <td>
                                <apex:outputText value="{0, number, currency}">
                                    <apex:param value="{!opp.Sold_Margin__c}" />
                                </apex:outputText>
                            </td>
                            <td>
                                <apex:outputText value="{0, number, ##.##}%" rendered="{!NOT(ISNULL(opp.Sold_Margin_Percentage__c))}">
                                    <apex:param value="{!opp.Sold_Margin_Percentage__c}"/>
                                </apex:outputText>
                            </td>
                            <td>
                                <apex:outputText value="{0, number, currency}">
                                    <apex:param value="{!opp.Booked_Margin__c}" />
                                </apex:outputText>
                            </td>
                            <td>
                                <apex:outputText value="{0, number, ##.##}%" rendered="{!NOT(ISNULL(opp.Booked_Margin_Percentage__c))}">
                                    <apex:param value="{!opp.Booked_Margin_Percentage__c}"/>
                                </apex:outputText>
                            </td>
                            <td>
                                <apex:outputText value="{0, number, currency}">
                                    <apex:param value="{!opp.Revised_Margin__c}" />
                                </apex:outputText>
                            </td>
                            <td>
                                <apex:outputText value="{0, number, ##.##}%" rendered="{!NOT(ISNULL(opp.Revised_Margin_Percentage__c))}">
                                    <apex:param value="{!opp.Revised_Margin_Percentage__c}"/>
                                </apex:outputText>
                            </td>
                        </tr>
                    </apex:repeat>
                </tbody>
            </table>
            </div></p>
            </apex:tab>
            <apex:actionFunction action="{!SummaryTab}" name="onClickSummaryTab"/>
            <apex:actionFunction action="{!MarginTab}" name="onClickMarginTab"/>
        </apex:tabPanel>
    </apex:form>
</apex:page>

Controller:
public class MarginTrackerController {
    public List<Opportunity> oppList{get; set;}
    public Opportunity opp{get;set;}
    public List<SelectOption> timeframeList{get;set;}
    public String selectedTimeframe { get; set; }
    public String varTabId { get; set; }
    public MarginTrackerController(){
    }
    public pageReference SummaryTab(){
        varTabId = 'Summary';
        /*some logic to fetch records*/
        return null;
    }
    public pageReference MarginTab(){
        varTabId = 'Margin';
        opp = new Opportunity();
        timeframeList = new List<SelectOption>();
        timeframeList.add(new SelectOption('', '--None--'));
        selectedTimeframe = '';
        oppList = new List<Opportunity>();
        oppList = [SELECT Id, Name,Market__c, Start_Date__c, End_Date__c, Net_Budget__c, Sold_Margin__c, Sold_Margin_Percentage__c, Booked_Margin__c, Booked_Margin_Percentage__c, Revised_Margin__c, Revised_Margin_Percentage__c FROM Opportunity ORDER BY createdDate DESC LIMIT 100];
        return null;
    }   
    public void searchAction(){
        timeframeList.add(new SelectOption('', '--None--'));
        timeframeList.add(new SelectOption('Q1', 'Q1'));
        timeframeList.add(new SelectOption('Q2', 'Q2'));
        timeframeList.add(new SelectOption('Q3', 'Q3'));
        timeframeList.add(new SelectOption('Q4', 'Q4'));
        timeframeList.add(new SelectOption('Date', 'Date'));
        /*fetch the filtered records here*/
    } 
}

​​​​​​​Thanks in advance!
 
Best Answer chosen by SATHISH REDDY.
Santosh Kumar 348Santosh Kumar 348
Hi Satish,

I am quiet confused why you are facing the issue because <apex:facet name="start"> is bascially used for AJX request and it automatically gets closed when the ajax request is completed as per Salesforce Documentation. Even I have tried the same and it's working for me perfectly.

Just to add some delay in code, I have changed the code a little bit and it's still working for me. As I don;t have your exact code I can only guess that you must have mis placed something.

Please try below code in your org as it is, look if it is working for you if not let me know. But if it is working for you just try to place your code on similar lines:

There are slight changes from previous version, On Vf page I have added an alert which will pop up once ajax request is complete this will help you to figure out that when ajax request is completed then only loading image is getting disappeared:

VisualForce Page : onClickTabControllerVF
<apex:page id="thePage" controller="onClickTabController">
    <apex:form>
        <apex:tabPanel switchType="client" id="tabPanelId" >
            <apex:tab label="Tab One" name="tab1" id="tabOne" onTabEnter="clickTab1()">
                <apex:outputText>Message From Tab1 = {!isTab1} </apex:outputText><br/>
                <apex:outputText>Message From Tab2 = {!isTab2} </apex:outputText>
            </apex:tab>
            <apex:tab label="Tab Two" name="tab2" id="tabTwo" onTabEnter="clickTab2()">
                <apex:outputText>Message From Tab1 = {!isTab1} </apex:outputText><br/>
                <apex:outputText>Message From Tab2 = {!isTab2} </apex:outputText>
            </apex:tab>
        </apex:tabPanel>
        
        <apex:actionFunction action="{!function1}" name="clickTab1" reRender="tabOne,tabPanelId" status="counterStatus"/>
        <apex:actionFunction action="{!function2}" name="clickTab2" reRender="tabTwo,tabPanelId" status="counterStatus"/>
        
        <apex:actionstatus id="counterStatus" onstop = "alert('Successfully Loaded');" >
            <apex:facet name="start">
                <div class="waitingSearchDiv" id="el_loading" style="background-color: #fbfbfb; height:100%;opacity:0.65;width:100%;">
                    <div class="waitingHolder" style="top: 100px; width: 91px;">
                        <img class="waitingImage" src="/img/loading.gif" title="Please Wait..." />
                        <span class="waitingDescription">Loading...</span>
                    </div>
                </div>
            </apex:facet>
        </apex:actionstatus>
        
    </apex:form>
</apex:page>
Class Name: onClickTabController
public class onClickTabController {
    public String isTab1 {get;set;}
    public String isTab2 {get;set;}
    public Integer x {get;set;}
    
    public onClickTabController(){
        isTab1 = 'I am default';
        isTab2 = 'Please click me to get my message. This message is from Constructor.';
    }
    public void function1(){
        isTab1 = 'I am Tab 1 and You have clicked me, I am calling function1() from server';
        isTab2 = 'Please click me if you want to see my message';
    }
    public void function2(){
        x = 0;
        for(Integer i=0; i<100000;i++){
            x = x + i;
        }
        isTab1 = 'Please click me if you want to see my message';
        isTab2 = 'I am Tab 2 and You have clicked me, I am calling function2() from server' + x;
        
    }
     
}

Just like I have mentioned that to add a delay from AJAX I have added an unnecessary for loop to incerease the time. 
So first check this code work for you or not, if it works then just make changes in code accordingly on similar line.

Regards,
Santosh

All Answers

Santosh Kumar 348Santosh Kumar 348
Hi Sathish,

I have written a sample working code which you can use as per your need.

I guess you are missing reRender in your action:Region as you need to refresh your Tab once data is fetched from server. I guess that is only the missing part in your code. (render attribute mark as bold in below code.)

VF Page: onClickTabVf
<apex:page id="thePage" controller="onClickTabController">
    <apex:form>
        <apex:tabPanel switchType="client" id="tabPanelId" >
            <apex:tab label="Tab One" name="tab1" id="tabOne" onTabEnter="clickTab1()" focus="tabOne">
                <apex:outputText>Message From Tab1 = {!isTab1} </apex:outputText><br/>
                <apex:outputText>Message From Tab2 = {!isTab2} </apex:outputText>
            </apex:tab>
            <apex:tab label="Tab Two" name="tab2" id="tabTwo" onTabEnter="clickTab2()" focus="tabTwo">
                <apex:outputText>Message From Tab1 = {!isTab1} </apex:outputText><br/>
                <apex:outputText>Message From Tab2 = {!isTab2} </apex:outputText>
            </apex:tab>
        </apex:tabPanel>
        
        <apex:actionFunction action="{!function1}" name="clickTab1" reRender="tabOne,tabPanelId"/>
        <apex:actionFunction action="{!function2}" name="clickTab2" reRender="tabTwo,tabPanelId"/>
        
    </apex:form>
</apex:page>


Class: onClickTabController
public class onClickTabController {
    public String isTab1 {get;set;}
    public String isTab2 {get;set;}
    
    public onClickTabController(){
        isTab1 = 'I am default';
        isTab2 = 'Please click me to get my message. This message is from Constructor.';
    }
    public void function1(){
        isTab1 = 'I am Tab 1 and You have clicked me, I am calling function1() from server';
        isTab2 = 'Please click me if you want to see my message';
    }
    public void function2(){
        isTab1 = 'Please click me if you want to see my message';
        isTab2 = 'I am Tab 2 and You have clicked me, I am calling function2() from server';
    }
    
}

 If it still doesn't work for you let me know we can figure this out.

If you find the above solution helpful. Please mark as Best Answer to help others too.

Thanks and Regards,
Santosh Kumar
 
SATHISH REDDY.SATHISH REDDY.

Hi Santosh, 

Thanks for you help, I used the reRender attribute before but looks like i left the actionfunction within the tabpanel which prevented the rerender to happen in the 1st place. Removed & placed it outside the tabpanel & it works!

However, i did notice that on the initial load, when i switch to tab2 it shows just the table header & after a couple of seconds the data loads into the table which makes the transition bit ugly & bad UX. I tried to use the Actionstatus to have the spinner shown on tab switch & changed the switchtype to "ajax" to cover it. It didn't help either as it continues to show the header 1st & loads the data into the table after 2 to 3 seconds. Any suggestions on that part is really appreciated!

Thanks!
Sathish

Santosh Kumar 348Santosh Kumar 348
Hi Sathish,

I have updated the vf with loading image. This will help you :
 
<apex:page id="thePage" controller="onClickTabController">
    <apex:form>
        <apex:tabPanel switchType="client" id="tabPanelId" >
            <apex:tab label="Tab One" name="tab1" id="tabOne" onTabEnter="clickTab1()">
                <apex:outputText>Message From Tab1 = {!isTab1} </apex:outputText><br/>
                <apex:outputText>Message From Tab2 = {!isTab2} </apex:outputText>
            </apex:tab>
            <apex:tab label="Tab Two" name="tab2" id="tabTwo" onTabEnter="clickTab2()">
                <apex:outputText>Message From Tab1 = {!isTab1} </apex:outputText><br/>
                <apex:outputText>Message From Tab2 = {!isTab2} </apex:outputText>
            </apex:tab>
        </apex:tabPanel>
        
        <apex:actionFunction action="{!function1}" name="clickTab1" reRender="tabOne,tabPanelId" status="counterStatus"/>
        <apex:actionFunction action="{!function2}" name="clickTab2" reRender="tabTwo,tabPanelId" status="counterStatus"/>
        
        <apex:actionstatus id="counterStatus">
            <apex:facet name="start">
                <div class="waitingSearchDiv" id="el_loading" style="background-color: #fbfbfb; height:100%;opacity:0.65;width:100%;">
                    <div class="waitingHolder" style="top: 100px; width: 91px;">
                        <img class="waitingImage" src="/img/loading.gif" title="Please Wait..." />
                        <span class="waitingDescription">Loading...</span>
                    </div>
                </div>
            </apex:facet>
        </apex:actionstatus>
        
    </apex:form>
</apex:page>

If you find the above solution helpful. Please mark as Best Answer to help others too.

Thanks and Regards,
Santosh Kumar
SATHISH REDDY.SATHISH REDDY.
Thanks for the response Santosh, as i mentioned in my previous response that even having an "Actionstatus" with "loading.." gif didn't help me to render the data on time. There is a slight delay happening where actionstatus is unable to cover, which leaves the UI to show header for a couple of seconds before loading the data table data.
Note:- This happens when we open the VFP and then click on Tab2.
SATHISH REDDY.SATHISH REDDY.
@Santosh Kumar
Hi Santosh,

Can you please help me with my previous query, just noticed that the above mentioned delay is increased when record count is increased. So this seems to be an issue as "action status" is unable to cover that lag.

Thanks,
Sathish
Santosh Kumar 348Santosh Kumar 348
Hi Satish,

I am quiet confused why you are facing the issue because <apex:facet name="start"> is bascially used for AJX request and it automatically gets closed when the ajax request is completed as per Salesforce Documentation. Even I have tried the same and it's working for me perfectly.

Just to add some delay in code, I have changed the code a little bit and it's still working for me. As I don;t have your exact code I can only guess that you must have mis placed something.

Please try below code in your org as it is, look if it is working for you if not let me know. But if it is working for you just try to place your code on similar lines:

There are slight changes from previous version, On Vf page I have added an alert which will pop up once ajax request is complete this will help you to figure out that when ajax request is completed then only loading image is getting disappeared:

VisualForce Page : onClickTabControllerVF
<apex:page id="thePage" controller="onClickTabController">
    <apex:form>
        <apex:tabPanel switchType="client" id="tabPanelId" >
            <apex:tab label="Tab One" name="tab1" id="tabOne" onTabEnter="clickTab1()">
                <apex:outputText>Message From Tab1 = {!isTab1} </apex:outputText><br/>
                <apex:outputText>Message From Tab2 = {!isTab2} </apex:outputText>
            </apex:tab>
            <apex:tab label="Tab Two" name="tab2" id="tabTwo" onTabEnter="clickTab2()">
                <apex:outputText>Message From Tab1 = {!isTab1} </apex:outputText><br/>
                <apex:outputText>Message From Tab2 = {!isTab2} </apex:outputText>
            </apex:tab>
        </apex:tabPanel>
        
        <apex:actionFunction action="{!function1}" name="clickTab1" reRender="tabOne,tabPanelId" status="counterStatus"/>
        <apex:actionFunction action="{!function2}" name="clickTab2" reRender="tabTwo,tabPanelId" status="counterStatus"/>
        
        <apex:actionstatus id="counterStatus" onstop = "alert('Successfully Loaded');" >
            <apex:facet name="start">
                <div class="waitingSearchDiv" id="el_loading" style="background-color: #fbfbfb; height:100%;opacity:0.65;width:100%;">
                    <div class="waitingHolder" style="top: 100px; width: 91px;">
                        <img class="waitingImage" src="/img/loading.gif" title="Please Wait..." />
                        <span class="waitingDescription">Loading...</span>
                    </div>
                </div>
            </apex:facet>
        </apex:actionstatus>
        
    </apex:form>
</apex:page>
Class Name: onClickTabController
public class onClickTabController {
    public String isTab1 {get;set;}
    public String isTab2 {get;set;}
    public Integer x {get;set;}
    
    public onClickTabController(){
        isTab1 = 'I am default';
        isTab2 = 'Please click me to get my message. This message is from Constructor.';
    }
    public void function1(){
        isTab1 = 'I am Tab 1 and You have clicked me, I am calling function1() from server';
        isTab2 = 'Please click me if you want to see my message';
    }
    public void function2(){
        x = 0;
        for(Integer i=0; i<100000;i++){
            x = x + i;
        }
        isTab1 = 'Please click me if you want to see my message';
        isTab2 = 'I am Tab 2 and You have clicked me, I am calling function2() from server' + x;
        
    }
     
}

Just like I have mentioned that to add a delay from AJAX I have added an unnecessary for loop to incerease the time. 
So first check this code work for you or not, if it works then just make changes in code accordingly on similar line.

Regards,
Santosh
This was selected as the best answer
SATHISH REDDY.SATHISH REDDY.
Hi Santosh,
Awesome! I initially added the "status" to <apex:tab> instead of actionfunction which caused this issue where the tab click shows the status & dissappears before ajax req is complete. Now i added status to actionfunction & removed from tab component. Thank you are really appreciate your help!
Sathish
SATHISH REDDY.SATHISH REDDY.
Hi Santhosh,
An extension to this, when i add this script, the tab click is not working
<script src="//code.jquery.com/jquery-1.11.3.min.js"></script>
<script src="app.js"></script>
I have a table in tab two where the rows are collapsaible with inner table content. I'm using this script to allow row click toggle show/hide the inner table. Trying to achieve this https://webdesignerhut.com/data-table-with-collapsible-table-rows/
Please share your input. thank you!