This is a continuation of another question with code pared down as much as possible. I'm trying to create a series of pageblocktables on a VF page, each displaying some number of empty rows which users will be able to populate. The user enters the number of rows to display in an apex:input component above each table. I was advised to use a wrapper class to do this in the other question, which I've tried to implement, but I'm fairly new to coding and I'm still not totally sure what I'm doing, to put it charitably.

My question is, can I bind the input component(s) to the numOlis property of oppWrapper, so that the number of rows for each table can be independently controlled? I've found that if I set the value of numOlis in the constructor, as below:

public oppWrapper(Opportunity oppRec){
    this.oppRec = oppRec;
    this.numOlis = 2;

I see the value reflected in the page.

enter image description here

But I've been unable to find a way to get any user-defined values from the inputs back to the controller before the list is built. Here is the code I've got at the minute, minus irrelevant lines:


<apex:page title="Clone N Opportunity Line Items" controller="OpportunityCloneNCtrlr" sidebar="false" tabStyle="Opportunity" showHeader="true" docType="HTML-5.0">
    <apex:form id="N">
        <apex:pageBlock id="pb" mode="inlineEdit">
            <apex:pageBlockSection id="pbs" columns="1">
                <apex:repeat id="oppAndOlis" var="opp" value="{!oppsList}">
                    <apex:outputPanel id="thePanel" styleClass="panelStyle">
                        How many line items would you like to create? &nbsp;
                        <apex:input id="numOlisInput" style="width:20px" value="{!opp.numOlis}" onkeydown="if(event.keyCode==13){this.blur();}">
                            <apex:actionSupport event="onchange" action="{!buildOlisList}" reRender="olis[opp]"/>
                        </apex:input> &nbsp;
                    <apex:pageBlockTable id="olis" value="{!allOliLists[opp]}" var="oli">
                        <apex:repeat value="{!oliLabelToAPIMap}" var="label">
                            <apex:column headerValue="{!label}">
                                <apex:outputField value="{!oli[oliLabelToAPIMap[label]]}">
                                    <apex:inlineEditSupport event="ondblclick"/>


public class OpportunityCloneNCtrlr {
    public List<Opportunity> clonesList {get; set;}
    public List<oppWrapper> oppsList {get; set;}

    public OpportunityLineItem oliPrototype {get; set;}
    public List<OpportunityLineItem> oliPrototypes {get; set;}

    public Map<String, Schema.sObjectField> oliFieldMap {get; set;}
    public Map<String, Schema.sObjectField> oliFieldMapHelper {get; set;}
    public Map<String, String> oliLabelToAPIMap {get; set;}

    public Set<String> oliFieldNames {get; set;}

    public Map<oppWrapper, List<OpportunityLineItem>> allOliLists {get; set;}

    public OpportunityCloneNCtrlr(){

        oliFieldMap = new Map<String, Schema.sObjectField>();
        oliFieldMapHelper = new Map<String, Schema.sObjectField>();
        oliFieldMapHelper = Schema.sObjectType.OpportunityLineItem.fields.getMap();

        for(String s : oliFieldMapHelper.keySet()){
            if((!oliFieldMapHelper.get(s).getDescribe().isCalculated() && oliFieldMapHelper.get(s).getDescribe().isUpdateable()) || s == 'product2Id') oliFieldMap.put(s, oliFieldMapHelper.get(s));
            else continue;

        oliLabelToAPIMap = new Map<String, String>();
        for(String fieldAPI : oliFieldMap.keySet()){
            } else{

    public void buildOlisList(){
        clonesList = 
                Related_Programmes__c = :rpId 
                AND Id != :crId];
        oppsList = new List<oppWrapper>();

        for(Opportunity o : clonesList){
            oppWrapper opp = new oppWrapper(o);

        oliFieldNames = new Set<String>();

        for(String s : oliFieldMap.keySet()){
             if(!oliFieldMap.get(s).getDescribe().isCalculated() && oliFieldMap.get(s).getDescribe().isUpdateable()) oliFieldNames.add(s);
            else continue;

        allOliLists = new Map<oppWrapper,List<OpportunityLineItem>>();

        Integer i;
        for(oppWrapper opp : oppsList){
            oliPrototype = new OpportunityLineItem();
            oliPrototype.OpportunityId = opp.oppRec.Id;
            oliPrototypes = new List<OpportunityLineItem>();

            for(i = 0; i < opp.numOlis; i++){

    public class oppWrapper{
        public Integer numOlis {get; set;}
        public Opportunity oppRec {get; set;}
        public oppWrapper(Opportunity oppRec){
            this.oppRec = oppRec;
            this.numOlis = numOlis;

1 Answer 1


In summary, each variable name only holds one value at a time. This is relevant because of this line of code right here:

oppsList = new List<oppWrapper>();

What you're essentially saying is "discard any previous values and make a new list." In this case, the previous values were the user input.

Before this line of code, assuming the list was previously built, already had the data you needed from the input fields.

You'll want to write a separate method that reflects changing the value. It will start off looking like:

public void updateOliList() {
  for(OppWrapper item: oppList) {
    OpportunityLineItem[] lines = allOliLists.get(item);
    while(lines.size() > item.numOlis) { // List too big, get rid of extras
    while(lines.size() < item.numOlis) { // List too small, add rows
      lines.add(new OpportunityLineItem(...));

Note, in programming, we have Objects and Variables. A single Object can be referenced by multiple Variables, but each Variable can only hold one Object. For example, your name might be Bob, but your kids call you Dad, your parents call you Son, your wife calls you... you get the idea. Multiple names, same person (I realize this isn't a perfect analogy, but I hope you get the idea).

In this case, your "prototype" list item is problematic, because of the same effect you had in your previous question; each item in the list is actually referring to the same Object, which means that when you edit one line, you're editing all of them.

Fortunately, sobjects can be "cloned" to make new copies, so this is what you should do to avoid this problem:

        for(i = 0; i < opp.numOlis; i++){
            oliPrototypes.add(oliPrototype.clone(false, false, false, false));

You can read more about the clone method in the SObject class.

  • Hi sfdcfox, I've managed to get the code working at last, following your suggestions. Thanks for a clear and thorough explanation! Nov 30, 2018 at 13:44

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .