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
London BenLondon Ben 

Moderators / VF experts - A bug with repeat/tabs? (full code to replicate inside)

Hi guys,

I'm experiencing some unexpected behavior when trying to use the apex:repeat function to render tabs....

Basically it seems that tabs cannot be created on demand?  My controller is definitely returning data... as the second page block in my sample will show... repeat works - just the tabs are not rendered..

I'm sure any of you guys can see where I was going with this - and I guess I can achieve a similar result by dropping down to boring old html - just trying to use the standard components (as per best practice)

Any assistance greatly appreciated - as the purist coder me is seriously disturbed at the moment...

here is my 'simplified' and easily testable page & controller

Code: VF PAGE
<apex:page controller="clsRecordType">
    <apex:pageBlock >
        <apex:tabPanel id="theTabPanel">
            <apex:tab label="Account Types"/>
            <apex:repeat value="{!RecordTypes}" var="types">
                <apex:tab label="{!types.Name}"/>     
            </apex:repeat>
        </apex:tabPanel>
    </apex:pageBlock>

    <apex:pageBlock >
        <apex:repeat value="{!RecordTypes}" var="types">
            {!types.Name}<br/>       
        </apex:repeat>
    </apex:pageBlock>
</apex:page>

and the controller

Code:
public class clsRecordType {

    list<RecordType> lrecordtypes;

    public list<RecordType> getRecordTypes() {
        if(lrecordtypes==null) lrecordtypes = [Select Name, Id, Description From RecordType  where SobjectType = 'Account' and IsActive=True];
        return lrecordtypes;
        }
   }

 
 

