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
steve_andersensteve_andersen 

CampaignMember.Status picklist not limited to the Campaign I'm on

I'm creating a UI for updating CampaignMember Statuses. I get all the members of a Campaign to a list and then bind that list of CampaignMembers to a dataTable. When the inputFields show up on the page, the Status picklist isn't limited to Statuses on my current campaign.

Here's the relevant controller code:
public List<CampaignMember> getMembers() {
allMembers.clear();
allMembers = [Select Contact.FirstName, Contact.LastName, Status from CampaignMember where CampaignId=:currentCampaignId];
return allMembers;
}

 
And the page code:
<apex:dataTable value="{!members}" var="member" id="theTable" rowClasses="odd,even" styleClass="tableClass" cellpadding="4">
    <apex:column >
 <apex:facet name="header">Status</apex:facet>
 <apex:inputField value="{!member.Status}"/>
    </apex:column>
.
.
.

 Seems to me that the picklist should be limited to available Status variables, just like it is in the UI.

Barring a change to that behavior, how is this for a plan of attack?
  • get my campaignmembers and bind them to a dataTable
  • create a new SelectList in each row
  • get items with a SOQL to CampaignMemberStatus, pulling only Statuses for this Campaign
  • and when the user updates the campaign members, somehow get that selected value from each row and put it in member.status for each campaignmember
Is that right? How would I easily match up the object with the selected status? That's the part I'm worried about.

Thanks,

Steve



TehNrdTehNrd
"the Status picklist isn't limited to Statuses on my current campaign."

Do you use record types on the campaign to determine what statuses can be selected. If so I'm pretty sure Visualforce pages do not (yet?) support record types and all values for Status field will be shown.

Will your group of campaing members ever change? If not you can do this:

Code:
List<CampaignMember> allMembers;

public List<CampaignMember> getMembers() {
     if(allMembers == null){
  allMembers = [Select Contact.FirstName, Contact.LastName, Status from CampaignMember where CampaignId=:currentCampaignId]; }
return allMembers; }

This way you don't execute the query everytime the get method is called.

Then on you page build your table as you normally would. The user would be able to select the status value and I believe this databinding will be automatic.

Then you could have a command button that saves and does a DML update on allMembers.

steve_andersensteve_andersen
The problem is that each Campaign can have certain memberstatuses:

Campaign 1
a
b
c

Campaign 2
b
c
d
e

But when I look at the VF rendered picklist for CampaignMembers on Campaign 1, it shows:

a
b
c
d
e

d and e are invalid selections for Campaign 1, so I don't want them to show up in the picklist. I was hoping this would be standard functionality, but it appears to be a bug/feature.

Steve
TehNrdTehNrd
What is controlling this Status picklists? Is it record types, field dependency?

To be honest I'm super familiar with all of the campaign relationships so maybe I'm overlooking something.

Edit: I just looked at the Schema and yup, Campaign member status is definitely special. You appear to be correct when you say this is a bug/feature.



Message Edited by TehNrd on 06-25-2008 04:23 PM
steve_andersensteve_andersen
It's core campaign functionality. Each campaign can have statuses. They get stored collectively in CampaignMemberStatus object with the Campaign Id they relate to.

Steve
TehNrdTehNrd
Since I wasn't much help before :smileytongue: here is a potential work around. Basically you need to create a container that holds the campaign Member and then a custom pick list with the correct Statuses. I was almost able to completly finish it but I couldn't figure out hot to call a method in an outer class from an inner class.

