Roll Your Own Salesforce Lookup Popup Window

Let's talk about the standard salesforce.com "lookup" popup window for a few minutes. You know what I'm talking about.. this button right here

It's a handy little button that pops up whenever you need to search for related records. It does a pretty good job but it has some serious drawbacks:

  1. It's virtually impossible to modify your search criteria. What if you want your users to do some crazy search based upon custom logic or search a field that is not typically available? Sorry... you are out of luck. Not possible.
  2. It's terrible for creating new records. Let's say that a user searches for a specific related record and it (absolutely) doesn't exist. To create the new record they need to close the lookup window, navigate to the tab to create the new related record, create the new record, then go back to the original record they were either editing or creating, pop up the lookup window again and find their newly created record. Wow! That's a lot of work.3. "Quick Create" is evil! You can enable the "Quick Create" option for an entire org but don't do it! It displays, by default, on the tab home pages for leads, accounts, contacts, forecasts, and opportunities! The major problems are that you can only create new records for these 5 objects (what about the other ones!?), you can't customize the fields on the page and validation rules don't fire (can you say, "bad data").

Here's the Solution!

I have some good news and some bad news. For standard page layouts I can't help you. Go vote for this idea and this idea.However, for Visualforce page I have a solution to all of these problems with code!

Here's the code you need to accomplish this and it's also available on this gist. You need two Visualforce pages (the record you are editing and the popup window) and two Apex controllers (a simple one for the record you are editing and the controller for the search and new record popup).

MyCustomLookupController

Here's the Apex controller for the record you are either creating or editing. This is an extremely simple controller that just creates a new contact so you can use the lookup for the related account field.

public with sharing class MyCustomLookupController {
  
 public Contact contact {get;set;}
 
 public MyCustomLookupController() {
  contact = new Contact();
 }
  
}

MyCustomLookup

This is the "magical" Visualforce page that uses jQuery to intercept the popup and instead of showing the standard salesforce.com pop, shows our custom popup instead. The user experience is seamless.

<apex:page controller="MyCustomLookupController" id="Page" tabstyle="Contact">

 <script type="text/javascript"> 
 function openLookup(baseURL, width, modified, searchParam){
  var originalbaseURL = baseURL;
  var originalwidth = width;
  var originalmodified = modified;
  var originalsearchParam = searchParam;
  
  var lookupType = baseURL.substr(baseURL.length-3, 3);
  if (modified == '1') baseURL = baseURL + searchParam;
  
  var isCustomLookup = false;
  
  // Following "001" is the lookup type for Account object so change this as per your standard or custom object
  if(lookupType == "001"){
 
 var urlArr = baseURL.split("&");
 var txtId = '';
 if(urlArr.length > 2) {
  urlArr = urlArr[1].split('=');
  txtId = urlArr[1];
 }
 
 // Following is the url of Custom Lookup page. You need to change that accordingly
 baseURL = "/apex/CustomAccountLookup?txt=" + txtId;
 
 // Following is the id of apex:form control "myForm". You need to change that accordingly
 baseURL = baseURL + "&frm=" + escapeUTF("{!$Component.myForm}");
 if (modified == '1') {
  baseURL = baseURL + "&lksearch=" + searchParam;
 }
 
 // Following is the ID of inputField that is the lookup to be customized as custom lookup
 if(txtId.indexOf('Account') > -1 ){
  isCustomLookup = true;
 }
  }
  
  
  if(isCustomLookup == true){
 openPopup(baseURL, "lookup", 350, 480, "width="+width+",height=480,toolbar=no,status=no,directories=no,menubar=no,resizable=yes,scrollable=no", true);
  }
  else {
 if (modified == '1') originalbaseURL = originalbaseURL + originalsearchParam;
 openPopup(originalbaseURL, "lookup", 350, 480, "width="+originalwidth+",height=480,toolbar=no,status=no,directories=no,menubar=no,resizable=yes,scrollable=no", true);
  } 
 }
</script>

<apex:sectionHeader title="Demo" subtitle="Custom Lookup" />

 <apex:form id="myForm"> 
  <apex:PageBlock id="PageBlock">  
 <apex:pageBlockSection columns="1" title="Custom Lookup">
  <apex:inputField id="Account" value="{!contact.AccountId}" />
 </apex:pageBlockSection>
  </apex:PageBlock>
 </apex:form>
  
</apex:page>

CustomAccountLookupController

The Apex controller for the custom popup window is yours to customize. I know what you are thinking, "Free at last! Free at last! Thank God Almighty, we are free at last!" This class has all of your custom search functionality plus the method to create a new account. This is demo code so the search is very limited and does not prevent soql injections.

