Mass Contact Transfer – Part 1 (The Page)

At my previous employer one of my regularly tedious tasks was to mass re-assign contacts based on various conditions. Many times it was as simple as a group of Accounts were transferred to new Account Managers and the default behavior of SalesForce.com is to only transfer contacts owned by the Account owner. The result would be a group of Contacts needed to be transferred manually. I handled these requests using an exported report of Contacts with Contact ID with Ron Hess’s Excel Connector to update the OwnerID column for the contacts. masstransfercriteria Clearly this required a better way – and VisualForce provided the platform. Creating the Mass Contact Transfer VisualForce page was for the most part fairly straight forward. It’s essentially a two part page:

  1. Search Criteria
  2. Search Results

Building the Search Criteria Portion

Retrieving the From and To User ID’s.

To simplify the process of selecting the From and To users, this was constructed using two picklist fields instead of two text fields with a lookup. The “From” picklist includes all Users – active or in-active; the “To” picklist only lists active users.  

<apex:pageBlockSection columns="3" id="UserSelection">
   <apex:pageBlockSectionItem id="FromUser">
       <apex:outputLabel >Transfer From User:</apex:outputLabel>
       <apex:selectList value="{!fromUserID}" size="1" required="false" id="fromUserID">
            <apex:selectOptions value="{!FromUsers}"></apex:selectOptions>
       </apex:selectList>
   </apex:pageBlockSectionItem>
   <apex:pageBlockSectionItem id="ToUser">
       <apex:outputLabel >Transfer To User:</apex:outputLabel>
       <apex:outputPanel layout="block" styleClass="requiredInput">
       <apex:outputPanel layout="block" styleClass="requiredBlock"/>
       <apex:selectList value="{!toUserID}" size="1" required="false"  id="toUserID">
           <apex:selectOptions value="{!ToUsers}"></apex:selectOptions>
       </apex:selectList>
       </apex:outputPanel>
   </apex:pageBlockSectionItem>
</apex:pageBlockSection>

Mass Contact Transfer - Required FieldThe tough part was getting the red required bar next to the “To” user field. You would expect that setting the Required attribute on the apex:selectList tag would tell SalesForce.com to use the standard red bar for the field, but that is not the case. Instead you have to wrap the field with an apex:outputPanel tag and have another apex:outputPanel tag within the first that closes itself. This code is highlighed above in red. The syntax should work for any field that you want to show the SalesForce.com standard red bar to indicate a required field.

Search Criteria Section

Next was to build the Selection Criteria section of the page. Initially it was very simple. I wanted five lines for selection criteria to match the standard Transfer pages. The first draft of the page had five repeated blocks of code in the VisualForce page for the Selection Criteria (field, operator, value). Clearly this was not the most efficient way to write the page, but it gave me a feel for the look and what was needed. The more efficient way is to use a separate class for Selection Criteria. The Page Controller constructor method creates a List with 5 instances of the searchCriteria class and the VisualForce page can use an apex:DataTable tag to display however many rows are in that list. The resulting page looks like this:

<apex:dataTable value="{!searchCriteria}" columns="3" var="criteria">
   <apex:column>
      <apex:selectList value="{!criteria.searchField}" size="1" id="SearchField" >
        <apex:selectOptions value="{!searchFields}"></apex:selectOptions>
      </apex:selectList>
   </apex:column>
   <apex:column>
      <apex:selectList size="1" value="{!criteria.searchOperator}" id="SearchOperator1">
        <apex:selectOptions value="{!criteria.OperatorSelectList}"></apex:selectOptions>
      </apex:selectList>
   </apex:column>
   <apex:column>
     <apex:inputText size="20" id="SearchValue1" value="{!criteria.searchValue}"/>
   </apex:column>
</apex:dataTable>

The searchCriteria class itself provides the standard get/set methods for the three columns:

  • searchField: The searchFields() method generates a selectList of fields in the Contact, Account, Contact.Owner, and Account.Owner objects.
  • searchOperator: The searchOperators() method returns a fixed selectList of operators – equals, not equals, etc.
  • searchValue: A simple inputText field to capture the search criteria value.

selectioncriteria When the user clicks the [Find] button on the page, the Page Controller Class is able to loop through the criteria lines and process each one individually. Since the Search Criteria section is written to be independent of the “Mass Contact Transfer” page, there is a method in the searchCriteria class to build the Where clause portion. The doSearch() method in the Page Controller Class appends each of the Where clause parts together to create a single SOQL statement to query the contacts based on the From UserID, To UserID, and user defined criteria. The Query is run and the results are appended to a searchResults List for display.

Search Results

Mass Contact Transfer Search Results

At the bottom of the page there is a section that starts as hidden using the style attribute on the apex:outputPanel tag. {!ShowBlockIfResults} returns either “display: block;” to show the block (only if there are search results) or “display: none;” to hide the block. In the list of Contacts, a checkbox in the first column is used to allow the user to select which contacts should be transferred. This is checked by default. To simulate how the standard SalesForce Transfer pages work, the user can check or uncheck all Contacts by clicking the checkbox in the column header. This is accomplished using some simple JavaScript code linked to onClick event on the checkbox in the column header. The styleClass, rowClasses, onrowmouseout, and onrowmouseover attributes of the apex:DataTable tag are used to format the results table so it looks like the standard SalesForce Transfer pages.

<apex:outputPanel id="Results" layout="block" style="{!ShowBlockIfResults}">
<apex:form id="resultsForm" >
  <apex:pageBlock id="resultsBlock">
  <apex:pageBlockButtons >
      <apex:commandButton title="Transfer Selected" value="Transfer Selected" action="{!doTransfer}"/>
  </apex:pageBlockButtons>
      <apex:dataTable value="{!searchResults}" var="Results" id="resultsDataTable"
      styleClass="tableClass list" rowClasses="odd,even"
      onrowmouseout="if (window.hiOff){hiOff(this);}" onrowmouseover="if (window.hiOn){hiOn(this);}">
        <apex:column >
          <apex:facet name="header"><apex:inputCheckbox id="selectall" selected="true"
              onclick="javascript:customSelectAllOrNoneByCheckbox(document.forms['MassTransferContactsPage:resultsForm'],'MassTransferContactsPage:resultsForm:resultsBlock:resultsDataTable:', this);"/></apex:facet>
          <apex:inputCheckbox value="{!Results.selected}" id="selected" />
        </apex:column>
		…. DataTable Columns go here ….
      </apex:dataTable>
   </apex:pageBlock>
</apex:form>
</apex:outputPanel>

Finally, when the user clicks the [Transfer Selected] button, the doTransfer() method on the Page Controller loops through the searchResults list to build a list of Contacts whose OwnerID should be changed. Any errors in the Database.Update call are displayed in the messages section at the top of the page. The last step is to re-run the doSearch() method to query any remaining contacts not transferred the first time (if they were not checked, there was an error, or the original query returned more than 250 rows).

Next week, Part 2 of this blog post will go into the Apex code in the Page Controller and the two supporting classes. In the mean time you are welcome to download the source code or install the AppExchange package by clicking on the SourceCode tab at the top of the page.