Code:
Controller:

            ID currentCampaignId;
  cMember [] members; public List<SelectOption> memberStatuses; //------------Method that gets the campaign members and populates the cMember class/container------------- public List<cMember> getMembers(){ if(members == null){ members = new cMember[]{}; for(CampaignMember cm: [Select Contact.FirstName, Contact.LastName, Status from CampaignMember where CampaignId=:currentCampaignId]) { members.add( new cMember(cm) ); } } return members; } //-------------------Method to get the status picklist selections for the given campaign------------------- public List<SelectOption> getMemberStatuses(){ if(memberStatuses == null){ for(CampaignMemberStatus stat : [select ID, Label from CampaignMemberStatus where CampaignID = :currentCampaignId]){ memberStatuses.add(new SelectOption(stat.Label,stat.Label)); } } return memberStatuses; } //----------Container for the campaign member and the picklist-------- public class cMember { CampaignMember cm; public string cmStatus{get; set;} public cMember(CampaignMember cmb){ cm = cmb; cmStatus = cmb.Status; } public CampaignMember getCampaignMember(){ return cm; } public List<SelectOption> getStatuses(){ //This is the one part I couldn't figure out as I'm not sure how to call a method in an outer class from an inner class //List<SelectOption> options = getMemberStatuses(); List<SelectOption> options; return options; } } //-------Save or action method------------- public PageReference submit(){ List<CampaignMember> CampaignMemberUpdate = new List<CampaignMember >(); for(cMember mem: members){ mem.cm.Status = mem.cmStatus; CampaignMemberUpdate.add(mem.cm); } update CampaignMemberUpdate ; return null; }

Page:
Code:
<apex:dataTable value="{!members}" var="member" id="theTable" rowClasses="odd,even" styleClass="tableClass" cellpadding="4">
     <apex:column >
         <apex:facet name="header">First Name</apex:facet>
         <apex:outputField value="{!member.CampaignMember.Contact.FirstName}"/>
    </apex:column>
     <apex:column >
         <apex:facet name="header">Last Name</apex:facet>
         <apex:outputField value="{!member.CampaignMember.Contact.LastName}"/>
    </apex:column>
    <apex:column >
         <apex:facet name="header">Status</apex:facet>
         <apex:selectList value="{!member.cmStatus}" >
            <apex:selectOptions value="{!member.Statuses}"/>
         </apex:selectList>
    </apex:column>
 </apex:dataTable>

 

 

steve_andersensteve_andersen
Thanks for the code!

I'll give it a try and report back.

Steve
BritishBoyinDCBritishBoyinDC
Hi Steve

just run into the same problem - did you get some code to work for this?
BritishBoyinDCBritishBoyinDC
As an update, I was able to take this code and make it use a new static list that overrides the Campaign Member Status list you get back if you just query against Campaign Member. However, still trying to work out how to resolve the outer/inner class issue to use the status values from the actual campaign.

PS - if you are writing a test script, I recommend putting it in the same class as the controller, as I seemed to get problems with the inner/outer issue if I created a separate test class.

Also, the only way I could find to actually deploy the custom controller was to create the new apex page as an empty shell in Production, with just the default 'hello world' text, upload this new custom controller class, and then go back and update the page code. Otherwise the deployment fails, because the test script can't find the page to test, but you can't create the actual page until the custom controller class is present - a slightly bizarre catch-22 me thinks...
BritishBoyinDCBritishBoyinDC
So this seems to work, though I suspect it is not the most elegant way of doing it... you'll need to change some of the references to the pages I have hard coded in for ease of testing...

It includes a test class as well... again, I am not sure how effective the actual test is, but acheives the level of coverage needed to deploy...

Code:
public class cmember2 {

 cMember [] members;
      
  //------------Method that gets the campaign members and populates the cMember class/container-------------
 public List<cMember> getMembers(){
                if(members == null){
                    members = new cMember[]{};
                    for(CampaignMember cm: [Select Campaign.Name, ContactId, Contact.FirstName, Contact.LastName,Contact.Account.Name, Contact.Email, Status from CampaignMember where CampaignId=:System.currentpagereference().getParameters().get('id')order by Contact.LastName asc]) {
                        members.add( new cMember(cm) );   
                    }    
                
                                }              
                return members;
            }          

            //----------Container for the campaign member and the picklist--------
            public class cMember { 
             