public with sharing class CustomAccountLookupController {
 
 public Account account {get;set;} // new account to create
 public List<Account> results{get;set;} // search results
 public string searchString{get;set;} // search keyword
 
 public CustomAccountLookupController() {
  account = new Account();
  // get the current search string
  searchString = System.currentPageReference().getParameters().get('lksrch');
  runSearch(); 
 }
  
 // performs the keyword search
 public PageReference search() {
  runSearch();
  return null;
 }
 
 // prepare the query and issue the search command
 private void runSearch() {
  // TODO prepare query string for complex serarches & prevent injections
  results = performSearch(searchString);    
 } 
 
 // run the search and return the records found. 
 private List<Account> performSearch(string searchString) {

  String soql = 'select id, name from account';
  if(searchString != '' && searchString != null)
 soql = soql + ' where name LIKE &#92;'%' + searchString +'%&#92;err!.localizedDescription'';
  soql = soql + ' limit 25';
  System.debug(soql);
  return database.query(soql); 

 }
 
 // save the new account record
 public PageReference saveAccount() {
  insert account;
  // reset the account
  account = new Account();
  return null;
 }
 
 // used by the visualforce page to send the link to the right dom element
 public string getFormTag() {
  return System.currentPageReference().getParameters().get('frm');
 }
  
 // used by the visualforce page to send the link to the right dom element for the text box
 public string getTextBox() {
  return System.currentPageReference().getParameters().get('txt');
 }
 
}

CustomAccountLookup

Any finally the Visualforce page for the popup itself. It contains a tabbed interface easily allowing a user to search for records and create new ones. Make sure you look at the code for the second tab for creating a new record. I have better things to do than change the fields on the input form every time a new field is created or something is made required. The solution is to usefield sets! So when an administrator makes a change, they can simply update the field set and the popup reflects the change accordingly. Life is good.

<apex:page controller="CustomAccountLookupController"
 title="Search" 
 showHeader="false" 
 sideBar="false" 
 tabStyle="Account" 
 id="pg">
 
 <apex:form >
 <apex:outputPanel id="page" layout="block" style="margin:5px;padding:10px;padding-top:2px;">
  <apex:tabPanel switchType="client" selectedTab="name1" id="tabbedPanel">
  
 <!-- SEARCH TAB -->
 <apex:tab label="Search" name="tab1" id="tabOne">
   
  <apex:actionRegion > 
   <apex:outputPanel id="top" layout="block" style="margin:5px;padding:10px;padding-top:2px;">
  <apex:outputLabel value="Search" style="font-weight:Bold;padding-right:10px;" for="txtSearch"/>
  <apex:inputText id="txtSearch" value="{!searchString}" />
   <span style="padding-left:5px"><apex:commandButton id="btnGo" value="Go" action="{!Search}" rerender="searchResults"></apex:commandButton></span>
   </apex:outputPanel>
 
   <apex:outputPanel id="pnlSearchResults" style="margin:10px;height:350px;overflow-Y:auto;" layout="block">
  <apex:pageBlock id="searchResults"> 
   <apex:pageBlockTable value="{!results}" var="a" id="tblResults">
    <apex:column >
   <apex:facet name="header">
    <apex:outputPanel >Name</apex:outputPanel>
   </apex:facet>
    <apex:outputLink value="javascript:top.window.opener.lookupPick2('{!FormTag}','{!TextBox}_lkid','{!TextBox}','{!a.Id}','{!a.Name}', false)" rendered="{!NOT(ISNULL(a.Id))}">{!a.Name}</apex:outputLink> 
    </apex:column>
   </apex:pageBlockTable>
  </apex:pageBlock>
   </apex:outputPanel>
  </apex:actionRegion>
   
 </apex:tab>
 
 <!-- NEW ACCOUNT TAB -->
 <apex:tab label="New Account" name="tab2" id="tabTwo">

  <apex:pageBlock id="newAccount" title="New Account" >
  
   <apex:pageBlockButtons >
  <apex:commandButton action="{!saveAccount}" value="Save"/>
   </apex:pageBlockButtons>
   <apex:pageMessages />
  
   <apex:pageBlockSection columns="2">
  <apex:repeat value="{!$ObjectType.Account.FieldSets.CustomAccountLookup}" var="f">
   <apex:inputField value="{!Account[f]}"/>
  </apex:repeat>
   </apex:pageBlockSection> 
  </apex:pageBlock>
   
 </apex:tab>
  </apex:tabPanel>
 </apex:outputPanel>
 </apex:form>
</apex:page>