Recipes by Category

App Distribution (2) Bundle logic, interface and services for distribution. App Logic (37) The Apex programming language, workflow and formulas for logic. Collaboration (6) The Salesforce Chatter collaboration platform. Database (29) Data persistence, reporting and analytics. Integration (33) Web Service APIs and toolkits for integration. Security (9) Platform, application and data security. Tools (4) Force.com tooling User Interface (36) Visualforce MVC and metadata-drive user interfaces. Web Sites (12) Public web sites and apps with optional user registration and login.
Beta Feedback
Cookbook Home » Selectable SObjects for Use with DataTables

Selectable SObjects for Use with DataTables

Post by Charles E Howard  (2012-04-13)

Status: Unverified
Level: advanced

Problem

You want to display a list of records in an Apex DataTable, with a checkbox next to each record that allows the user to select one (or many) of the records. You also want to display this type of list for more than one SObject type across your instance in the cleanest way possible, code-wise, without creating a separate Class for each SObject type.

Solution

The general idea of a selectable record is to create a custom Class that contains a SalesForce object of the desired type, and a Boolean value to indicate whether it's selected. The way to do this for a single SObject type could look something like this:

public class SelectableAsset 
{
	public Asset asset {get; set;}
	public Boolean selected {get; set;}
	
	public SelectableAsset(Asset ast)
	{
		asset = ast;
		selected = false;
	}
}
But, let's say, for example, that in various places within your instance you want to have additional selectable lists of other object types (this example includes Asset, Contact and Line_Item__c). A possible solution could be to create three separate classes with the same form as the class above; one for each of Assets, Contacts and Line_Item__c records. However, using polymorphism, with multiple constructors, and the fact that all objects within SalesForce are a type of SObject, you can condense all of this into one class:
public with sharing class SelectableSObject 
{
	private SObject record;
	public Boolean selected {get; set;}
	
        // Universal constructor for any SalesForce object type
	public SelectableSObject(SObject obj)
	{
		record = obj;
		selected = false;
	}
	
        // Getter for Asset
	public Asset getAsset()
	{
		return (Asset)record;
	}
	
        // Getter for Contact
	public Contact getContact()
	{
		return (Contact)record;
	}
	
        // Getter for Line_Item__c
	public Line_Item__c getLineItem()
	{
		return (Line_Item__c)record;
	}
}

Code Explanation

Each constructor takes an argument of a single record of one of the supported object types, which it assigns to the class's private SObject. To retrieve the record, the appropriate getter method is called, which casts the SObject as the desired object type, and returns the result. If you want to include support for additional objects in the future, you can simply add a getter method for each new object type.

Implementation

So far the class only provides a flexible container for a single record with a boolean value attached to indicate whether it's selected. To use these records in a data table Step 1 is to package the records to be displayed into a List of SelectableSObjects. The use case for the example code below is displaying a list of Line_Item__c records for use in adjusting the Line Items attached to an Invoice.
public List<SelectableSObject> availableLineItems {get; set;};

...

this.availableLineItems = new List<SelectableSObject> {};

// Look up the list of available Line_Item__c records at an Account
for(Line_Item__c item : [select Id, Name, RecordType.Name, Amount__c, Invoice__c,    
    Date__c from Line_Item__c 
    where Account__c = :this.accountId 
    and (Invoice__c = null or Invoice__c = :this.invoiceId)])
{
    // Put the Line_Item__c into a SelectableSObject
    SelectableSObject lineItem = new SelectableSObject(item);

    // See if this Line Item should start out being selected
    if(lineItem.getLineItem().Invoice__c == this.invoiceId)
        lineItem.selected = true;

    // Add the SelectableSObject to the list
    this.availableLineItems.add(lineItem);
}
To display the list of SObjects provided by the controller in a DataTable you would use VisualForce code something like this:
<apex:pageBlockSection title="Available Line Items" columns="1">
  <apex:dataTable id="selectLineItemsTable" value="{!availableLineItems}"  var="item" width="95%">
    <apex:column width="5%">
      <apex:facet name="header"/>
      <apex:inputCheckBox value="{!item.selected}"/>
    </apex:column>
    <apex:column width="25%">
      <apex:facet name="header">Date</apex:facet>
      <apex:outputText value="{!item.LineItem.Date__c}"/>
    </apex:column>
    <apex:column width="20%">
      <apex:facet name="header">Amount</apex:facet>
      <apex:outputText value="{!item.LineItem.Amount}"/>
    </apex:column>
    <apex:column width="20%">
      <apex:facet name="header">Item Type</apex:facet>
      <apex:outputText value="{!item.LineItem.RecordType.Name}"/>
    </apex:column>
    <apex:column width="20%">
      <apex:facet name="header">Item Name</apex:facet>
      <apex:outputText value="{!item.LineItem.Name}"/>
    </apex:column>
  </apex:dataTable>