                CampaignMember cm; 
                public List<SelectOption> cMemberStatuses;           
                public string cmStatus{get; set;} 
        
                public cMember(CampaignMember cmb){
                
                        cm = cmb;
                        cmStatus = cmb.Status;
                }
                
                public CampaignMember getCampaignMember(){ 
                        return cm; 
                }                      
               
                public List<SelectOption> getcMemberStatuses(){
                List<SelectOption> options = new List<SelectOption>();
                if(cmemberStatuses == null){
                    for(CampaignMemberStatus stat : [select ID, Label from CampaignMemberStatus where CampaignID = :System.currentpagereference().getParameters().get('id')]){
                        options.add(new SelectOption(stat.Label,stat.Label));
                    }
                }
                return options;
            }   
                                                                      
            } 
            
            //-------Save or action method-------------
            public PageReference save(){
                List<CampaignMember> CampaignMemberUpdate = new List<CampaignMember >();
                system.debug('in save');      
                for(cMember mem: members){
                        mem.cm.Status = mem.cmStatus;
                        CampaignMemberUpdate.add(mem.cm);
                }       
                update CampaignMemberUpdate ;                     
                members = null;
               members = getMembers();
          PageReference updatedcm = new PageReference('/apex/capcmupdate2—id=' + System.currentpagereference().getParameters().get('id') );
          return updatedcm;      
            }            
  public PageReference cancel() {
        PageReference returntoPage = new PageReference('/' + System.currentpagereference().getParameters().get('id'));
        return returntoPage;
        }
        
 public static testMethod void cmember2() {
        
Campaign newc = new Campaign(Name='Test One Campaign');
  insert newc;
  system.debug('Created Campaign: ' + newc.Id);

Contact newcon = new Contact (LastName='TestL', FirstName = 'TestF');
insert newcon;

CampaignMember newcm = new CampaignMember (ContactId=newcon.Id, CampaignId=newc.Id);
insert newcm;

PageReference pageRef = Page.capcmupdate2;
 Test.setCurrentPage(pageRef);  
     // Add parameters to page URL
  ApexPages.currentPage().getParameters().put('id', newc.Id);
  system.debug(ApexPages.currentPage().getURL());
  Test.setCurrentPage(pageRef);     
  
  // Instantiate a new controller with all parameters in the page     

  cmember2 cmember2 = new cmember2();
  cmember2.getmembers();
  List <SelectOption> Status = cmember2.members[0].getcMemberStatuses();   
  CampaignMember cm = cmember2.members[0].getCampaignMember();

  cmember2.members[0].cmStatus = 'Responded';
  
  String nextPage = cmember2.save().getUrl();
  System.assertEquals(ApexPages.currentPage().getURL(), nextPage);          
 
  CampaignMember[] checkcms = [select Id, Status from CampaignMember where CampaignId = :newc.Id and ContactId = :newcon.Id]; 
      
 System.assertEquals('Responded', checkcms[0].Status);
 String nextPage1 = cmember2.cancel().getUrl();
 System.assertEquals('/' + newc.Id, nextPage1);
          }
           
}

 Page Code here:

Code:
<apex:page controller="cmember2">
<apex:form >
<apex:pageBlock id="cmain" mode="edit">
   <apex:commandButton action="{!cancel}" value="Return to Campaign" id="returnButton"/>
    <apex:commandButton action="{!save}" value="Update Members" rerender="cStatus"/>
 <apex:pageBlockTable id="members" value="{!members}" var="member" cellPadding="4" border="0">
     <apex:column >
         <apex:facet name="header">First Name</apex:facet>
         <apex:outputField value="{!member.CampaignMember.Contact.FirstName}"/>
    </apex:column>
     <apex:column >
         <apex:facet name="header">Last Name</apex:facet>
     <apex:commandLink rerender="detail">{!member.CampaignMember.Contact.LastName}<apex:param name="cid" value="{!member.CampaignMember.ContactId}"/></apex:commandLink></apex:column>
         <apex:column >
         <apex:facet name="header">Organization</apex:facet>
         <apex:outputField value="{!member.CampaignMember.Contact.Account.Name}"/>
    </apex:column>
    
         <apex:column >
         <apex:facet name="header">Email</apex:facet>
         <apex:outputField value="{!member.CampaignMember.Contact.Email}"/>
    </apex:column>
    <apex:column >
         <apex:facet name="header">Current Status</apex:facet>
         <apex:outputField value="{!member.CampaignMember.Status}"> </apex:outputField>
    </apex:column>
    <apex:column >
         <apex:facet name="header">New Status</apex:facet>
         <apex:selectList size="1" value="{!member.cmStatus}" >
            <apex:selectOptions value="{!member.cMemberStatuses}"/>
         </apex:selectList>
    </apex:column>
  </apex:pageBlockTable>
 <apex:outputPanel id="detail"><apex:detail subject="{!$CurrentPage.parameters.cid}" relatedList="true" title="false"/></apex:outputPanel>

 </apex:pageBlock>
 <apex:pageBlock id="cmainfooter" mode="edit">
   <apex:commandButton action="{!cancel}" value="Return to Campaign" id="returnButton"/>
    <apex:commandButton action="{!save}" value="Update Members" rerender="cStatus"/>
 </apex:pageBlock>
 
 </apex:form>
</apex:page>

 


BritishBoyinDCBritishBoyinDC
As an update to anyone reading this post, use with caution - having tested it more thoroughly today, I am realizing that the code is very inefficient from a SOQL/DML point of view,
so once you have more than about 25 members on a campaign, it quickly begins to exceed the governor limits...
TehNrdTehNrd
Yes, that's because every time you create a new cMember you are running a SOQL query. With my code it will only run this query once but we need to figure out how to call a method in a outer class from an inner class.
BritishBoyinDCBritishBoyinDC
Well, this seems to work - I am able to see and update a list of 450+ members.

In the end, I was able to bring the Status SOQL/ List Creation back up into the main class. I made some changes to reduce the SOQL/DML calls, but I haven't made it robust enough to handle unlimited campaigns. It would also be nice if you could only bring back a certain number of contacts at a time, and then use some previous - next construct to move through them, but that's for another time. Also - there is a change to the page, so remember to update that...

Code:
public class cmember2 {

 cMember [] members;
 public List<SelectOption> cmemberStatuses;           

  //------------Method that gets the campaign members and populates the cMember class/container-------------
 public List<cMember> getMembers(){
                 
                if(members == null){
                    members = new cMember[]{};
               
                    for(CampaignMember [] cm: [Select Campaign.Name, ContactId, Contact.FirstName, Contact.LastName,Contact.Account.Name, Contact.Email, Status from CampaignMember where CampaignId=:System.currentpagereference().getParameters().get('id')order by Contact.LastName asc]) {
               for (Integer i = 0, j = 0; i < cm.size(); i++) {
           members.add( new cMember(cm[i]) );   
                    }    
                 }
                }              
                return members;
            }                 
             
            
//-------------------Method to get the status picklist selections for the given campaign-------------------
           public List<SelectOption> getcMemberStatuses(){
                List<SelectOption> options = new List<SelectOption>();
                if(cmemberStatuses == null){
                    for(CampaignMemberStatus stat : [select ID, Label from CampaignMemberStatus where CampaignID = :System.currentpagereference().getParameters().get('id')]){
                    options.add(new SelectOption(stat.Label,stat.Label));
                    }
                }
                return options;
            }   


            //----------Container for the campaign member and the picklist--------
            public class cMember { 
             
                CampaignMember cm; 
     //           public List<SelectOption> cMemberStatuses;           
                public string cmStatus{get; set;} 
        
                public cMember(CampaignMember cmb){
                
                        cm = cmb;
                        cmStatus = cmb.Status;
                            }
                