Best Answer chosen by Admin (Salesforce Developers) 
dchasmandchasman
I would recommend taking a look at the combination of VF custom components and well build browser widget library like Ext.js (this is the technology we are moving many of our standard components to over time - including the tab related ones. Here is the basic start of a custom component using Ext.js's

First create a Static Resource called ext and use this compacted ext.js zip as the resources contents.

Next create the following page and 2 components and you should be good to go.

Demo Page:
<apex:page standardController="Account" title="XXX">
    <apex:pageBlock>
        <apex:detail relatedList="false"/>
        <apex:pageblockSection title="Contacts">
            <c:tabpanel width="1000">
                <apex:repeat var="contact" value="{!account.contacts}">
                    <c:tab title="{!contact.name}">
                        <apex:detail subject="{!contact.id}" relatedList="false"/>
                    </c:tab>
                </apex:repeat>            
            </c:tabPanel>
        </apex:pageblockSection>            
    </apex:pageBlock>        
</apex:page>

 
Component c:tabPanel:
<apex:component id="theTabPanel">
    <apex:attribute name="width" type="String" description="TODO: Describe me"/>
   
    <apex:includeScript value="{!urlFor($Resource.Dutchco__ext, '/ext-2.1/adapter/ext/ext-base.js')}"/>
    <apex:includeScript value="{!urlFor($Resource.Dutchco__ext, '/ext-2.1/ext-all.js')}"/>
    
    <apex:stylesheet value="{!urlFor($Resource.Dutchco__ext, '/ext-2.1/resources/css/ext-all.css')}"/>
    
    <apex:outputPanel id="theTab"/>

    <script>
    var currentTabPanelItems = [];
    </script>    
    <apex:componentBody />
    
    <script>    
    Ext.onReady(function() {
        // basic tabs 1, built from existing content
        var tabs = new Ext.TabPanel({
            renderTo: '{!$Component.theTab}',
            width: '{!nullValue(width, '100%')}',
            activeTab: 0,
            frame: true,
            defaults: {autoHeight: true},
            items: currentTabPanelItems
        });
    });
    </script>
</apex:component>

 
Component c:tab:
<apex:component id="theTab">
    <apex:attribute name="title" type="String" description="TODO: Describe me"/>
    
    <div id="{!$Component.theTab}:content" style="display: none">
        <apex:componentBody/>
    </div>
     
    <script>
    currentTabPanelItems.push({ html: Ext.fly('{!$Component.theTab}:content').dom.innerHTML, title: '{!title}'});
    </script>
</apex:component>



Message Edited by dchasman on 07-11-2008 09:49 AM

Message Edited by dchasman on 07-11-2008 09:59 AM

All Answers

jwetzlerjwetzler
The repeat tag does not work with some of our components the way you might want it to for some reasons that are difficult to explain without getting into the details of the code behind them.  Some of our components (like tabPanel and pageBlockSection, to name a couple) are not equipped to render their children when they are contained in a repeat tag (respectively, tab and pageBlockSectionItem). 

It's a known issue and I think we've got a bug on that already.

PS - Thanks for reducing your code down to a small, reproducible example.  It's much appreciated by my team!
London BenLondon Ben
Jill,

many thanks for your prompt & frank response - sadly it only served to confirm my observation (I was holding out some vague hope that I was just being a muppet).

Given the situation - is it possible for you or someone to write a quick summary of all the components that are specifically NOT compatible with repeat? - I've chased my tail for a while on this particular repeat/tab issue and I'm sure I'm not going to be the last to do so.

Likewise - perhaps one of you SF developer geniuses with the benefit of the code behind such components - can suggest a work around...  I've been pondering the possibility of constructing a set of tabs in the controller code for injection back into the page etc... not sure if this is feasible for example?

Cheers,
Ben
dchasmandchasman
I would recommend taking a look at the combination of VF custom components and well build browser widget library like Ext.js (this is the technology we are moving many of our standard components to over time - including the tab related ones. Here is the basic start of a custom component using Ext.js's

First create a Static Resource called ext and use this compacted ext.js zip as the resources contents.

Next create the following page and 2 components and you should be good to go.

Demo Page:
<apex:page standardController="Account" title="XXX">
    <apex:pageBlock>
        <apex:detail relatedList="false"/>
        <apex:pageblockSection title="Contacts">
            <c:tabpanel width="1000">
                <apex:repeat var="contact" value="{!account.contacts}">
                    <c:tab title="{!contact.name}">
                        <apex:detail subject="{!contact.id}" relatedList="false"/>
                    </c:tab>
                </apex:repeat>            
            </c:tabPanel>
        </apex:pageblockSection>            
    </apex:pageBlock>        
</apex:page>

 
Component c:tabPanel:
<apex:component id="theTabPanel">
    <apex:attribute name="width" type="String" description="TODO: Describe me"/>
   
    <apex:includeScript value="{!urlFor($Resource.Dutchco__ext, '/ext-2.1/adapter/ext/ext-base.js')}"/>
    <apex:includeScript value="{!urlFor($Resource.Dutchco__ext, '/ext-2.1/ext-all.js')}"/>
    
    <apex:stylesheet value="{!urlFor($Resource.Dutchco__ext, '/ext-2.1/resources/css/ext-all.css')}"/>
    
    <apex:outputPanel id="theTab"/>

    <script>
    var currentTabPanelItems = [];
    </script>    
    <apex:componentBody />
    
    <script>    
    Ext.onReady(function() {
        // basic tabs 1, built from existing content
        var tabs = new Ext.TabPanel({
            renderTo: '{!$Component.theTab}',
            width: '{!nullValue(width, '100%')}',
            activeTab: 0,
            frame: true,
            defaults: {autoHeight: true},
            items: currentTabPanelItems
        });
    });
    </script>
</apex:component>

 
Component c:tab:
<apex:component id="theTab">
    <apex:attribute name="title" type="String" description="TODO: Describe me"/>
    
    <div id="{!$Component.theTab}:content" style="display: none">
        <apex:componentBody/>
    </div>
     
    <script>
    currentTabPanelItems.push({ html: Ext.fly('{!$Component.theTab}:content').dom.innerHTML, title: '{!title}'});
    </script>
</apex:component>



Message Edited by dchasman on 07-11-2008 09:49 AM

Message Edited by dchasman on 07-11-2008 09:59 AM
This was selected as the best answer
hamayoun65hamayoun65

Doug

 

I just cannot tell you how much you rock!   I just ran into this bug and was looking down and out, and then I see your solution.... thank you sooooo much!

JumboJumbo

Hi,

 

Many thanks for this sollution, i have using this for my application.

 

Tariq.

pooja Agichapooja Agicha

Hi I am using the below code. Instead of the Displaying detail page on Tab click , I want to send the Id of the selected tab to the controller . This is not happening currently . Any help is appreciated .

 

<apex:pageBlockSection title="Offering Products" id="product1">

         <c:tabpanel width="1000" >

            <apex:repeat var="OfferingProduct" value="{!OfferingProducts}">

                    <c:tab title="{!OfferingProduct.Name}">

                          <apex:detail Subject="{!OfferingProduct.Id}" relatedList="false"/>

                              <apex:inputHidden value="{!selectedValue}" id="theHiddenInput"/>

                                        <apex:repeat var="Service" value="{!Services}">

                                                 <apex:outputLabel value="{!Service.Name}" for="theCheckbox"/>

                                                         <apex:inputCheckbox value="{!CheckBoxSelectedValue}" id="theCheckbox"/>

                                     </apex:repeat>

                            </c:tab>

                        </apex:repeat>

                </c:tabpanel>

                 </apex:pageblockSection>

Thanks,

pooja Agichapooja Agicha

Can anyone help me with the above code.. I want {!Offering.Id} in controller class

Rajesh ShahRajesh Shah

How are you passing the Id to the controller?

pooja Agichapooja Agicha

<apex:pageBlockSection title="Offering Products" id="product1">

         <c:tabpanel width="1000" >

            <apex:repeat var="OfferingProduct" value="{!OfferingProducts}">

                    <c:tab title="{!OfferingProduct.Name}">

                          <apex:repeat var="Service" value="{!Services}">

                                     <apex:outputLabel value="{!Service.Name}" for="theCheckbox"/>

                                         <apex:inputCheckbox value="{!CheckBoxSelectedValue}" id="theCheckbox"/>

                                     </apex:repeat>

                            </c:tab>

                        </apex:repeat>

                </c:tabpanel>

                 </apex:pageblockSection>

I am using the above code in my page.

Offering.Name is my tab name , my requirement is when I click the tab , it should pass the offering.Id to the controller class.below is my controller code

 

public List<VMobile_Product_catalogue_VM__c> getOfferingProducts() {

       List<VMobile_Product_catalogue_VM__c> ProductName = new List<VMobile_Product_catalogue_VM__c>();

        

         if(offer != NULL) {

                String offerId;

                

                for ( 

                Vmobile_offering_product__c vProd :[select Id,vProd.VMobile_Product__c from Vmobile_offering_product__c vProd

                where vProd.Vmobile_offering__c  = : offer] ){

                        offerId = vProd.VMobile_Product__c;

                        for(VMobile_Product_catalogue_VM__c  prod : [Select Name, Id,Is_Root__c From VMobile_Product_catalogue_VM__c  where Id =: offerId])

                            {

                                    if(prod.Is_Root__c == true)

                                    {

                                                                                                           

ProductName.add(Prod);

                                     //   ProdId = OfferingProducts.Id;

                                        system.debug('Prod Name List ==== ==='+ProductName);

                                        List<aggregateResult> results = [Select v.VMobile_Product__c, SUM(v.Price__c) sum From Vmobile_Product_Price__c v where v.VMobile_Product__c = : offerId group by v.VMobile_Product__c ];

                                        //List<AggregateResult> results = [Select AccountId, SUM(Amount) sum From Opportunity  GROUP BY AccountId];

                                        for(AggregateResult ar : results){

                                        productPrice = String.valueOf(ar.get('sum'));

                                        }

                                    }

                            }

                        }

        }

        system.debug('Returning Produvt Name.....'+ProductName);

        return ProductName;

    }

  

DrawloopSupportDrawloopSupport

This has been an issue for 2 1/2 years?? Salesforce, any idea when this functionality might be available out-of-the-box?

 

Thanks.

GoForceGoGoForceGo

Doug,

 

This does not seem to work anymore - I made it work last year. I had Resource.Ext  (instead of Ducho_Ext) and I am still using it. I even tried using ext-3.0.0.

Seems like the js files are there, but it doesn't want to work...

 

 

 

ashokkumar.rrashokkumar.rr

 

Hi

 

    i am facing some issue after implementing this code.

 

Assume this senario :

 

       U create the check box in Checked state. when user uncheck the check box   and rerender the the block.. its rerender the check box with check state..

 

if u are using my  code please uncheck the check box and whic is located inside the tab pabel.....

 

it  is not capturing the uncheck state.. this happens only in this senario.

 

U create the check box in unchecked state every this work file..

if u remove the tab panel tags .. every thing work fine....  

 

 

Can any 1 help to resolve this issue..

 

Apex page :

 

 

<apex:page controller="TestCheck">
<apex:form >
  <apex:pageBlock id="CheckBlock">
  <apex:pageBlockButtons >
      <apex:commandButton action="{!markCheck}" reRender="CheckBlock"  value="Save Workflow"/>
  </apex:pageBlockButtons>
 
        <apex:inputCheckbox id="checkBox" value="{!status1}"/>
  <c:ashTabPanel width="980" id="thePanel" >
  <c:ashtab title="test" id="tab1">
      <apex:inputCheckbox id="checkBox11" value="{!status}" lang="test"/>
  </c:ashtab>
  </c:ashTabPanel>
 
  </apex:pageBlock>
  </apex:form>
</apex:page>

 

 

Controller:

 

public class TestCheck
{
    public boolean status {get;set;}
    public boolean status1 {get;set;}
   
    public TestCheck()
    {
        status  = true;
        status1 = true;
    }
   
    public void markCheck()
    {
    }
}

alaschgarialaschgari

Solution (with <apex:detail />) does not work anymore. Any ideas why?

Keith987Keith987

Just discovered that apex:repeat and apex:tab don't work together; looks like many hours of extra (and potentially fragile) work is now going to be needed.

 

+1 for the platform providing support for this.

tsalb_tsalb_

Would love to know why this doesn't work with apex:detail too...

adflintadflint
Just implemented this and am having the same issue of passing a boolean value of one of my records back to the controller...

here is an idea for SFDC to support apex:tab inside of apex:repeat:

https://success.salesforce.com/ideaView?id=08730000000l3gHAAQ

Please vote for the idea!
ClaiborneClaiborne
I was able to download the ext.js v2.1, and it works great. I did not try the detail. Instead, I created an <apex:pageBlock><apex:pageBlockSection> and then added the fields I wanted to edit as <apex:inputField>.

BUT I HAVE A PROBLEM.

I have a pretty complex page that has several dynamic tab panels and dynamic tabs. The code works create with one dynamic tab panel, but when I add a second one, it duplicates the information from the first tab. I tried creating different components with different Id's, tabPanelA and tabA, but I cannot get this to work at all.

I am somewhat of a novice with Javascript, but I think I need a way to pass a unique id with each implementation of the c:tabPanel. 

Any ideas?

And YES, vote for the enhancement - it has been 6 years with only 4 votes.
ClaiborneClaiborne
I solved my earlier problem with multiple tab panels components on a page. I also solve a problem with where I needed to display the tab strip - the names of the tab at the top of the panel in a wrapped manner. Both problems were fixed with a change to dchasman's original tab panel component.

There two new attributes added to the component:
  • uid is an id for the tab panel component. It can be any text string, but there has to be a unique uid for every tab panel component onthe visualforce page.
  • rowStyle specifies a style class that is associated with the ul.x-tab-strip. The two classes are defined in the component code as:
    • onerow - The width defined in onerow should be large enough so that all of your tabs show in a single row. If the number of tabs require more than the width specified, the row will wrap. Be aware that this also expands the width of the tab component, which can result in a lot of scrolling.
    • multirow - The width for multi row should always be "100%". This will cause the tab row to wrap to fit the size of the browser window and to dynamically expand or contract if the browser window side changes.
Screenshots of the different rowStyle values:
  • rowStyle = "oneRow" - tab headers in a long single row. Note how the tab area is stretched to width specified in the onerow style class.rowStyle = "oneRow" - tab headers in a long single row. Note how the tab area is stretched to width specified in the onerow style class.
  • ​rowStyle = "multirow" - tab headers wrapped into multiple rows to fit the browser window size.tab headers wrapped into multiple rows to fit the browser window size
WARNING - This works in Chrome and Internet Explorer. But I have had problems with rerendering the page in Internet Explorer when using <apex:actionSupport>. For some reason, the refresh operation in IE does not refresh anything inside of the tab panel component.

New tab panel component is below. Refer to dchasman's original post for the rest of the instructions.
<apex:component id="theTabPanel">
    <apex:attribute name="width" type="String" description="Width of tab panel"/>
    <apex:attribute name="uid" type="String" description="Unique for each time tab panel component used on page" required="true"/>
    <apex:attribute name="rowStyle" type="String" description="Options are onerow for one long row of tabs or multirow for wrapped tabs in current frame" required="true" />
   
    <apex:includeScript value="{!urlFor($Resource.extjs21, '/ext-2.1/adapter/ext/ext-base.js')}"/>
    <apex:includeScript value="{!urlFor($Resource.extjs21, '/ext-2.1/ext-all.js')}"/>
    
    <apex:stylesheet value="{!urlFor($Resource.extjs21, '/ext-2.1/resources/css/ext-all.css')}"/>

<!-- Add styles to allow for one row or multirow tab strip
  
     Always use 100% for multirow width
     
     Use a large pixel value for onerow width. The tabs will wrap once 
     the pixel value is used up. But if the pixel value is too large, 
     the resulting window will have to scroll.
-->
    <style>
        .onerow ul.x-tab-strip {width: 1500px !important;}
        .multirow ul.x-tab-strip {width: 100% !important;}
    </style>    

<!-- Apply rowStyle - onerow or multirow - as style class to outputPanel -->  
    <apex:outputPanel id="theTab"  styleClass="{!rowStyle}"/>

    <script>
        var currentTabPanelItems{!uid} = [];
    </script>    
    <apex:componentBody />
    
    <script>    
        Ext.onReady(function () {
                // basic tabs, built from existing content
            var tabs{!uid} = new Ext.TabPanel({
                renderTo: '{!$Component.theTab}',
                width: '{!nullValue(width, '100%')}',
                activeTab: 0,
                frame: true,
                defaults: {autoHeight: true},
                items: currentTabPanelItems{!uid}
            });
        });
    </script>
</apex:component>