Tuesday, 22 March 2011

Working with Apex data tables: Confirming choices

In my previous post, I used Apex page messages to provide direct feedback from an action to users. If the action you are about to perform is particularly critical or hard to reverse, you may want to add some kind of confirmation mechanism. Of course we could simply use a javascript function on the page to produce a simple confirm pop up, but it is usually preferable to provide something more comprehensive. An effective way to do this in Salesforce is to create a two page Visualforce menu consisting of an action page and a confirmation page, where we can make sure the user is aware of the action they are about to perform.

I have built an example of such a menu based on the page and controller from my previous post. When the user selects contact records and clicks on "process selected contacts", they are taken to a confirmation page that displays the contacts that they have selected, and asks them to confirm that these are the records they wish to process. This confirmation page shares the same controller as the main selection page. They can choose to cancel, which will return them to the previous selection menu taking no action, or click confirm to accept the selections and then be returned to the selection menu. Here is a screenshot of the confirmation screen:



In terms of altering the controller code, I added two new actions to confirm and cancel processing the contact records. Users can be directed to a particular page by returning a page reference (of the form "Page.*Visualforce page name*") in the action, or null to remain on the same page. If no records are selected on the first page, then instead of being directed to the confirmation page, the user will remain on the selection page, and an error message will be displayed.

In order for menus like this to work, the two pages must share the same controller type. Also, ensure that all page references used to change the page do not have the redirect attribute set to true, otherwise the users selections will be lost as the controller is reset.

Below is the Apex controller and Visualforce page code for the example.

Controller:
public class WorkingWithApexDataTablesController {

 public List<ContactWrapper> allContacts { get; set; }
 public Boolean allChecked { get; set; }
 
 public WorkingWithApexDataTablesController () {
  allContacts = new List<ContactWrapper>();
  allChecked = false;
  
  for(Contact contact: [select Name, Title, Department, Email                                   from Contact ]){ 
   allContacts.add(new ContactWrapper(contact));
  } 
 }
 
 public PageReference CheckAll(){
  
  for(ContactWrapper contact : allContacts){
   contact.selected = allChecked;
  }
  
  return null;
 }
 
 public PageReference ProcessSelectedContacts(){
  
  // If at least one contact has been selected,
  // forward to confrimation page
  for (ContactWrapper contactWrapper : allContacts ){
   if(contactWrapper.selected == true){
    return Page.WorkingWithApexDataTablesConfirmation;
   }
  }  
  
  // if no contacts have been selected, write an error message
  Apexpages.addMessage(new ApexPages.Message (ApexPages.Severity.ERROR,                                               'No contacts selected'));
  return null;
 }

 public PageReference confirmSelectedContacts(){
   
  List<String> selectedContacts = new List<String>{};   

  for (ContactWrapper contactWrapper : allContacts ){
   if(contactWrapper.selected == true){
    selectedContacts.add(contactWrapper.con.Name);
   }
  }
  
  // otherwise, write a confirmation message 
  Apexpages.addMessage(new ApexPages.Message (
     ApexPages.Severity.CONFIRM, selectedContacts.size() + 
     ' contacts selected ' + selectedContacts));
  
  // If all contacts have been selected, write a warning message
  if(selectedContacts.size() == allContacts.size()){
   Apexpages.addMessage(new ApexPages.Message (
     ApexPages.Severity.WARNING, 'All contacts selected'));
  }
   
  return Page.WorkingWithApexDataTables;
 }
 
 public PageReference cancelConfirmSelectedContacts(){
   
  // If the users cancels the action at the confirmation screen, 
  // return them to the main screen with a cancellation message
  Apexpages.addMessage(new ApexPages.Message (
    ApexPages.Severity.CONFIRM, 'Action Cancelled'));   
   
  return Page.WorkingWithApexDataTables;
 }
 
 public List<Contact> getSelectedContactObjects(){
  List<Contact> selectedContactObjects = new List<Contact>{};
  
  for (ContactWrapper contactWrapper : allContacts ){
   if(contactWrapper.selected == true){
    selectedContactObjects.add(contactWrapper.con);
   }
  }
  
  return selectedContactObjects;
 }

 public class ContactWrapper {
  
  public Contact con{get; set;}
  public Boolean selected {get; set;}
        
  public ContactWrapper(Contact c){
   con = c;
   selected = false;
  }
 }
}

WorkingWithApexDataTables Page:
<apex:page controller="WorkingWithApexDataTablesController">
  
  <apex:sectionHeader title="Working With Apex Data Tables"/>
  
  <apex:pageMessages />
  
  <apex:form >

    <apex:dataTable value="{!allContacts}" var="c" id="contactsTable">
      <apex:column >
        <apex:facet name="header">
          <apex:inputCheckbox value="{!allChecked}">
            <apex:actionSupport event="onclick" action="{!CheckAll}"
                                rerender="contactsTable"/>
          </apex:inputCheckbox>
        </apex:facet>
        <apex:inputCheckbox value="{!c.selected}"/>
      </apex:column>
      <apex:column value="{!c.con.Name}" headervalue="Full Name"/>
      <apex:column value="{!c.con.Title}" headervalue="Title"/>
      <apex:column value="{!c.con.Department}" 
                   headervalue="Department"/>
      <apex:column value="{!c.con.Email}" headervalue="Email"/>
    </apex:dataTable>

    <apex:commandButton action="{!ProcessSelectedContacts}" 
                        value="Process Selected Contacts"/>

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

WorkingWithApexDataTablesConfirmation Page:
<apex:page controller="WorkingWithApexDataTablesController">
  
  <apex:sectionHeader                                                           title="Working With Apex Data Tables (Confirmation)"/>
  
  <apex:form >

    <apex:outputText value="You have selected the following contacts for                            processing, are you sure?"/>

    <apex:dataTable value="{!selectedContactObjects}" var="con"                             id="selectedContactsTable">
      <apex:column value="{!con.Name}" headervalue="Full Name"/>
      <apex:column value="{!con.Title}" headervalue="Title"/>
      <apex:column value="{!con.Department}" headervalue="Department"/>
      <apex:column value="{!con.Email}" headervalue="Email"/>
    </apex:dataTable>

    <apex:commandButton action="{!CancelConfirmSelectedContacts}" 
                        value="Cancel"/>

    <apex:commandButton action="{!ConfirmSelectedContacts}" 
                        value="Confirm"/>

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