                public CampaignMember getCampaignMember(){ 
                        return cm; 
                }                                                                                            
            } 
            
            //-------Save or action method-------------
            public PageReference save(){
                List<CampaignMember> CampaignMemberUpdate = new List<CampaignMember >();
                system.debug('in save');      
                for(cMember mem: members){
                        If (mem.cm.Status != mem.cmStatus) {
                        mem.cm.Status = mem.cmStatus;
                        CampaignMemberUpdate.add(mem.cm);
                }       
                }
                system.debug('Before Update: DML Rows =' + Limits.getDMLRows());
                update CampaignMemberUpdate ; 
                system.debug('After Update: DML Rows =' + Limits.getDMLRows());                    
               members = null;
               members = getMembers();
          PageReference updatedcm = new PageReference('/apex/capcmupdate2—id=' + System.currentpagereference().getParameters().get('id') );
          return updatedcm;      
            }            
  public PageReference cancel() {
        PageReference returntoPage = new PageReference('/' + System.currentpagereference().getParameters().get('id'));
        return returntoPage;
        }
        
 public static testMethod void cmember2() {
        
Campaign newc = new Campaign(Name='Test One Campaign');
        insert newc;
        system.debug('Created Campaign: ' + newc.Id);

Contact[] newcon = new Contact[]{};
  for (integer i=0;i<200;i++) {
   newcon.add(new Contact(
   LastName='TestL' + i, 
   FirstName = 'TestF'
   )); 
  }
   insert newcon;
  
CampaignMember [] newcm = new CampaignMember []{};
  for (integer i=0;i<200;i++) {
   newcm.add(new CampaignMember (
   ContactId=newcon[i].Id, 
   CampaignId=newc.Id
   )); 
   }
   insert newcm;
  
Test.startTest();
system.debug('Starting Test: DML Rows =' + Limits.getDMLRows());
PageReference pageRef = Page.capcmupdate2;
 Test.setCurrentPage(pageRef);  
     // Add parameters to page URL
  ApexPages.currentPage().getParameters().put('id', newc.Id);
  system.debug(ApexPages.currentPage().getURL());
  Test.setCurrentPage(pageRef);     
  
  // Instantiate a new controller with all parameters in the page     

  cmember2 cmember2 = new cmember2();
  cmember2.getmembers();
  //List <SelectOption> Status = cmember2.members[0].getcMemberStatuses();        
  List <SelectOption> Status = cmember2.getcMemberStatuses();
  CampaignMember cm = cmember2.members[0].getCampaignMember();

  cmember2.members[0].cmStatus = 'Responded';
  
  String nextPage = cmember2.save().getUrl();
  System.assertEquals(ApexPages.currentPage().getURL(), nextPage);          
 
  CampaignMember[] checkcms = [select Id, Status from CampaignMember where CampaignId = :newc.Id and ContactId = :newcon[0].Id]; 
      
  System.assertEquals('Responded', checkcms[0].Status);
  String nextPage1 = cmember2.cancel().getUrl();
System.assertEquals('/' + newc.Id, nextPage1);
          }
           
}

Page here - see line in red for changes

Code:
<apex:page controller="cmember2">
<apex:form >
<apex:pageBlock id="cmain" mode="edit">
<apex:commandButton action="{!cancel}" value="Return to Campaign" id="returnButton"/>
<apex:commandButton action="{!save}" value="Update Members" rerender="cStatus"/>
<apex:pageBlockTable id="members" value="{!members}" var="member" cellPadding="4" border="0">
<apex:column >
<apex:facet name="header">First Name</apex:facet>
<apex:outputField value="{!member.CampaignMember.Contact.FirstName}"/>
</apex:column>
<apex:column >
<apex:facet name="header">Last Name</apex:facet>
<apex:commandLink rerender="detail">{!member.CampaignMember.Contact.LastName}<apex:param name="cid" value="{!member.CampaignMember.ContactId}"/></apex:commandLink></apex:column>
<apex:column >
<apex:facet name="header">Organization</apex:facet>
<apex:outputField value="{!member.CampaignMember.Contact.Account.Name}"/>
</apex:column>

