January 23, 2011

Custom Component for iterating over 1000 items in Visualforce !

This post tells how to get around, a common problem faced in visualforce, i.e. iterating over more than 1000 items. Though after recent releases Apex collections can hold upto 10,000 items, but visualforce can iterate only upto 1000 items, this is a strange limitation Smile

Solution – Custom Visualforce Component !

Though this problem can be cracked using an Visualforce component. This component will give you almost same functionality as of <apex:repeat  … /> tag in visualforce.

This component is not doing anything really too smart. It addresses this 1000 limitation using the pretty common fix i.e. splitting the huge collection into a collection of collection(List<List<Object>>) and repeating in nested manner on both parent and child lists.

Component – “repeat10k”

“repeat10k” is the name of component shown below to iterate upto 10,000 items. Component code comprises of some visualforce markup and apex controller code. 

Component Visualforce markup - Repeat10K

<apex:component controller="Repeat10KController">
 <apex:attribute name="var" type="Object" description="The variable to represent a single var in the iteration." required="true"/>
 <apex:attribute name="collection" description="collection to iterate" type="object[]" required="true"
     assignTo="{!userCollection}" />

<apex:repeat var="lstOfObjs" value="{!wrappedCollection}">
    <apex:repeat value="{!lstOfObjs}" var="rec">
        <apex:componentBody >
            <apex:variable var="{!var}" value="{!rec}"/>     
        </apex:componentBody>    
    </apex:repeat>
</apex:repeat>

</apex:component>

Component apex controller - Repeat10KController

public class Repeat10KController {

    // Binds with the user provided value
    public Object[] userCollection {get;set {
       wrappedCollection = toCollectionOfCollection(value);       
    }}

    // derived collection created, from user provided collection 
    // of upto 10000 items
    transient public List<List<Object>> wrappedCollection  {get; private set;}
    
    /*
        Converts a collection of Object to 
        collection of collection with parent collection holding upto 1000 items
    */
    static List<List<Object>> toCollectionOfCollection(List<Object> coll){
        List<List<Object>> mainList = new List<List<Object>>();
        List<Object> innerList = null;
        Integer idx = 0;
        for(Object obj:coll){
            if (Math.mod(idx++, 1000) == 0 ) {
                innerList = new List<Object>();
                mainList.add(innerList);
            }
            innerList.add(obj);            
        }        
        return mainList;
    }    
}

Testing the Component !

To test this component, I created a visualforce page backed by Apex controller. This page feeds a collection of 10,000 integers to the repeat10k component, and in turn prints out the loop variable in each iteration.

Visualforce Page !

<apex:page controller="Repeat10kTestController" sidebar="false">
  <apex:sectionHeader title="Iterating 10,000 items in Visualforce"/>

  <c:repeat10k collection="{!integers}" var="val" >
     <apex:outputText value="{!val}"/> &nbsp;
  </c:repeat10k>  
</apex:page>

Component apex controller

public class Repeat10kTestController {
// Collection of intergers to hold 10000 items
public List<Integer> integers {get;set;}

{
    // Populate the collection in initalizer block
    integers = new List<Integer>();
    for (Integer idx = 1; idx <= 10000; idx++) integers.add(idx);
}

}

Test Page Screen shot !

For sake of demo purposes, on executing above page, prints all the 10,000 items like this.

image

Known Issues

I tried to make this component generic enough to fit in any type of Collection. But unfortunately this component code will gel with all primitive data types only. This is because visualforce supports static binding for page variables. So we can’t generalize this component to work for any type/class/sobject.

To make it work with other types like Sobjects or UDT(User Defined Types) you need to copy this component and replace all occurrences of Object to the class/sobject name.

Feedback

Let me know, if you have any more ideas and improvements. For sure one can add support for missing attributes available in <apex:repeat  … /> tag. I kept the code simple for sake of demoing the core purpose.