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
MC2014MC2014 

blockTable not displaying quantity from a wrapper class.

I borrow  a good part of michalforce receipe (http://www.michaelforce.org/recipeView?id=a0G30000006eVxVEAU) and used this shopping cart code work with creating a case.

So whenever, I added a new product to the shopping cart, the quantity doesn't get saved. The quantity is stored in a wrapper class, and if system.debug upon saving it shows that PricebookEntry object and quantity is in the "shoppingCart2" wrapper list, somehow {!s.productQuantity} in VF isn't being pulled.?

Here is the entire Apex:
public with sharing class InternalFurfillment2 {

    public String searchString {get;set;}

    private List<PriceBookWrapper> forDeletion2 = new List<PriceBookWrapper>();
    public List<PriceBookWrapper> shoppingCart2 {get;set;}
    
    public priceBookEntry[] AvailableProducts {get;set;}
    public String toSelect {get; set;}
    public String toUnselect {get; set;}
    public Boolean overLimit {get;set;}
    
    public string priceBookName = 'Internal Price Book';
    
    private ApexPages.StandardController stdController;    
    private Case caseObject;
      
    public InternalFurfillment2(ApexPages.StandardController controller) {
        this.caseObject = (Case) controller.getRecord();  
        shoppingCart2 = new List<PriceBookWrapper>();

        updateAvailableList();       
    }

    public void updateAvailableList() {
        
        // We dynamically build a query string and exclude items already in the shopping cart
        String qString = 'select Id, Pricebook2Id, IsActive, Product2.Name, Product2.Family, Product2.IsActive, Product2.Description, UnitPrice, Product2.Vendor__c from PricebookEntry where IsActive=true and PricebookEntry.Pricebook2.Name = \'' + priceBookName + '\'';
        system.debug('qString begin==>' + qString);
        // note that we are looking for the search string entered by the user in the name OR description
        // modify this to search other fields if desired
        if(searchString!=null){
            qString+= ' and (Product2.Name like \'%' + searchString + '%\' or Product2.Description like \'%' + searchString + '%\')';
        }
        
        Set<Id> selectedEntries = new Set<Id>();
        
        for(PriceBookWrapper d : shoppingCart2){
            system.debug('d==>' + d);
            selectedEntries.add(d.pbeEntry.Id);
        }
        system.debug('selectedEntries==>' + selectedEntries);
               
        if(selectedEntries.size()>0){
            String tempFilter = ' and Id not in (';
            for(Id i : selectedEntries){
                tempFilter+= '\'' + (String)i + '\',';
            }
            String extraFilter = tempFilter.substring(0,tempFilter.length()-1);
            extraFilter+= ')';
            
            qString+= extraFilter;
        }
        
        qString+= ' order by Product2.Name';
        qString+= ' limit 101';
        
        system.debug('qString:' +qString);        
        AvailableProducts = database.query(qString);
        
        // We only display up to 100 results... if there are more than we let the user know (see vf page)
        if(AvailableProducts.size()==101){
            AvailableProducts.remove(100);
            overLimit = true;
        }
        else{
            overLimit=false;
        }
    }
    
    public void addToShoppingCart(){
        system.debug('AvailableProducts==>' + AvailableProducts);
        system.debug('shopping cart outside top==>' + ShoppingCart2); 
        // This function runs when a user hits "select" button next to a product    
        for(PricebookEntry d : AvailableProducts){
            if((String)d.Id==toSelect){
                shoppingCart2.add(new PriceBookWrapper(d,0)); system.debug('shopping cart inside==>' + ShoppingCart2);
                break;
            }
        }system.debug('shopping cart outside==>' + ShoppingCart2);       
        updateAvailableList();  
    }
    

    public PageReference removeFromShoppingCart(){    
        // This function runs when a user hits "remove" on an item in the "Selected Products" section            Integer count = 0;    
        for(PriceBookWrapper d : shoppingCart2){
            if((String)d.pbeEntry.Id==toUnselect){            
                if(d.pbeEntry.Id!=null)
                    forDeletion2.add(d);            
                shoppingCart2.remove(count);
                break;
            }
            count++;
        }        
        updateAvailableList();       
        return null;
    }
    
    public PageReference onSave(){        
        system.debug('shoppingCart2 submit==>' + shoppingCart2);
               return null;
    }
         
     public class PriceBookWrapper{  
         public Integer productQuantity {get;set;}  
         public PriceBookEntry pbeEntry {get;set;}  
         
         public PriceBookWrapper(PriceBookEntry pbeEntry, Integer productQuantity){  
             this.pbeEntry = pbeEntry;  
             this.productQuantity = productQuantity; 
         }      
    }  
}


The portion of VF that where quantity should be displaying:
..	
<apex:pageBlock title="My Content">	
		<apex:outputPanel id="mainBody">
        
<!-- this is the upper table... a.k.a. the "Shopping Cart"-->
            <!-- notice we use a lot of $ObjectType merge fields... I did that because if you have changed the labels of fields or objects it will reflect your own lingo -->
            <apex:pageBlockSection title="{!$ObjectType.Product2.LabelPlural} Selection" id="selected" columns="1"> 
                <apex:pageBlock title="Selected {!$ObjectType.Product2.LabelPlural}" id="selected">
                <apex:pageblockTable value="{!shoppingCart2}" var="s">
                
                    <apex:column >
                        <apex:commandLink value="Remove" action="{!removeFromShoppingCart}" reRender="selected,searchResults" immediate="true">
                            <!-- this param is how we send an argument to the controller, so it knows which row we clicked 'remove' on -->
                            <apex:param value="{!s.pbeEntry.Id}" assignTo="{!toUnselect}" name="toUnselect"/>
                        </apex:commandLink>
                    </apex:column>
                    
                    <apex:column headerValue="{!$ObjectType.Product2.LabelPlural}" value="{!s.pbeEntry.Product2.Name}"/>
                    
                    <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.Quantity.Label}">
                        <apex:inputText value="{!s.productQuantity}" style="width:70px" required="true" onkeyup="refreshTotals();"/>
                    </apex:column>
                        <!--   -->
             
                    <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.Description.Label}">
                        <apex:outputField value="{!s.pbeEntry.Product2.Family}"/>    
                    </apex:column>
                   
                </apex:pageblockTable>
         </apex:pageBlock>