</apex:pageBlockSection>
One handy thing about VisualForce code is that the same short hand that allows you to access a value in a controller that is returned by a method called getValue() by simply referencing 'Value' instead of 'getValue()' applies to the getter methods in the custom class. That's what allows us to access the underlying SObject record cast as a Line_Item__c in each row by just calling 'item.LineItem' instead of 'item.getLineItem()'.
Finally, at the point that you want to capture the selections/de-selections the user has made, you would call a method in the controller or extension that looks through the List of SelectableSObjects, determines which records are checked, and posts updates to records:
private List<Line_Item__c> lineItemsToUpdate = new List<Line_Item__c> {};

...

for(SelectableSObject item : availableLineItems)
{
    Line_Item__c liRecord = item.getLineItem();

    // Determine if the current Line Item is selected and link or clear the   
    // Invoice__c field
    if(item.selected)
        liRecord.Invoice__c = this.invoiceId;
    else
        liRecord.Invoice__c = null;

    // Add the current line item to a List to be updated
    lineItemsToUpdate.add(liRecord);
}

...

// Update the set of Line Items
Database.update(lineItemsToUpdate);

Discussion

Potential Problems

  • The Class contains a single constructor that puts any specifically typed object instance passed into it into a generic SObject instance, then returns the stored instance by casting back into a specific object type, depending on the getter that is called. This creates the possibility to do something like store and Asset record, then request it back as Contact. When I do this the compiler throws back an error once I try to reference a field that is not part of the Asset object, but doesn't return an error about the operation of going from Asset -> SObject -> Contact.
  • The Class needs to be updated to contain a getter method for each object that you want to use it with. It will give you a 'Method does not exist or incorrect signature' error if the getter is missing.

Variations and Improvements

  • If you want the DataTable to allow only one selection instead of many, you would update the apex:inputCheckBox component in the data table to include ActionSupport like this:

    Original

        <apex:inputCheckBox value="{!item.selected}"/>
    
    With ActionSupport
        <apex:inputCheckbox value="{!item.selected}">
            <apex:actionSupport event="onclick"
                action="{!adjustLineItemSelection}" 
                rerender="selectLineItemsTable"/>
        </apex:inputCheckbox>
    

    IMPORTANT: The controller or extension then needs an additional variable to store the current selection value, and a new method (adjustLineItemSelection in this case), that does the following:
    • 1. If the current selection variable has a value, iterate through the list of selectable items, and set the matching item's selected value to false
    • 2. Iterate through the list a second time to see if there is still another value selected and, if there is, set the current selection variable to that SObject's id value
    There might be a cleaner way to do this using a Map instead of a List.
  • The class could potentially be made more intelligent by including a change flag in addition to the selection flag. This could be used in the Implementation to only save updates to records that have been selected or de-selected instead of the whole List.

References

Share

Recipe Activity - Please Log in to write a comment

Hi Mihir,

Sorry that wasn't clear!  The line "public List<SelectableSObject> availableLineItems {get; set;};" would be one of the declarations at the top of the controller or extension.  The line above it would be the opening line of the controller class, something like "public with sharing class MyPageControllerExtension {".  Let me know if that helps!

by Charles E Howard  (2012-07-24)

Charles,


I am new to apex development, and find your code very useful. Can you help me understand where below statement should be located. Definitely not in SelectableSObject class.

public List<SelectableSObject> availableLineItems {get; set;};

by Mihir Mehta  (2012-07-13)

X

Vote to Verify a Recipe

Verifying a recipe is a way to give feedback to others and broaden your own understanding of the capabilities on Force.com. When you verify a recipe, please make sure the code runs, and the functionality solves the articulated problem as expected.

Please make sure:
  • All the necessary pieces are mentioned
  • You have tested the recipe in practice
  • Have sent any suggestions for improvements to the author

Please Log in to verify a recipe

You have voted to verify this recipe.