<apex:column >
<apex:facet name="header">Email</apex:facet>
<apex:outputField value="{!member.CampaignMember.Contact.Email}"/>
</apex:column>
<apex:column >
<apex:facet name="header">Current Status</apex:facet>
<apex:outputField value="{!member.CampaignMember.Status}"> </apex:outputField>
</apex:column>
<apex:column >
<apex:facet name="header">New Status</apex:facet>
<apex:selectList size="1" value="{!member.cmStatus}" >
<apex:selectOptions value="{!cMemberStatuses}"/>
</apex:selectList>
</apex:column>
</apex:pageBlockTable>
<apex:outputPanel id="detail"><apex:detail subject="{!$CurrentPage.parameters.cid}" relatedList="true" title="false"/></apex:outputPanel>

</apex:pageBlock>
<apex:pageBlock id="cmainfooter" mode="edit">
<apex:commandButton action="{!cancel}" value="Return to Campaign" id="returnButton"/>
<apex:commandButton action="{!save}" value="Update Members" rerender="cStatus"/>
</apex:pageBlock>

</apex:form>
</apex:page>

 

 

TehNrdTehNrd
Nice. I didn't even think (obviously) of doing that but it looks like it should work.
TehNrdTehNrd
For your next and previous question this may help:

http://community.salesforce.com/sforce/board/message?board.id=Visualforce&thread.id=2034
BritishBoyinDCBritishBoyinDC

For anyone who comes across this post, here is an updated version of the code that we found works better for something like Event Registration - instead of waiting for a user to click save, it saves the changed record and refreshes the screen.

 

Controller:

 

 

public class cmember2 { cMember [] members; public List<SelectOption> cmemberStatuses; //------------Method that gets the campaign members and populates the cMember class/container------------- public List<cMember> getMembers(){ if(members == null){ members = new cMember[]{}; for(CampaignMember [] cm: [Select Campaign.Name, Contact.Press__c, ContactId, Contact.FirstName, Contact.LastName,Contact.Account.Name, Contact.Email, Status from CampaignMember where HasResponded = FALSE AND CampaignId=:System.currentpagereference().getParameters().get('id')order by Contact.LastName asc]) { for (Integer i = 0, j = 0; i < cm.size(); i++) { system.debug('Adding to Members'); members.add( new cMember(cm[i]) ); } } } return members; } //-------------------Method to get the status picklist selections for the given campaign------------------- public List<SelectOption> getcMemberStatuses(){ List<SelectOption> options = new List<SelectOption>(); if(cmemberStatuses == null){ for(CampaignMemberStatus stat : [select ID, Label from CampaignMemberStatus where CampaignID = :System.currentpagereference().getParameters().get('id')]){ options.add(new SelectOption(stat.Label,stat.Label)); } } return options; } //----------Container for the campaign member and the picklist-------- public class cMember { CampaignMember cm; // public List<SelectOption> cMemberStatuses; public string cmStatus{get; set;} public cMember(CampaignMember cmb){ cm = cmb; cmStatus = cmb.Status; } public CampaignMember getCampaignMember(){ return cm; } } //-------Save or action method------------- public PageReference save(){ List<CampaignMember> CampaignMemberUpdate = new List<CampaignMember >(); system.debug('in save'); for(cMember mem: members){ If (mem.cm.Status != mem.cmStatus) { mem.cm.Status = mem.cmStatus; CampaignMemberUpdate.add(mem.cm); } } update CampaignMemberUpdate ; members = null; members = getMembers(); PageReference updatedcm = new PageReference('/apex/capcmupdate2?id=' + System.currentpagereference().getParameters().get('id') ); updatedcm.setRedirect(true); return updatedcm; } public PageReference cancel() { PageReference returntoPage = new PageReference('/' + System.currentpagereference().getParameters().get('id')); return returntoPage; } public static testMethod void cmember2() { Campaign newc = new Campaign(Name='Test One Campaign'); insert newc; system.debug('Created Campaign: ' + newc.Id); Contact[] newcon = new Contact[]{}; for (integer i=0;i<200;i++) { newcon.add(new Contact( LastName='TestL' + i, FirstName = 'TestF' )); } insert newcon; CampaignMember [] newcm = new CampaignMember []{}; for (integer i=0;i<200;i++) { newcm.add(new CampaignMember ( ContactId=newcon[i].Id, CampaignId=newc.Id )); } insert newcm; Test.startTest(); system.debug('Starting Test: DML Rows =' + Limits.getDMLRows()); PageReference pageRef = Page.capcmupdate2; Test.setCurrentPage(pageRef); // Add parameters to page URL ApexPages.currentPage().getParameters().put('id', newc.Id); system.debug(ApexPages.currentPage().getURL()); Test.setCurrentPage(pageRef); // Instantiate a new controller with all parameters in the page cmember2 cmember2 = new cmember2(); cmember2.getmembers(); //List <SelectOption> Status = cmember2.members[0].getcMemberStatuses(); List <SelectOption> Status = cmember2.getcMemberStatuses(); CampaignMember cm = cmember2.members[0].getCampaignMember(); cmember2.members[0].cmStatus = 'Responded'; cmember2.save(); String nextPage = cmember2.save().getUrl(); System.assertEquals(ApexPages.currentPage().getURL(), nextPage); CampaignMember[] checkcms = [select Id, Status from CampaignMember where CampaignId = :newc.Id and ContactId = :newcon[0].Id]; System.assertEquals('Responded', checkcms[0].Status); String nextPage1 = cmember2.cancel().getUrl(); System.assertEquals('/' + newc.Id, nextPage1); } }

