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
hemmhemm 

inputText not binding to my wrapper class

I have a VF page with Controller Extension that very much resembles the example at http://wiki.apexdevnet.com/index.php/Wrapper_Class.  I had it all working great as it resembles that class.

I received a new requirement to add a String field into the wrapper and to add an inputText field onto the VF page.  So I added a new "public String problemDescription {get; set;}"  property into the wrapper class.  I then added an inputText field with a value attribute of that field, allowing the user to enter a value, process the records and use that input value in the Apex method.

For some reason, that field in the wrapper class is not picking up the value entered into the VF page.  If I hardcode a value as a default in the wrapper class constructor, that value will appear in my VF page.  However, if I overwrite that value in the page, the controller will not pick up on the new value.  It only uses the initial value.

The strange thing is that I have an inputCheckbox on my page bound to the selected property of the wrapper class and the controller picks up the value set on the page.  For some reason, it won't pickup my new value.  Ideas?

I can post the code if my explanation doesn't make sense, but I thought I'd keep it as simple as possible and ask people to start with that example on the wiki and try to add a String to the wrapper class and get the controller to read the values submitted from the page.
JimRaeJimRae
Where are you expecting to see the updated value? Be careful you don't expect it in the contact record, since it doesn't exist there.  I added a new string element to my wrapper, not part of the contact itself (like the selected boolean), and an inputtext field to capture it.
If you look at the debug log when you execute the process selected, you can see any data you put in the contactList  wrapper item.

Here is my version of the sample:

Page:
Code:
<apex:page controller="wrapperClassController">
    <apex:form >
        <apex:pageBlock >
            <apex:pageBlockButtons >
                <apex:commandButton value="Process Selected" action="{!processSelected}" rerender="table"/>
            </apex:pageBlockButtons>
            <!-- In our table we are displaying the cContact records -->
            <apex:pageBlockTable value="{!contacts}" var="c" id="table" >
                <apex:column >
                    <!-- This is our selected Boolean property in our wrapper class -->
                    <apex:inputCheckbox value="{!c.selected}"/>
                </apex:column>
                <!-- This is how we access the contact values within our cContact container/wrapper -->
                <apex:column value="{!c.con.Name}" />
                <apex:column value="{!c.con.Email}" />
                <apex:column value="{!c.con.Phone}" />
                 <apex:column >
                 <apex:facet name="header"> 
                 Problem Description
                 </apex:facet>
                    <apex:inputtext value="{!c.ProblemDescription}" />
                </apex:column>
            </apex:pageBlockTable>
        </apex:pageBlock>
    </apex:form>
</apex:page>

 
Controller:
Code:
public class wrapperClassController {

    //Our collection of the class/wrapper objects cContact 
    public List<cContact> contactList {get; set;}
    
    //This method uses a simple SOQL query to return a List of Contacts
    public List<cContact> getContacts(){
        if(contactList == null){
            contactList = new List<cContact>();
        
            for(Contact c : [select Id, Name, Email, Phone from Contact limit 10]){
                /* As each contact is processed we create a new cContact object and
                add it to the contactList */
                contactList.add(new cContact(c));
            }
        }
        return contactList;
    }
    
    public PageReference processSelected(){
        /*We create a new list of Contacts that we be populated only with Contacts
        if they are selected*/
        List<Contact> selectedContacts = new List<Contact>();
        
        /*We will cycle through our list of cContacts and will check to see if the 
        selected property is set to true, if it is we add the Contact to the 
        selectedContacts list. */
        for(cContact cCon : getContacts()){
            if(cCon.selected == true){
                selectedContacts.add(cCon.con);
            }
        }
        
        /* Now we have our list of selected contacts and can perform any type of 
        logic we want, sending emails, updating a field on the Contact, etc */
        System.debug('These are the selected Contacts...');
        for(Contact con : selectedContacts){
            system.debug(con);
        }
        for(cContact ccon : contactList){
            System.debug('\n\nTest CONTACTLIST:'+ccon);
        }
        return null;
    }
    
    /* This is our wrapper/container class. A container class is a class, a data 
    structure, or an abstract data type whose instances are collections of other 
    objects. In this example a wrapper class contains both the standard salesforce 
    object Contact and a Boolean value */
    public class cContact{
        public Contact con {get; set;}
        public Boolean selected {get; set;}
        public String ProblemDescription {get; set;}
        
