0

I have a wrapper class to that include opportunity line items and a selectlist value set. When the page is rerendered, the oli fields are maintained, but the selectlist values are reset. I can't see why.

<apex:page standardController="Opportunity" extensions="OpportunityProductEntry" >
        <apex:sectionHeader Title="Manage Products" subtitle="{!Opportunity.Name}"/>
        <apex:messages style="color:red"/>

        <apex:form id="frm">
            <apex:outputPanel id="mainBody">
                <apex:pageBlock title="Selected Products" id="sp">
                    <apex:variable var="rowNum" value="{!-1}" />
                    <apex:pageblockTable value="{!tableRows}" var="s" id="pgbt">
                        <apex:column >
                            <apex:commandLink value="Remove" action="{!RemoveFromTable}" reRender="sp" immediate="true" onComplete="ReRenderTable();">
                                <!-- this param is how we send an argument to the controller, so it knows which row we clicked 'remove' on -->
                                <apex:param value="{!s.oli.PriceBookEntryId}" assignTo="{!toUnselect}" name="toUnselect"/>
                            </apex:commandLink>
                            <apex:variable var="rowNum" value="{!rowNum + 1}" /> 
                        </apex:column>
                        <apex:column headerValue="Product Name" value="{!s.oli.PriceBookEntry.Product2.Name}"/>
                        <apex:column >
                          <apex:selectList value="{!s.pb}" size="1"> 
                            <apex:selectOptions value="{!s.pb}"/>
                          </apex:selectList>
                        </apex:column>
                        <apex:column >
                            <apex:inputText value="{!s.str}"/>
                        </apex:column>
                        <apex:column headerValue="Unit Price">
                            <apex:inputField value="{!s.oli.UnitPrice}" required="true" id="price_level"/>
                        </apex:column>
                        <apex:column headerValue="Quantity">
                            <apex:inputField value="{!s.oli.Quantity}" required="true"/>
                        </apex:column>
                        <apex:column headerValue="Discount Item">
                            <apex:inputField value="{!s.oli.netsuite_conn__Discount_Item__c}"/>
                        </apex:column>
                    </apex:pageblockTable>
                    <apex:pageBlockButtons >
                        <apex:commandButton action="{!OnCancel}" value="Cancel" immediate="true"/>
                    </apex:pageBlockButtons>
                </apex:pageBlock>
            </apex:outputPanel>
        </apex:form>
    </apex:page>

public with sharing class OpportunityProductEntry {
    public Opportunity opp;
    public opportunityLineItem[] shoppingCart {get;set;}
    public String toUnselect {get; set;}
    public List<aTableRow> tableRows { get; set; }
    public List<selectoption> PriceBooks { get; set; }


    public class aTableRow {
        public OpportunityLineItem oli { get; set; }
        public List<selectoption> pb { get; set; }
        public String str { get; set; }

        public aTableRow(OpportunityLineItem c, List<selectoption> a, String b) {
            oli = c;
            pb = a;
            str = b;
        }
    }


    public List<selectoption> getPriceBooks() {
        List<selectoption> options = new List<selectoption>();
        Schema.DescribeFieldResult fr = Product2.Family.getDescribe();
        List<Schema.PicklistEntry> plelist = fr.getpicklistvalues();
        for(Schema.Picklistentry ple: plelist){
            options.add(new selectoption(ple.getvalue(),ple.getlabel()));
        }
        return options;
    } 


    public OpportunityProductEntry(ApexPages.StandardController controller) {
        opp = [SELECT Id FROM Opportunity WHERE Id = :controller.getRecord().Id];

        shoppingCart = [
            SELECT  Id, netsuite_conn__Discount_Item__c, Quantity, UnitPrice, PriceBookEntryId, PriceBookEntry.Product2.Name
            FROM OpportunityLineItem 
            WHERE OpportunityId = :opp.Id
        ];

        PriceBooks = getPriceBooks();

        tableRows = new List<aTableRow>();      
        for (OpportunityLineItem sc : shoppingCart)     {
            tableRows.add(new aTableRow(sc, PriceBooks, 'test'));
        }
    }


    public PageReference RemoveFromTable() {
        // This function runs when a user clicks "Remove" on an item in the "Selected Products" section.
        Integer count = 0;

        for (OpportunityLineItem d : shoppingCart) {
            if ((String)d.PriceBookEntryId == toUnselect) {
                tableRows.remove(count);
                break;
            }
            count++;
        }

        return null;
    }


    public PageReference OnCancel() {
        // If user hits cancel we commit no changes and return them to the Opportunity. 
        return new PageReference('/' + ApexPages.currentPage().getParameters().get('Id'));
    }
}
1
  • Welcome to the community Jerry. Your code should be stripped down to the minimum code required to reproduce your error. Not only does this help us answer your question faster and more accurately this is also a vital troubleshooting step (and it protects proprietary information). A rule of thumb to follow is that any user should be able to copy your code into a fresh org and the code should produce the same result. Here is a sample question
    – gNerb
    Aug 2, 2018 at 21:33

