List Component
The ListComponent provides developers with a dynamic, highly-customizable data list or grid UI component that can be used to display, edit, and select data within Salesforce. Through the use of Salesforce sObject field sets and managed package plug-ins, the developer has a full control what data is displayed and how it is handled.
The ListComponent supports the following:
- Fields that are displayed are derived from a previously-defined field set for an sObject, enabling a drag-and-drop configuration interface for customizing the content of the component.
- The component can be used anywhere Visualforce pages can be used.
- Data displayed on the ListComponent can be sorted, searched, and filtered.
- Through a plug-in architecture, developers can customize the handling of querying data, user-selected data, user-updated data, and post-update results processing.
- Through global methods exposed from the ListComponentController, developers can customize actions such as pagination, navigation, updating changed data, and submitting selected data.
- Using jQuery, developers can bind to UI elements and events in a structured manner to customize UI actions.
- Through a basic HTML table structure for the data grid, developers can customize the look and feel of the table by using complete custom CSS, modifying existing CSS elements, or using jQuery to modify the CSS.
- Developers can use the ListComponent multiple times on a single page and maintain direct control over each instance both on the client-side and server-side.
- Developers can implement and register the List Filter Admin Plugin and enable List Filters on a custom instance of ListComponent.
The ListComponent is available in the Version 5.100 and later of Zuora Quotes.
The ListComponent consists of:
- ListComponent.component: The Visualforce UI component
- ListComponentController.cls: The Apex controller class for the ListComponent UI component. This class contains mechanisms for communicating with the controller class of the page that embeds the component. These mechanisms are described below.
- ListComponentOptions.cls: The Apex class that stores the configuration options specified by the developer and used by the controller to render the component
- ListComponentJS.component: A standalone component made up of the JavaScript code required for the ListComponent
- ListComponentCSS.component: A standalone component that contains the default CSS for the ListComponent
ListComponent Component
Component Attributes
The ListComponent has the following attributes.
Attribute | Type | Description |
---|---|---|
id | String | An identifier that allows the component to be referenced by other components in the page |
options | ListComponentOptions | The list component's configuration options. See the ListComponentOptions Class section that follows for option details. |
rendered | Boolean | Specifies whether the component is rendered on the page. If not specified, this value defaults to true. |
Embed ListComponent on a Visualforce Page
The following are the requirements you need to comply when embedding the ListComponent on a Visualforce page:
- The ListComponent includes an attribute, options, which is an instance of the ListComponentOptions class that specifies the component configuration. The ListComponentOptions object must be built in an Apex class that is the controller (or extension) of the page containing the ListComponent.
- jQuery is used for DOM manipulation and UI behaviors. To avoid conflicts with other JavaScript libraries that use the jQuery default "
$
" variable on the page, the ListComponent callsjQuery.noConflict()
and assigns the "$jq
" as the variable shortcut to thejQuery()
method. - The component must be placed within the
<apex:form>
and</apex:form>
tags, and any buttons or other UI elements that call the ListComponentController's global methods must be inside the same form container as the component. - The component is defined with the
layout="none"
option, so the component should be wrapped in block-level HTML elements, such as<div>
or<td>
. - The data table is rendered with a basic HTML table structure, and it does not use Visualforce tags like
<apex:pageBlockTable>
or<apex:dataTable>
. This allows for simpler overriding of the table CSS.
ListComponentOptions Class Properties
The ListComponent can be configured by constructing an instance of the ListComponentOptions class as a public property of your page controller and setting the following options.
Property | Type | Default | Description |
---|---|---|---|
exceptionPlugin | String | The default implementation of this plugin | The fully-qualified class name of the custom implementation of this plugin. If your implementation is named "MyPlugin" and is an inner class of "SomeClass", then this should be set to "SomeClass.MyPlugin". |
disableSort | Boolean | false | If set to true, the columns are not sortable. |
fieldSetName | String | n/a | The API name of the field set that contains the fields that should be queried and rendered in the list. The field set must belong to the sObject named in the objectName property. Required. |
hideActionButtons | Boolean | false | Set this to true to hide the default update buttons in the "edit" or "inlineEdit" modes or the default selection processing buttons in "singleSelect" or "multiSelect" mode so that you can implement custom buttons for these modes. |
hidePaginationNav | Boolean | false | Set this to true to hide the default page navigation controls so that you can implement custom page navigation. See the ListComponentController Global Methods section for instructions on implementing custom page navigation. |
hideSearch | Boolean | false | Set this to true to hide the search field and buttons that appear by default just above the data table. |
instanceName | String | listComponent | Allows developers to give a custom name to the instance of the ListComponent. Only needs to be set when using multiple ListComponents on a singe page. The name can include letters, underscores, and numbers, but no other characters. Also, it cannot start with a number. |
mode | String | detail | The ListComponentController supports five modes. The mode values are case-sensitive:
When using one of the edit,inlineEdit, singleSelect, and multiSelect modes, any selections or edits will be lost if the user moves to another page of data prior to submitting the updates or selected data. |
multipleListComponents | Boolean | false | Set this to true if you are using multiple instances of the ListComponent on a single page. When setting this option to true, developers must manually embed the ListComponentJS and ListComponentCSS components on the page prior to embedding the ListComponent instances. |
objectName | String | n/a | The API name of the sObject whose data will be displayed in the list. When referencing sObjects that belong to a managed package or reside in another namespace, the namespace must be included. For example, if a managed package is part of the "MyPackage" namespace and the sObject is a custom object named "MyObject", then the value for objectName would be "MyPackage__MyObject__c". Required. |
pageSize | Integer | 20 | The number of records to be displayed on a single page. If a value less than 1 or greater than 2000 is entered, the default number of records will be displayed. |
parentController | ListComponentController. ParentController | When using the ListComponent on a page, the Apex controller (or extension) of that page should extend the virtual ListComponentController.ParentController class, then assign itself to this property when the options are being set. This allows all plugin implementations to be passed the current instance of the page controller so that the plugin implementations have access to all of the page controller public methods and properties (and can therefore modify what appears on the page). Required. | |
postUpdatePlugin | String | The default implementation of this plugin | The fully-qualified class name of the custom implementation of this plugin. |
queryPlugin | String | The default implementation of this plugin | The fully-qualified class name of the custom implementation of this plug-in. |
readOnlyFields | Set<String> | An empty Set<String> | When in the "edit" or "inlineEdit" modes, the fields in this set will be displayed as read-only, even if the current user has the write access to the field. The values of this set must be the fully-qualified developer name (including the namespace prefix) of the field. For example: "zqu__MyCustomField__c". |
selectButtonName | String | Submit Selection | In "singleSelect" or "multiSelect" modes, this controls the display name of the button used to submit the selected data. |
selectionPlugin | String | The default implementation of this plugin | The fully-qualified class name of the custom implementation of this plugin. |
updatePlugin | String | The default implementation of this plugin | The fully-qualified class name of the custom implementation of this plugin. |
updateButtonName | String | Update Records | In the "inlineEdit" mode, this controls the display name of the button used to submit the data for updating. |
useCustomCSS | Boolean | false | Setting this to true will suppress the default CSS used to style the list component table and buttons. See the "Using Custom CSS" section of this document for a description of the DOM element class structure and how to use custom CSS for it. |
ListComponentController Class
Access the ListComponentController Instance
In the ParentController virtual class, there is a virtual method called registerController
. This method is called by the ListComponentController after it has completed its initialization. It can be overriden in your page controller to receive each instance of the ListComponentController that is on your page. The method takes both a ListComponentController and a String parameter, the value of the instanceName options. It is recommended that you store the ListComponentController instances in a map keyed by the instanceName.
ListComponetController Plug-Ins
The ListComponentController exposes the following interface classes that can be implemented to handle actions a user can perform with the ListComponent:
- Query
- Selection Handling
- Update
- Post-update
- Exception
The concrete implementations of these interfaces are placed in your page controller (or any other class) and their names are passed to the ListComponentOptions object as Strings at runtime.
In addition to other parameters, each of the plug-in methods takes an instance of ListComponentController.ParentController. When the plug-in methods are called, this parameter is passed to the parentController option from the ListComponentOptions object that your controller passed into the ListComponent. This gives your plug-in implementation access to all of the publicly-accessible methods and properties of your page controller.
ListComponentController.IQueryPlugin
The IQueryPlugin allows a developer to modify the baseSoql
then pass the modified baseSoql
string to the Database.getQueryLocator(String query)
method. For example, you can add a WHERE clause with additional filters to baseSoql.
This plug-in builds a QueryLocator object with the data returned by the query. The data returned by the query and passed to Database.getQueryLocator
is treated as the primary data set for the ListComponent. Any searching or filtering will be performed against this primary data set.
Interface Class Signature
Database.QueryLocator getQueryLocator (String baseSoql, ListComponentController.ParentController parentController);
The baseSoql
string passed to the plugin method is in the format:
select [field1],[field2],...[fieldN] from [objectName]
where the objectName
and fieldSetName
are from the ListComponentOptions property values.
Default Behavior
The QueryLocator
is initialized using the baseSoql
string with no modifications and returned.
ListComponentController.ISelectionPlugin
The ISelectPlugin returns a PageReference
, allowing the plugin implementation to send a new page to the controller user after the selected data as been processed.
When the component is in the multiSelect or singleSelect mode, use this plugin to process the selected records.
Interface Class Signature
PageReference handleSelected (Map<Id,SObject> selectedData, ListComponentController.ParentController parentController);
Default Behavior
The selected data is processed, and a null PageReference
is returned.
ListComponentController.IUpdatePlugin
Use the IUpdatePlugin to customize the handling of data when it is submitted for updating. This could include performing additional validation or even modifying data.
The method must call Database.update()
and return the resulting List<Database.SaveResult>
collection.
Only records on the current page that were modifiedare passed to this plugin.
Interface Class Signature
List<Database.SaveResult> doUpdate (List<SObject> data, ListComponentController.ParentController parentController);
Default Behavior
The records are updated without modification or additional validation, and the "allOrNone" option passed to the Database.update() method is set to false. This allows some records to be successfully updated even if other records fail due to validation or other errors.
ListComponentController.IPostUpdatePlugin
After the data has been updated, you can use the IPostUpdatePlugin to customize handling of the List<Database.SaveResult>
collection. For example, with this plugin, you can write the error messages for any failed updates to the page.
This plugin method returns a PageReference
, allowing the plugin implementation to send a new page to the controller user after the update results have been processed.
Interface Class Signature
PageReference handleResults (List<Database.SaveResult> results, ListComponentController.ParentController parentController);
Default Behavior
No additional processing is performed to the update results, and a null PageReference
is returned.
ListComponentController.IExceptionPlugin
The IExceptionPlugin enables the custom gathering and display of error messages. Any validation errors or exceptions encountered after the ListComponentController is initialized will be passed to the IExceptionPlugin as an instance of ListComponentException. If the options object is null or the implementation of this plugin cannot be found, an exception is thrown directly instead of being passed to the plug-in.
Interface Class Signature
void handleException (ListComponentException lce, ListComponentController.ParentController parentController);
Default Behavior
The exception message is written to the page via ApexPages.addMessage()
with a severity of ERROR.
ListComponentController Global Methods and Properties
By default, the ListComponent provides a number of UI features that can be displayed or hidden via the ListComponentOptions class properties, or optionally used in your own UI:
- a pagination-based navigation interface for paging through records
- a row of buttons for the "edit", "inlineEdit", "multiSelect", and "singleSelect" modes
- a search box and buttons, rendered above the data table by default
The ListComponentController methods for these features are exposed globally so that they can be called by custom Visualforce UI controls.
Pagination Global Properties
The following properties, except goToPage, are read-only.
Property | Type | Description |
---|---|---|
currPage | Integer | The current page number |
goToPage | Integer | When implementing functionality to allow a user to go to a specific page, set this property to the desired page number before calling thejumpToPage () global method. |
hasNext | Boolean | Indicates if there is another page after the current page |
hasPages | Boolean | Indicates if the number of records returned in the initial query that was executed when the ListComponent was first initialized |
hasPrevious | Boolean | Indicates if there is another page prior to the current page |
numPages | Integer | The total number of pages in the current data set. This number adjusts to reflect the correct number of pages after a search has been performed. |
Pagination Global Methods
In the "edit", "inlineEdit", "multiSelect", and "singleSelect" modes, all of the following methods discards any edits or selections made by the user and re-queries the entire result set.
Method | Return Type | Description |
---|---|---|
first | PageReference (null) | Moves to the first page |
jumpToPage | PageReference (null) | Moves the data to the page number specified in the goToPage property |
last | PageReference (null) | Moves to the last page |
next | PageReference (null) | Advances to the next page of data unless the current page is the last page |
previous | PageReference (null) | Moves to the previous page unless the current page is the first page. |
Custom Pagination Sample Code
// This example assumes that there is a Visualforce page that has this class as its controller or extension, // and that page embeds the ListComponent. public class MyListClass extends ListComponentController.ParentController { public ListComponentOptions theOptions { get; set; } private Map<String,ListComponentController> listControllerMap; public MyListClass() { this.theOptions = new ListComponentOptions(); this.theOptions.objectName = 'Account'; this.theOptions.fieldSetName = 'ListTest'; this.theOptions.parentController = this; this.theOptions.instanceName = 'MyList'; listControllerMap = new Map<String,ListComponentController>(); } public override void registerController(ListComponentController lcc, String instanceName) { this.listControllerMap.put(instanceName, lcc); } public PageReference nextPage() { ListComponentController lcc = this.listControllerMap.get(this.theOptions.instanceName); return lcc.next(); } }
Search Global Property
Property | Type | Description |
---|---|---|
searchStr | String | Set this to the search string that a user inputs prior to calling the processSearch () global method. |
Search Global Method
Method | Return Type | Description |
---|---|---|
processSearch | PageReference (null) | Performs a search using the searchStr entered by the user. |
Data Handling Global Methods
Method | Return Type | Description |
---|---|---|
clearResults | PageReference (null) | After a search has been performed, this method resets the ListComponent back to the original data sets and set searchStr to blank. |
processUpdate | PageReference | Sends any modified records to the update and post-update plugins. Returns the PageReference object that is returned by the post-update plugin. |
processSelection | PageReference | Sends any selected records to the selection plugin. Returns the PageReference object that is returned by the selection plugin. |
Custom CSS and JavaScript
Custom CSS Notes
- "listComponent" is the default value for the instanceName option. When using multiple list components on a page, the instanceName value you give to each one will replace "listComponent" in the markup.
- Data rows (
<tr>
) in the table have an element id in the format{instanceName}:{record id}
. - Data cells (
<td>
) in the table have an element id in the format{instanceName}:{record id}:{field name}
. - In the "edit" mode, input controls inside each of the data cells are rendered automatically by Salesforce based on the underlying data type, using the apex:inputField tag. For example, text fields are rendered as
<input type="text" ...>
. - The row containing the "defaultButtonCell" is not rendered when the hideActionButtons and hidePaginationNav options are set to true.
- The row containing the "defaultSearchCell" is not rendered when the hideSearch option is set to true.
Custom JavaScript Notes
Use the jQuery CSS selector pattern matching (http://api.jquery.com/category/selectors/) to manipulate elements, bind events to specific data cells, or bind events to groups based on the element id. For example:
- In a ListComponent using a field set from the Account object where the AnnualRevenue field is included, to bind to all AnnualRevenue text boxes:
- jQuery("[id$='\\:AnnualRevenue']").children(":input[type=text]");
- In a ListComponent using a field set from the Account object where the Industry field is included, to bind to all Industry select elements (picklists):
jQuery("[id$='\\:Industry']").children("select");
ListComponent Markup Structure
<div class="listComponentPanel" id="listComponent:listComponentPanel"> <table class="outerTable" id="listComponent:outerTable"> <tbody> <tr> <td id="listComponent:defaultSearchCell" style="text-align: right;"> <!-- default search box and buttons, can be hidden via the list component options --> </td> </tr> <tr id="listComponent:dataGridRow"> <td> <table class="innerTable bPageBlock" id="listComponent:innerTable"> <thead> <tr> <th class="textField">Account ID</th> <th class="textField">Account Name</th> <th class="numericField">Annual Revenue</th> <th class="numericField">Created Date</th> <!-- additional fields --> </tr> </thead> <tbody> <tr class="dataRow" id="listComponent:001i0000008Oah1AAC"> <td class="dataCell textField readOnly" id="listComponent:001i0000008Oah1AAC:Id"><!-- data, etc. --></td> <td class="dataCell textField" id="listComponent:001i0000008Oah1AAC:Name"><!-- data, etc. --></td> <td class="dataCell numericField" id="listComponent:001i0000008Oah1AAC:AnnualRevenue"><!-- data, etc. --></td> <!-- additional fields --> </tr> <!-- more data rows --> </tbody> </table> </td> </tr> <tr> <td id="listComponent:defaultButtonCell" style="text-align: center;"> <!-- default pagination navigation and update/submit buttons, can be hidden via a ListComponent option --> </td> </tr> </tbody> </table> </div>
Known Issues
- When in the inlineEdit mode, there is a known issue with Visualforce inlineEdit support that prevents changes from being saved when a user enters data that causes a validation error (such as text characters into a numeric field). This issue is discussed here: http://boards.developerforce.com/t5/Visualforce-Development/Problem-with-Saving-an-Inline-Edit-that-causes-a-validation/td-p/333045. This is a Visualforce defect.
- Visualforce inlineEdit support does not support the editing of lookup fields via click events. For instance, when displaying an Account record, the Owner's name will be displayed as a link to the Owner's User record. If inlineEdit mode is set to use click or double-click as the events to enter edit mode for that field, this field cannot be edited. This is a Visualforce defect.
- If a custom object in an org where the Zuora Quotes package is deployed has an object that has the same name as a Zuora Quotes object (except for the namespace), the Schema.getGlobalDescribe() call, which is core to the ListComponent's dynamic functionality, will only return the package object. For instance, if a Zuora customer creates an object called "Quote__c", there will be no way to access that object's properties, since calling Schema.getGlobalDescribe().get('Quote__c') will only return the Zuora Quotes package object "Quote__c". This issue was addressed in the Summer '13 release (https://success.salesforce.com/issues_view?id=a1p30000000SUwKAAW) however it has not been extensively tested here.