        /*This is the contructor method. When we create a new cContact object we pass a 
        Contact that is set to the con property. We also set the selected value to false*/
        public cContact(Contact c){
            con = c;
            selected = false;
        }
    }
}

 

iceberg4uiceberg4u
Sometimes there are binding issues which usually get resolved when you use java style getter and setter instead of the .NET one

public void setx(String val)
{
            this.x = val;
}

public String getx()
{
         return x;
}
hemmhemm
This is killing me.  I can't figure it out!  I've had many people look at it and say it looks right.  My code is below.  I have gone through a few iterations of changing it between a Controller vs Controller Extension, having a getHardware method (analogous to the getContacts function in the sample posted) vs defaulting it in the constructor and using a custom constructor to populate the wrapper class vs. creating a new object and setting the properties one by one. Any help is appreciated. 

My real use case is that my client uses a Hardware__c object linked to Contracts.  Cases can be associated to Contracts.  There is a Service_Request_Hardware__c object that will hold the Hardware__c items being supported on a specific Case.  My VF page is intended to make it easy for people to choose the Hardware__c records from the associated Contract and automatically create Service_Request_Hardware__c records when clicking Save.  It's been working up until my latest requirement.

For my latest requirement, I was asked to add a problem_description__c field onto the Service_Request_Hardware__c custom object that should be populated from the page.  I am unable to get the problem description field populated in the wrapper object.  The selected property gets populated without issue.  It just refuses to take the value in the page for the problemdescription field.

Below is my code.

FYI - that sample wrapper works just fine in my org.

Apex
Code:
public class VFController_caseAddSRHardware {

/*******************
* Variables
*******************/
// The Case the page is on
public Case c {get; private set;}

// List of Hardware associated to the Contract using the wrapper class
public List<cHardware> cHardwareList {get; set;}

// Count of items in the wrapper class
public Integer cHardwareListCount{get; private set;}

/*******************
* Classes
*******************/
// Wrapper class that includes the Hardware associated to a Contract plus another field to indicate whether it's been selected
public class cHardware {

// Class variables
public Boolean selected {get; set;}
public Boolean exists {get; set;}
public Hardware__c hw {get; set;}
public String pd;

// Default Constructor
cHardware(){}

// Constuctor setting all variables
cHardware(Boolean bSelected, Boolean bExists, Hardware__c theHW, String spd) {
selected = bSelected;
exists = bExists;
pd= spd;
hw = theHW;
}

// Getters and Setters
public String getPd() {return pd;}
public void setPd(String val) {this.pd = val;}

}

/*******************
* Methods
*******************/
// Constructor for the page
public VFController_caseAddSRHardware() {

// Get the case object. Need to query all used fields since we are using a custom controller
c = [select id, casenumber, contract__c, contract__r.contractnumber from Case where id =:ApexPages.currentPage().getParameters().get('id')];

// Reinit this variable. Same as variable above.
cHardwareList = new List<cHardware>();

// Get a set of Hardware IDs already associated with the Case
Set<ID> srHardwareIDSet = srHardwareSet();

// Get all hardware associated with the Contract and compare it to what's been associated with the Case already. Set the selected property accordingly
for (Hardware__c theHW : [select id, contract__c, name, manufacturer__c, model__c, serial_number__c
from Hardware__c
where contract__c = :c.contract__c]) {

if (srHardwareIDSet.isEmpty()) {
// Add an unchecked item if no hardware is on the case at all
cHardwareList.add(new cHardware(false,false,theHW,''));
} else {
if (srHardwareIDSet.contains(theHW.id)) {
// Add a checked item if the hardware is already on the case;
cHardwareList.add(new cHardware(true,true,theHW,[select problem_description__c
from Service_Request_Hardware__c
where case__c = :c.id and hardware__c = :theHW.id LIMIT 1][0].problem_description__c));
} else {
// Add an unchecked item if the hardware is not on the case
cHardwareList.add(new cHardware(false,false,theHW,''));
}
}
}
System.debug('\n\nConstructor Called');
for(cHardware chw : cHardwareList){
System.debug('\n\nTest HARDWARE LIST:'+chw);
}
cHardwareListCount = cHardwareList.size();

}

// Creates a Set of Hardware IDs that are already associated with the Case
private Set<ID> srHardwareSet() {
Set<ID> srHardwareIDSet = new Set<ID>();

for (Service_Request_Hardware__c theSRH : [select id, hardware__c, problem_description__c from Service_Request_Hardware__c where case__c = :c.id]) {
srHardwareIDSet.add(theSRH.Hardware__c);
}

return srHardwareIDSet;

}

// Process the selected Hardware items and associate them to the Case
public PageReference processSelected(){

// The list of Service Request Hardware records to insert
List<Service_Request_Hardware__c> srHardwareForInsert = new List<Service_Request_Hardware__c>();

// Loop through the cHardware list from the page
for(cHardware chw : cHardwareList){
// If the item is selected and has not yet been added to the Case, add it to srHardwareForInsert list
if(chw.selected == true && chw.exists == false){
srHardwareForInsert.add(new Service_Request_Hardware__c
(
case__c = c.id,
hardware__c = chw.hw.id,
manufacturer__c = chw.hw.manufacturer__c,
model__c = chw.hw.model__c,
serial_number__c = chw.hw.serial_number__c,
problem_description__c = chw.pd
)
);
}

}

System.debug('These are the selected Items...');
for(Service_Request_Hardware__c srh : srHardwareForInsert){
system.debug(srh);
}
for(cHardware chw : cHardwareList){
System.debug('\n\nTest HARDWARE LIST:'+chw);
}


// Insert any new records
if (srHardwareForInsert.size() > 0) {
insert srHardwareForInsert;
}

return null;

/***
// Send the user back to the case
PageReference casePage = new PageReference('/' + c.id);
casePage.setRedirect(true);
return casePage;
*/
}

// Process the selected Hardware items and associate them to the Case
public PageReference cancel(){

// Send the user back to the case
PageReference casePage = new PageReference('/' + c.id);
casePage.setRedirect(true);
return casePage;

}

}

 