2 Answers 2

1
<apex:selectList value="{!s.pb}" size="1"> 

This isn't bound to the correct attribute. You need to have a String to bind to in order to save the value. I'm not sure what field you meant to bind this to, but you'll need to fix this line in order to get it to work.

9
  • If I change my code to have a string variable in the wrapper, it doesn't get updated either.
    – Jerry
    Aug 2, 2018 at 20:35
  • @Jerry Saw you edited your question--you defined a new variable, but you still didn't change your Visualforce code to match.
    – sfdcfox
    Aug 2, 2018 at 20:41
  • I added the variable to the wrapper class in my apex code and I have it showing on the vf page. What else do I have to do?
    – Jerry
    Aug 2, 2018 at 20:43
  • @Jerry Not according to the update I see in the question. The problem is the line <apex:selectList value="{!s.pb}" size="1">.
    – sfdcfox
    Aug 2, 2018 at 20:53
  • 1
    @Jerry Right. That has nothing to do with the select list.
    – sfdcfox
    Aug 2, 2018 at 21:07
0

Sample Page:

<apex:page controller="testPageController">
    <apex:form id="renderTarget">    
        <apex:pageBlock>
            <apex:pageBlockButtons>
                <apex:commandButton action="{!test}" value="Test" rerender="renderTarget" />
            </apex:PageBlockButtons>
            <apex:pageBlockTable value="{!contacts}" var="key">
                <apex:column value="{!contacts[key].key}"/>
                <apex:column>
                    <apex:outputLink value="javascript:;" onmouseup="remove('{!contacts[key].obj.Id}')">
                        Remove
                    </apex:outputLink>
                </apex:column>
                <apex:column value="{!contacts[key].obj.FirstName}"/>
                <apex:column>
                    <apex:selectList value="{!contacts[key].picklistVar}">
                        <apex:selectOptions value"{!options}" />
                    </apex:selectList>
                </apex:column>
            </apex:pageBlockTable>
        </apex:pageBlock>
        <apex:actionFunction action="{!remove}" rerender="renderTarget">
            <apex:param name="toUnSelect" value=""/>
        </apex:actionFunction>
    </apex:form>
</apex:page>

Sample Controller

public class testPageController {
    public Map<Id, ContactWrapper> contacts {get; set;}
    public List<SelectOption> options {get; set;}

    public testPageController  () {
        contacts = new List<ContactWrapper>();
        options = new List<SelectOption>();

        options.add(new SelectOption('0','none'));
        options.add(new SelectOption('1', 'Test Option'));

        for (Contact con : [
            SELECT FirstName, Custom_Field__c FROM Contact LIMIT 10
        ]) {
            contacts.out(con.Id, new ContactWrapper(con));
        }
    }

    public void test() {
        for (contactWrapper cw : contacts) {
            system.debug(cw.picklistVar);
        }
    }

    public void remove() {
        Id toUnSelect = ApexPages.CurrentPage.getParameters().get('toUnSelect');
        contacts.remove(toUnSelect);
    }

    public class ContactWrapper {
        public static Integer count {get; set;}
        public Contact obj {get; set;}
        public String picklistVar {get; set;}
        public Integer key {get; set;}

        static {
            count = 0;
        }

        public contactWrapper (Contact con) {
            this.obj = con;
            this.picklistVar = '';
            this.key = ++contactWrapper.count;
        }
    }
}

Additional Notes

Row Count

The way you're tracking the row number is "technically" not supported.

Note: does not support reassignment inside of an iteration component, such as <apex:dataTable> or <apex:repeat>. The result of doing so, e.g., incrementing the <apex:variable> as a counter, is unsupported and undefined.

I updated my answer to show 1 way you can do this without the apex:variable component.

Removing items from maps is easier than lists

Also, the way you remove an item from your "shopping cart" can be made more efficient by using a map (update in answer). This approach saves you a for loop and a counter variable.

You'll have to pay special attention to the syntax I used in the VF page when switching to a map.

Single Purpose Parameters

I also reconfigured the remove link so that instead of storing the Id of the record to remove at the page level, I store it in the method. This ensures that the variable can only be used for this one purpose and is cleaned up once it's done. This can prevent issues that occur when accessing the variable outside of this context.

Disclaimer

This code is intentionally not copy-pasta.

1
  • The values selected in the dropdown get reset after clicking on "Remove". Same as in my code.
    – Jerry
    Aug 2, 2018 at 23:19

You must log in to answer this question.

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