[SalesForce] Help adding wrapper class in order to save selectlist

I have a visualforce page that I made that loads a table of Registrations (Object Registrant__c) for a particular Enrollee (a Contact on an object called Enrollment).

Then I created a Select list that is populated with a list of Graduation Requirements (Object Graduation_Requirements__c) that are related to the Enrollment that the Registration Contact is a part of.

The end goal is to allow the user to select which Graduation Requirement is equivalent to each of the Registrations the Contact has completed. Then it should store the Graduation Requirement SFID into a field on the Registration Object.

The problem now is that it will only save the value of the Select list from the last row in the table. I understand that a wrapper class may need to be applied somewhere but can't tell where or how.

The visual force page
enter image description here

The visual force code

<apex:page standardController="Registrant__c" recordSetVar="unused" sidebar="false" extensions="contactRegistrationList">

<apex:includeScript value="{!$Resource.UtilJS}" />
<apex:form >
  <apex:pageBlock >
    <apex:pageMessages />
    <apex:pageBlock >
      Note: All modifications made on the page will be lost if Return button is clicked without clicking the Save button first. 
    </apex:pageBlock>
    <apex:pageBlockButtons >
      <apex:commandButton value="Save" action="{!saveList}"/>
      <apex:commandButton value="Return" action="{!cancel}"/>
    </apex:pageBlockButtons>
    <apex:pageBlockTable value="{!Records}" var="a" id="table">
      <apex:column headerValue="Id" value="{!a.name}"/>
      <apex:column headerValue="Contact" value="{!a.Contact__c}"/>
      <apex:column headerValue="Event" value="{!a.Associated_Event__c}"/>
      <apex:column headerValue="Section" value="{!a.Section_del__c}"/>
      <apex:column headerValue="Grade" value="{!a.Grade__c}"/>
      <apex:column headerValue="Graduation Requirement Equivalent">
          <apex:selectList value="{!selectedGradReq}" multiselect="false" size="1">
            <apex:selectOptions value="{!gradReqs}"/>
          </apex:selectList>
      </apex:column>
      <apex:column headerValue="Enter amount to credit">
         <apex:inputField value="{!a.Licensure_Credit__c}"/>
      </apex:column>
    </apex:pageBlockTable>
  </apex:pageBlock>
</apex:form>
</apex:page>

And the Apex Class

public class contactRegistrationList { 

    public Registrant__c Registrant {get;set;}     

    public contactRegistrationList(ApexPages.StandardController controller) {
    }

    public List<Registrant__c> Records {get; set;}

    public String selectedGradReq{get;set;}

    public contactRegistrationList(ApexPages.StandardSetController controller){
        controller.AddFields(new List<String>{'Contact__c'});
        Registrant = (Registrant__c) controller.getRecord();     
        Records = new List<Registrant__c>();
        Records = [SELECT id,name,Contact__c,Associated_Event__c,Grade__c,Section_del__c,Licensure_Credit__c,Credited_Date__c,Registration_Date__c,Graduation_Requirement__c FROM Registrant__c WHERE Contact__c = :ApexPages.currentPage().getParameters().get('contactid')]; 
    }

    public List<selectOption> getGradReqs() {
        List<SelectOption> gradReqs=new List<SelectOption>();
        gradReqs.add(new SelectOption('0001', '--Select--'));
        List<Graduation_Requirement__c> gradreqsList=[SELECT Id, Name FROM Graduation_Requirement__c WHERE Enrollment__c =:ApexPages.currentPage().getParameters().get('returl')];
        for(Graduation_Requirement__c r:gradreqsList) {
            gradReqs.add(new SelectOption(r.ID,r.Name));
        }
        return gradReqs;
    }

    public PageReference saveList(){
       // update Records;
        for(Registrant__c r:Records){
          r.Graduation_Requirement__c = selectedGradReq;
        }

        update Records;
        String returnUrl = ApexPages.currentPage().getParameters().get('returl');
        PageReference reRend = new PageReference('/'+ returnUrl);
        reRend.setRedirect(true);
        return reRend;
    }
    public PageReference cancel(){
        String cancelUrl = ApexPages.currentPage().getParameters().get('returl');
        PageReference reRend = new PageReference('/'+ cancelUrl);
        reRend.setRedirect(true);
        return reRend;
    }
}