VF

Code:
<apex:page controller="VFController_caseAddSRHardware" tabStyle="Case">
<apex:form >
<apex:sectionHeader title="Add Service Request Hardware to Case #{!c.CaseNumber}" />
<apex:messages />
<!-- Page Block if Contract and it has Hardware -->
<apex:pageBlock id="contractwithhardware" title="Add Service Request Hardware from Contract" rendered="{! AND(NOT(ISNULL(c.Contract__c)), cHardwareListCount > 0) }">
<apex:pageBlockButtons location="top">
<apex:commandButton action="{!processSelected}" value="Save" />
<apex:commandButton action="{!cancel}" value="Cancel"/>
</apex:pageBlockButtons>
<apex:pageBlockTable value="{!cHardwareList}" var="chw">
<apex:column rendered="{!NOT(chw.selected)}">
<apex:inputCheckbox value="{!chw.selected}"/>
</apex:column>
<apex:column rendered="{!(chw.selected)}">
<apex:image value="/img/checkbox_checked.gif" alt="selected"></apex:image>
</apex:column>
<apex:column value="{!chw.hw.name}" />
<apex:column value="{!chw.hw.Manufacturer__c}" />
<apex:column value="{!chw.hw.Model__c}" />
<apex:column value="{!chw.hw.Serial_Number__c}" />
<apex:column rendered="{!NOT(chw.selected)}" headerValue="Problem Description">
<apex:inputText value="{!chw.pd}" />
</apex:column>
<apex:column rendered="{!(chw.selected)}" headerValue="Problem Description">
<apex:outputText value="{!chw.pd}"/>
</apex:column>
</apex:pageBlockTable>
</apex:pageBlock>

<!-- Page Block if Contract has no Hardware -->
<apex:pageblock id="contractnohardware" title="Add Service Request Hardware from Contract" rendered="{! AND(NOT(ISNULL(c.Contract__c)), cHardwareListCount = 0)}">
<apex:pageBlockButtons location="top">
<apex:commandButton action="{!cancel}" value="Return to Case"/>
</apex:pageBlockButtons>
<apex:outputText value="Contract #"/>
<apex:outputLink value="/{!c.Contract__r.Id}" target="_blank">{!c.Contract__r.ContractNumber}</apex:outputLink>
<apex:outputText value=" has no hardware."/>
</apex:pageBlock>

<!-- Page Block if no Contract -->
<apex:pageblock id="nocontract" title="Add Service Request Hardware from Contract" rendered="{! ISNULL(c.Contract__c) }">
<apex:pageBlockButtons location="top">
<apex:commandButton action="{!cancel}" value="Return to Case"/>
</apex:pageBlockButtons>
<apex:outputText value="A Contract must be associated with the Case in order to use this functionality" />
</apex:pageBlock>
</apex:form>
</apex:page>

 

TehNrdTehNrd
Try making these tweaks:

Code:
Leave pd as a property and remove the get/set methods. It shouldn't make a difference but it is cleaner:

public String pd {get; set;}

Change this:

<apex:column rendered="{!NOT(chw.selected)}" headerValue="Problem Description">
<apex:inputText value="{!chw.pd}" />
</apex:column>
<apex:column rendered="{!(chw.selected)}" headerValue="Problem Description">
<apex:outputText value="{!chw.pd}"/>
</apex:column>

To this:

<apex:column headerValue="Problem Description">
<apex:inputText value="{!chw.pd}" rendered="{!NOT(chw.selected)}" />
<apex:outputText value="{!chw.pd}" rendered="{!(chw.selected)}"/>
</apex:column>



I'm not positive these will work but give it a try.

-Jason



Message Edited by TehNrd on 12-22-2008 11:56 AM
hemmhemm
Thanks.  I switched the class back to

Code:
 public class cHardware {
        
        // Class variables
        public Boolean selected {get; set;}
        public Boolean exists {get; set;}
        public Hardware__c hw {get; set;}
        public String pd {get; set;}
        
        // Default Constructor
        cHardware(){}
        
        // Constuctor setting all variables
        cHardware(Boolean bSelected, Boolean bExists, Hardware__c theHW, String spd) {
            selected = bSelected;
            exists = bExists;
            pd= spd;
            hw = theHW;
        }
        
    }

 and the VF page to

Code:
<apex:column>
                <apex:facet name="header">Problem Description</apex:facet>
                <apex:inputText value="{!chw.pd}" rendered="{!NOT(chw.selected)}" />
                <apex:outputText value="{!chw.pd}" rendered="{!(chw.selected)}"/>
            </apex:column>

 