...


This is the entire VF page:

<apex:form>
	<apex:pageBlock title="My Content">	
		<apex:outputPanel id="mainBody">
			<apex:pageBlockSection title="{!$ObjectType.Product2.LabelPlural} Selection" id="selected" columns="1"> 
                <apex:pageBlock title="Selected {!$ObjectType.Product2.LabelPlural}" id="selected">
                <apex:pageblockTable value="{!shoppingCart2}" var="s">
                
                    <apex:column >
                        <apex:commandLink value="Remove" action="{!removeFromShoppingCart}" reRender="selected,searchResults" immediate="true">
                            <!-- this param is how we send an argument to the controller, so it knows which row we clicked 'remove' on -->
                            <apex:param value="{!s.pbeEntry.Id}" assignTo="{!toUnselect}" name="toUnselect"/>
                        </apex:commandLink>
                    </apex:column>
                    
                    <apex:column headerValue="{!$ObjectType.Product2.LabelPlural}" value="{!s.pbeEntry.Product2.Name}"/>
                    
                    <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.Quantity.Label}">
                        <apex:inputText value="{!s.productQuantity}" style="width:70px" required="true" onkeyup="refreshTotals();"/>
                    </apex:column>
                        <!--   -->
             
                    <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.Description.Label}">
                        <apex:outputField value="{!s.pbeEntry.Product2.Family}"/>    
                    </apex:column>
                   
        </apex:pageblockTable>
    </apex:pageBlock>
    
		<!--this is the lower table: search bar and search results-->
		<apex:pageBlock >          
			<apex:outputPanel styleClass="search">
				Search for {!$ObjectType.Product2.LabelPlural}:
			</apex:outputPanel>

			<apex:actionRegion renderRegionOnly="false" immediate="true">
			
				<apex:actionFunction name="fetchResults" action="{!updateAvailableList}" reRender="searchResults" status="searchStatus"/>
				
				<!-- here we invoke the scripting to get out fancy 'no button' search bar to work -->
				<apex:inputText value="{!searchString}" onkeydown="if(event.keyCode==13){this.blur();}else{resetTimer();}" style="width:300px"/>
				&nbsp;&nbsp;
				<i>
					<!-- actionStatus component makes it easy to let the user know when a search is underway -->
					<apex:actionStatus id="searchStatus" startText="searching..." stopText=" "/>
				</i>
				
			</apex:actionRegion>
		
			<br/>
			<br/>
		
			<apex:outputPanel id="searchResults">
			
				<apex:pageBlockTable value="{!AvailableProducts}" var="a">
				
					<apex:column headerValue="{!$ObjectType.Product2.Fields.Name.Label}" value="{!a.Product2.Name}" />
					
					<apex:column headerValue="{!$ObjectType.Product2.Fields.Family.Label}" value="{!a.Product2.Family}"/>
					
					<apex:column headerValue="{!$ObjectType.Product2.Fields.Description.Label}" value="{!a.Product2.Description}"/>
					
					<apex:column >
						<!-- command button in a column... neato -->
						<apex:commandButton value="Select" action="{!addToShoppingCart}" reRender="selected,searchResults" immediate="true">
							<!-- again we use apex:param to be able to tell the controller which row we are working with -->
							<apex:param value="{!a.Id}" assignTo="{!toSelect}" name="toSelect"/>
						</apex:commandButton>
					</apex:column>
					
				</apex:pageBlockTable>
				
				<!-- We put up a warning if results exceed 100 rows -->
				<apex:outputPanel styleClass="fyi" rendered="{!overLimit}">
					<br/>
					Your search returned over 100 results, use a more specific search string if you do not see the desired {!$ObjectType.Product2.Label}.
					<br/>
				</apex:outputPanel>
				
				</apex:outputPanel>
				</apex:pageBlock>
			</apex:pageBlockSection>
		</apex:outputPanel>

		<apex:pageBlockSectioncolumns="1">
				   <apex:outputPanel layout="block" style="align: center">
			   <apex:commandButton action="{!onSave}" value="Save"/>
			</apex:outputPanel>

		</apex:pageBlockSection> 
		</apex:pageBlock>
    </apex:form>