Best Answer

I've left the explanation of wrapper classes, and the examples using one to fit your requirement, but I do have to wonder, why don't you use something like:

<apex:selectList value="{!a.Graduation_Requirement__c}" multiselect="false" size="1">
    <apex:selectOptions value="{!gradReqs}"/>
</apex:selectList>

A wrapper class is usually a private inner class, which takes a record as in the constructor, and holds it there, with additional information. This allows you to use this extra data in your controller, or visualforce page.

As an example, lets say you are working on a simple page to select a number of records on this page. To do this without a wrapper class, you would need either a Map<Id, Boolean>, or add another field on the object, such as Visualforce_Selected__c. But what if you also want another field, such as a custom comment? You'll need another map, or another field, and so on.

To avoid this, you can add that extra data to the wrapper class. Below is a full code sample for this example, but lets focus on the important part here.

Each property can be added to this inner class, and will only exist on this data, which wraps a CustomObject__c. You can see it takes a CustomObject__c as a parameter to the constructor, and sets the property Selected, and sets the wrapped record to the record provided.

private class InnerWrapperClass {
    public CustomObject__c Record { get; set; }
    public Boolean Selected { get; set; } // Example Property 

    public InnerWrapperClass(CustomObject__c record) {
        this.Record = record; 
        this.Selected = false; 
    }

}

Using this class means you'll need to be specific about what data you want to operate on- you'll need to call someWrapperReference.Record.Some_Field__c as opposed to someObjectReference.Some_Field__c. You can see this in the visualforce snippet below:

<apex:repeat var="w" value="{!wrapperClass}">
    <apex:input value="{!w.Selected}">
    <apex:input value="{!w.Record.Name}">
</apex:repeat>

In the apex:repeat, both the property on the wrapper class (Selected), as well as a property on the wrapped sObejct (Record.Name) are referenced.


Apex Class

public class YourController {

    public List<InnerWrapperClass> wrapperClass { get; set; }

    public YourController() {
        wrapperClass = new List<InnerWrapperClass>();

        // Add records if you have them 

        List<CustomObject__c> customObjects = [SELECT Id, Name FROM CustomObject__c LIMIT 5];

        for (CustomObject__c c:customObjects) {
            wrapperClass.add(new InnerWrapperClass(c));
        }
    }

    public void Save() {
        List<CustomObject__c> recordsToSave = new List<CustomObject__c>();

        for (InnerWrapperClass w:wrapperClass) {
            if (w.Selected) {
                recordsToSave.add(w.Record);
            }
        }

        update recordsToSave;
    }

    private class InnerWrapperClass {
        public CustomObject__c Record { get; set; }
        public Boolean Selected { get; set; } // Example Property 

        public InnerWrapperClass(CustomObject__c record) {
            this.Record = record; 
            this.Selected = false; 
        }

    }

}

Visualforce

<apex:repeat var="w" value="{!wrapperClass}">
    <apex:input value="{!w.Selected}">
    <apex:input value="{!w.Record.Name}">
</apex:repeat>

To make this example applicable to you, use a List<SelectOption> inside your wrapper class, and provide the values in the constructor. It should look something like:

public InnerWrapperClass(List<SelectOption> options, SomeObject__c record) {
    // .... 
}

You'll need the properties:

public String SelectedOption { get; set; }
public List<SelectOptions> AvaliableOptions { get; set; }
public SomeObject__c Record { get; set; }

And your visualforce will look something like:

<apex:selectList value="{!wrapper.SelectedOption}" multiselect="false" size="1">
    <apex:selectOptions value="{!wrapper.AvaliableOptions}"/>
</apex:selectList>
<apex:column headerValue="Id" value="{!wrapper.Record.Name}"/>

And lastly, you'll have to change your save method:

for(InnerWrapperClass w:Wrappers){
    w.Record.Graduation_Requirement__c = w.SelectedOption;
}
Related Topic