 Page:

 

 

<apex:page controller="cmember2"> <apex:form > <apex:pageBlock id="cmain" mode="edit"> <apex:commandButton action="{!cancel}" value="Return to Campaign" id="returnButton"/> <apex:pageBlockTable id="members" value="{!members}" var="member" cellPadding="4" border="0"> <apex:column > <apex:facet name="header">First Name</apex:facet> <apex:outputField value="{!member.CampaignMember.Contact.FirstName}"/> </apex:column> <apex:column > <apex:facet name="header">Last Name</apex:facet> <apex:commandLink rerender="detail">{!member.CampaignMember.Contact.LastName}<apex:param name="cid" value="{!member.CampaignMember.ContactId}"/></apex:commandLink></apex:column> <apex:column > <apex:facet name="header">Press</apex:facet> <apex:outputField value="{!member.CampaignMember.Contact.Press__c}"/> </apex:column> <apex:column > <apex:facet name="header">Organization</apex:facet> <apex:outputField value="{!member.CampaignMember.Contact.Account.Name}"/> </apex:column> <apex:column > <apex:facet name="header">Email</apex:facet> <apex:outputField value="{!member.CampaignMember.Contact.Email}"/> </apex:column> <apex:column > <apex:facet name="header">Current Status</apex:facet> <apex:outputField value="{!member.CampaignMember.Status}"> </apex:outputField> </apex:column> <apex:column > <apex:facet name="header">New Status</apex:facet> <apex:selectList size="1" value="{!member.cmStatus}" > <apex:actionSupport event="onchange" action="{!save}" rerender="member"/> <apex:actionSupport event="oncomplete"/> <apex:selectOptions value="{!cMemberStatuses}"/> </apex:selectList> </apex:column> </apex:pageBlockTable> <apex:outputPanel id="detail"><apex:detail subject="{!$CurrentPage.parameters.cid}" relatedList="true" title="false"/></apex:outputPanel> </apex:pageBlock> <apex:pageBlock id="cmainfooter" mode="edit"> </apex:pageBlock> </apex:form> </apex:page>