</apex:page>





Best Answer chosen by MC2014
kaustav goswamikaustav goswami
Hi,

I think the actual problem is that the SELECT Link on the page has its immediate attribute set to true.

Please remove the immediate attribute and then try the code.

<apex:commandButton value="Select" action="{!addToShoppingCart}" reRender="selected,searchResults" > - No need of immediate attribute

Thanks,
Kaustav

All Answers

Matt WhalleyMatt Whalley
Any paticular reason for the constructor of the wrapper to be formed that way?  If I'm reading your code right, you are setting the quantity to 0 everytime.  Why not scale back the constructor to just assign the price book, and have the VF page determine the quantity.  Also, on save, you aren't really doing anything if that is your question.
kaustav goswamikaustav goswami
Hi,

I think the actual problem is that the SELECT Link on the page has its immediate attribute set to true.

Please remove the immediate attribute and then try the code.

<apex:commandButton value="Select" action="{!addToShoppingCart}" reRender="selected,searchResults" > - No need of immediate attribute

Thanks,
Kaustav
This was selected as the best answer
MC2014MC2014
Matt,

My first version of my form had multi select drop-down, so only way for me to get quantity and the price book id for all the items user have selected is to do a wrapper.
So if I were to just assign price book back in the constructor, how would I suppose to keep track of quantity they've enter?  
Yes, on save doesn't do anything because I remove the logic for this post because it is not related, other than to show that the information are in the wrapper variable.


Kaustav,
removed the immediate, hitting the select button won't add anything to the cart.

kaustav goswamikaustav goswami
I tried out the following code. It works in my dev org. Can you please try something like below

Controller:

