Showing posts with label apex:dataTable. Show all posts
Showing posts with label apex:dataTable. Show all posts

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>

Friday, 30 July 2010

"Check All" checkbox in an apex:dataTable (without using scripts)

Recently, a business requirement emerged for a client whereby a user needed to be able to select a number of contacts from a data table, and then carry out some business logic on those selected clients. The process of selecting several contacts using a wrapper class is easy enough, but what about a simple way to select and deselect all of the records in one go? This can be achieved placing a checkbox in the header element of the column. I found an example solution that uses a javascript function embedded on the page to copy the true/false value of the header checkbox to all the boxes in the column.

This certainly does the job, but I found from a design perspective it had some limitations. The javascript function it calls gets the elements by tag, specifically the input tag, which may be used elsewhere if the page you are designing contains more than just this data table. Also, placing selection controls in scripts divides the selection logic between two entities; the page and the apex controller class.

With this in mind, I started to think about how I could develop an equivalent solution that uses logic inside the controller to change the state of all of the checkboxes. This is the solution I came up with:

Controller:
public class CheckboxCheckAllController {
 
 public List<ContactWrapper> allContacts { get; set; }
 public Boolean allChecked { get; set; }
 
 public CheckboxCheckAllController () {
  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(){
  
  for (ContactWrapper contactWrapper : allContacts ){
   if(contactWrapper.selected == true){
    /* Do Some Processing */
   }
  }
  
  return null;
 }

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

Page:
<apex:page controller="CheckboxCheckAllController">

  <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>

Page In Browser:


Admittedly, it provides a slightly slower response time than the pure javascript, but I think this provides benefits in singularity of location of control code and adaptability, as you can control the behaviour of the selection using array logic.