Same result :(
hemmhemm
What I've found is that the only time it NULLS the text field out is when I check a box on the record.  If I enter something into the text field on all 4 records on my page, but only check the box (bound to the "selected" property) on 2 of them, the text field is NULL on the ones I checked and is set to whatever I type into the page on the other 2.  Something about setting the "selected" property to true is NULLing the text field.
hemmhemm
If I put the inputText column before the inputCheckbox column in the list, everything works correctly.  The inputText value is populated in the controller.  If I move the column after the inputCheckbox, the controller cannot get the inputText value. 

This has to be a bug in VisualForce.

The workaround to put the column before the checkbox makes the page look really bad.  It technically works, but it's kind of embarassing to tell my client that it's the only way I can get it to work.

Salesforce, what say you?
Colin LoretzColin Loretz
I'm having a similar issue and I'm starting to agree with Scott that it might be a visualforce bug.

The following code works perfectly if I remove the apex:SelectList from the form altogether. However adding the selectList either before or after the <apex:inputText> fields does not allow for the form to be saved. In fact, when the SelectList is present, neither of the commandButtons function.

The following is the VF code in question:
Code:
<apex:form >
 <table>
  <tr class = "header">
        <td>Projects</td>
        <td>Tasks</td>
        <td>Description</td>
        <td width = "50">Time</td>
       </tr>
  <apex:repeat value="{!mondayTime}" var="t">
   <tr>
    <td>
           <select>
            <option value="project1">Project 1</option>
      <option value="project2">Project 2</option>
      <option value="project3">Project 3</option>
      <option value="project4">Project 4</option>
           </select>
          </td>
          <td>
     <apex:selectList value="{!tasks}" size="1" multiselect="false">
          <apex:selectOptions value="{!items}"/>
        </apex:selectList>
          </td>
    <td>Desc: <apex:inputText value="{!t.description}" /></td>
    <td>Hours: <apex:inputText value="{!t.hours}" /></td>
   </tr>
  </apex:repeat>
  <tr>
   <td>&nbsp;</td>
   <td>&nbsp;</td>
   <td><apex:commandButton action="{!addEntry}" value="add row" /></td>
   <td><apex:commandButton action="{!saveMondayTime}" value="save" /></td>
  </tr>
 </table>
</apex:form>

Apex class:
Code:
public class wrapper {

 public class timeEntry {
 
  public String description { get; set;}
  public Double hours { get; set; }
  public Id task { get; set; }
  public Id project { get; set; }
 }
 
  timeEntry [] mondayTime = new timeEntry[]{};
 
 Public timeEntry [] getMondayTime()
 {
  return mondayTime;
 }
 
 public wrapper()
 {
  mondayTime.add(new timeEntry());
 }
 
 
 
 Public void saveMondayTime()
 {

  TimeSheetEntry__c [] entriesToSave = new TimeSheetEntry__c[]{};
   
//bucket hardcoded for testing purposes
TimeBucket__c bucket = [Select Id, Name from TimeBucket__c where Id = 'a0T80000000ROkn' LIMIT 1];

for(timeEntry entry : mondayTime)
{

TimeSheetEntry__c te = new TimeSheetEntry__c();
te.Time_Bucket__c = bucket.Id;
te.Time_Spent_in_Hours__c = double.valueOf(entry.hours);
te.Date__c = system.today();
te.Description__c = entry.description;

entriesToSave.add(te);
}


insert entriesToSave;

mondayTime.clear();
mondayTime.add(new timeEntry());

}


}

 
And finally a screenshot of the UI for those more visually inclined individuals:



 

dchasmandchasman
Can you please add in an apex:pageMessages component somewhere in your page and let us know what, if any, error messages are displayed when you attempt to save? Also, there appear to be some important details omitted from your example (e.g. where is the definition of the tasks property?) - can you please provide a complete, self contained, example instead of just the snippets? At first glance I suspect that the issue here is a raw HTML POST value conversion issue (we should see a failure message once you add in apex:pageMessages).


Message Edited by dchasman on 12-29-2008 09:34 AM
hemmhemm
In my code, I've had an <apex:messages /> tag in there the whole time.  No error is returned.
TehNrdTehNrd
I can't recall with <apex:messages> if it shows up automatically or not but you may need to wrap it in an <apex: outputPanel> and then rerender the panel with your action method.


Message Edited by TehNrd on 12-29-2008 08:46 AM
JimRaeJimRae

hemm wrote:
In my code, I've had an tag in there the whole time.  No error is returned.


I think Doug was suggesting the pagemessages tag.

The pagemessages message take will show any messages returned by the page, whereas the messages tag will only show for the selected component.
hemmhemm

JimRae wrote:

hemm wrote:
In my code, I've had an tag in there the whole time.  No error is returned.


I think Doug was suggesting the pagemessages tag.

The pagemessages message take will show any messages returned by the page, whereas the messages tag will only show for the selected component.


Thanks for clarifying.  I didn't catch that.  I added the <apex:pagemessages/> tag with the same result.  No messages.  The record is selected and saves just fine.  However, the inputText value is not captured.  The inputText IS captured, however, if I make it the first column in the table.
Colin LoretzColin Loretz
Doug,

I solved my issue. apex:messages did return an error regarding the SelectList.

The selectList was of not a multi-select list and the variable I was trying to store the selected value in was an array instead of a simple string. Once I changed that in the getter/setter methods, everything seems to be working now.

Thanks for pointing me in the right direction.

Message Edited by Colin Loretz on 12-29-2008 12:47 PM
hemmhemm
Got it!  Thanks to Colin for the help!

Now that I fixed it, I see what was happening, but it was really hard to track down.  The important parts of the page are:

Code:
<apex:column rendered="{!NOT(chw.selected)}">
<apex:inputCheckbox value="{!chw.selected}"/>
</apex:column>
<apex:column rendered="{!(chw.selected)}">
<apex:image value="/img/checkbox_checked.gif" alt="selected"></apex:image>
</apex:column>

<apex:column rendered="{!NOT(chw.selected)}" headerValue="Problem Description">
<apex:inputText value="{!chw.pd}" />
</apex:column>
<apex:column rendered="{!(chw.selected)}" headerValue="Problem Description">
<apex:outputText value="{!chw.pd}"/>
</apex:column>

 
I was rendering an inputText if the record had never been selected before and an outputText if it had.  Because that same field, "selected", is the one I was using in the page to select records, Visualforce was essentially treating my problemDescription value as an outputText the moment I checked the box.  If I had tried re-rendering the data table upon checking the selected box, I would've seen what was happening. 

To fix it, I render the Problem Description using the "exists" attribute instead.  This attribute is set to true upon page load for existing records and is not affected by anything happening in the page.

So it's NOT a Visualforce bug.  It was a change in properties that was occurring in memory, but not visually. 

The lesson learned is to use fields that are un-changable in the page for the rendered attribute.  If you do need to use a value that's changable in the rendered attribute, then you should re-render the dependent components upon changing that value.




Message Edited by hemm on 12-29-2008 02:03 PM
Patcs_1Patcs_1
Hi Colin

Thanks for your solution. Its saved me. :)

Thanks