public with sharing class InternalFurfillment2 {

    public String searchString {get;set;}

    private List<PriceBookWrapper> forDeletion2 = new List<PriceBookWrapper>();
    public List<PriceBookWrapper> shoppingCart2 {get;set;}
    
    public priceBookEntry[] AvailableProducts {get;set;}
    public String toSelect {get; set;}
    public String toUnselect {get; set;}
    public Boolean overLimit {get;set;}
    
    public string priceBookName = 'Internal Price Book';
    
    private ApexPages.StandardController stdController;    
    private Case caseObject;
      
    public InternalFurfillment2(ApexPages.StandardController controller) {
        this.caseObject = (Case) controller.getRecord();  
        shoppingCart2 = new List<PriceBookWrapper>();
        

        updateAvailableList();       
    }

    public void updateAvailableList() {
        
        // We dynamically build a query string and exclude items already in the shopping cart
        String qString = 'select Id, Pricebook2Id, IsActive, Product2.Name, Product2.Family, Product2.IsActive, Product2.Description, UnitPrice, Product2.Vendor__c from PricebookEntry where IsActive=true and PricebookEntry.Pricebook2.Name = \'' + priceBookName + '\'';
        system.debug('qString begin==>' + qString);
        // note that we are looking for the search string entered by the user in the name OR description
        // modify this to search other fields if desired
        if(searchString!=null){
            qString+= ' and (Product2.Name like \'%' + searchString + '%\' or Product2.Description like \'%' + searchString + '%\')';
        }
        
        Set<Id> selectedEntries = new Set<Id>();
        
        for(PriceBookWrapper d : shoppingCart2){
            system.debug('d==>' + d);
            selectedEntries.add(d.pbeEntry.Id);
        }
        system.debug('selectedEntries==>' + selectedEntries);
               
        if(selectedEntries.size()>0){
            String tempFilter = ' and Id not in (';
            for(Id i : selectedEntries){
                tempFilter+= '\'' + (String)i + '\',';
            }
            String extraFilter = tempFilter.substring(0,tempFilter.length()-1);
            extraFilter+= ')';
            
            qString+= extraFilter;
        }
        
        qString+= ' order by Product2.Name';
        qString+= ' limit 101';
        
        system.debug('qString:' +qString);        
        AvailableProducts = database.query(qString);
        
        // We only display up to 100 results... if there are more than we let the user know (see vf page)
        if(AvailableProducts.size()==101){
            AvailableProducts.remove(100);
            overLimit = true;
        }
        else{
            overLimit=false;
        }
    }
    
    public void addToShoppingCart(){
        system.debug('AvailableProducts==>' + AvailableProducts);
        system.debug('shopping cart outside top==>' + ShoppingCart2); 
        // This function runs when a user hits "select" button next to a product    
        for(PricebookEntry d : AvailableProducts){
            System.debug('#### toSelect value #### ' + toSelect);
            if((String)d.Id==toSelect){
                shoppingCart2.add(new PriceBookWrapper(d,0)); 
                system.debug('shopping cart inside==>' + ShoppingCart2);
                break;
            }
        }
        system.debug('shopping cart outside==>' + ShoppingCart2);       
        updateAvailableList();  
    }
    

    public PageReference removeFromShoppingCart(){    
        // This function runs when a user hits "remove" on an item in the "Selected Products" section            
        Integer count = 0;    
        for(PriceBookWrapper d : shoppingCart2){
            if((String)d.pbeEntry.Id==toUnselect){            
                if(d.pbeEntry.Id!=null)
                    forDeletion2.add(d);            
                shoppingCart2.remove(count);
                break;
            }
            count++;
        }        
        updateAvailableList();       
        return null;
    }
    
    public PageReference onSave(){        
        system.debug('shoppingCart2 submit==>' + shoppingCart2);
               return null;
    }
         
     public class PriceBookWrapper{  
         public Integer productQuantity {get;set;}  {productQuantity = 0;}
         public PriceBookEntry pbeEntry {get;set;}  
         
         public PriceBookWrapper(PriceBookEntry pbeEntry, Integer productQuantity){  
             this.pbeEntry = pbeEntry;  
             this.productQuantity = productQuantity; 
         }      
    }  
}

VF Page:

<apex:page standardController="Case" extensions="InternalFurfillment2">
<apex:form >
    <apex:pageBlock title="My Content"> 
        <apex:outputPanel id="mainBody">
            <apex:pageBlockSection title="{!$ObjectType.Product2.LabelPlural} Selection" id="selected" columns="1"> 
                <apex:pageBlock title="Selected {!$ObjectType.Product2.LabelPlural}" id="selected">
                <apex:pageblockTable value="{!shoppingCart2}" var="s">
                
                    <apex:column >
                        <apex:commandLink value="Remove" action="{!removeFromShoppingCart}" reRender="selected,searchResults" immediate="true">
                            <!-- this param is how we send an argument to the controller, so it knows which row we clicked 'remove' on -->
                            <apex:param value="{!s.pbeEntry.Id}" assignTo="{!toUnselect}" name="toUnselect"/>
                        </apex:commandLink>
                    </apex:column>
                    
                    <apex:column headerValue="{!$ObjectType.Product2.LabelPlural}" value="{!s.pbeEntry.Product2.Name}"/>
                    
                    <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.Quantity.Label}">
                        <apex:inputText value="{!s.productQuantity}" style="width:70px" required="true" />
                    </apex:column>
                        <!--   -->
             
                    <apex:column headerValue="{!$ObjectType.OpportunityLineItem.Fields.Description.Label}">
                        <apex:outputField value="{!s.pbeEntry.Product2.Family}"/>    
                    </apex:column>
                   
        </apex:pageblockTable>
    </apex:pageBlock>
    
        <!--this is the lower table: search bar and search results-->
        <apex:pageBlock >          
            <apex:outputPanel styleClass="search">
                Search for {!$ObjectType.Product2.LabelPlural}:
            </apex:outputPanel>

            <apex:actionRegion renderRegionOnly="false" immediate="true">
            
                <apex:actionFunction name="fetchResults" action="{!updateAvailableList}" reRender="searchResults" status="searchStatus"/>
                
                <!-- here we invoke the scripting to get out fancy 'no button' search bar to work -->
                <apex:inputText value="{!searchString}" onkeydown="if(event.keyCode==13){this.blur();}else{resetTimer();}" style="width:300px"/>
                &nbsp;&nbsp;
                <i>
                    <!-- actionStatus component makes it easy to let the user know when a search is underway -->
                    <apex:actionStatus id="searchStatus" startText="searching..." stopText=" "/>
                </i>
                
            </apex:actionRegion>
        
            <br/>
            <br/>
        
            <apex:outputPanel id="searchResults">
            
                <apex:pageBlockTable value="{!AvailableProducts}" var="a">
                
                    <apex:column headerValue="{!$ObjectType.Product2.Fields.Name.Label}" value="{!a.Product2.Name}" />
                    
                    <apex:column headerValue="{!$ObjectType.Product2.Fields.Family.Label}" value="{!a.Product2.Family}"/>
                    
                    <apex:column headerValue="{!$ObjectType.Product2.Fields.Description.Label}" value="{!a.Product2.Description}"/>
                    
                    <apex:column >
                        <!-- command button in a column... neato -->
                        <apex:commandButton value="Select" action="{!addToShoppingCart}" reRender="selected,searchResults" >
                            <!-- again we use apex:param to be able to tell the controller which row we are working with -->
                            <apex:param value="{!a.Id}" assignTo="{!toSelect}" name="toSelect"/>
                        </apex:commandButton>
                    </apex:column>
                    
                </apex:pageBlockTable>
                
                <!-- We put up a warning if results exceed 100 rows -->
                <apex:outputPanel styleClass="fyi" rendered="{!overLimit}">
                    <br/>
                    Your search returned over 100 results, use a more specific search string if you do not see the desired {!$ObjectType.Product2.Label}.
                    <br/>
                </apex:outputPanel>
                
                </apex:outputPanel>
                </apex:pageBlock>
            </apex:pageBlockSection>
        </apex:outputPanel>

        <apex:pageBlockSection columns="1">
                   <apex:outputPanel layout="block" style="align: center">
               <apex:commandButton action="{!onSave}" value="Save"/>
            </apex:outputPanel>

        </apex:pageBlockSection> 
        </apex:pageBlock>
    </apex:form>

</apex:page>

Please let us know if it works.

Thanks,
Kaustav
MC2014MC2014
Kaustav,

Your original post does resolve this, its just that I had a require field some where on the form that I had missed.

Thanks.
Gaurav NirwalGaurav Nirwal
plese check this line to resolve the problem 
<apex:commandButton value="Select" action="{!addToShoppingCart}" reRender="selected,searchResults" > - No need of immediate attribute
Gaurav NirwalGaurav Nirwal
the value can be taken in apex command button does not correct the attribute can gives the wrong value