Home > Apex, Visualforce > Super Cool Advanced Lookup Component

Super Cool Advanced Lookup Component

July 26th, 2009

Okay, so maybe it isn’t exactly a super cool advanced lookup, but it is a lookup, and it is cool. Title at least got your attention right? No? Darn.

So what is a super cool advanced lookup you may ask? It is basically text field that as you start to type it becomes a picklist of values depending on the text you are typing. You can have it lookup records on any object that has a Name field. This type of functionality can be done with javascript libraries (examples here) but why do that if you can build something that performs just as well using only Apex and Visualforce? I did play around with the ext.js library and it is definitely more polished but on very large data sets performance was not that good. As I would type it felt like it was freezing up. To me this is much more annoying than a slight delay the VF component makes when performing the SOQL query.

Here is the example page, enjoy: http://tehnrd-developer-edition.na7.force.com/comboBox

First we will take a look at the component named advancedLookup. This component has 4 important attributes:
- object: The object you would like to search, Account, Contact, MyCustomObject__c, etc.
- value: This component returns an Id value and this must be bound to a value in your page’s controller or extension
- maxHeight: As you begin you type a picklist will appear of matching results and you must set the default height.
- width: Depending on the object searched the Name may only be 15 characters wide or it could be 250. Here you set the width so the names are readable.

<apex:component controller="advancedLookUp" >
    <apex:attribute name="object" assignTo="{!objectType}" required="true" type="string" description="The object this component will search."/>
    <apex:attribute name="value" assignTo="{!selectValue}" required="true" type="object" description="A merge feld that references the controller class variable that is associated with this feld. This must be an Id field."/>
    <apex:attribute name="maxHeight" assignTo="{!maxHeight}" required="true" type="Integer" description="Max height of selectList."/>
    <apex:attribute name="width" required="true" type="string" description="Width of the component in pixels or percent."/>
    <apex:attribute name="style" type="string" description="The style used to display the inputText component, used primarily for adding inline CSS styles."/>
    <apex:outputPanel layout="block" style="{!style}">
 
            <!-- This is our input text that we will use in a wildcard search against the names of the defined object. We refresh the results every time this field
            is clicked or a letter is entered (keyup) -->
            <apex:inputText value="{!searchValue}" style="width:{!width}" id="searchValue">
                <apex:actionSupport event="onkeyup" action="{!search}" rerender="resultsPanel"/>
                <apex:actionSupport event="onclick" action="{!search}" rerender="resultsPanel"/>
            </apex:InputText>
 
            <!-- Once we perform the query on the object we will populate this list with the returned values. When a user selects a value we will hide this list
            and auto complete the selected value -->
            <apex:outputPanel id="resultsPanel" layout="block" style="position: absolute;">
                <apex:selectList value="{!value}" size="{!height}" style="width:{!width}" rendered="{!showList}" >
                    <apex:selectOptions value="{!results}"/>
                    <apex:actionSupport event="onchange" action="{!selectvalue}" rerender="resultsPanel,searchValue"/>
                </apex:selectList>
            </apex:outputPanel>
 
    </apex:outputPanel>
 
    <!-- This component has one little piece of javascript. If a user clicks on the screen before selecting a value we want to hide the list of options similar to
    how a normal picklist behaves -->
    <apex:actionFunction name="hideResults" action="{!hideResults}" reRender="resultsPanel"/>
    <script type="text/javascript">
        document.onclick = function(){
            hideResults();
        }
    </script>
</apex:component>

Next the controller for this this custom component:

public class advancedLookUp{
 
    public Object selectValue{get; set;}
    public String objectType {get; set;}
    public String searchValue {get; set;}
    public Integer maxHeight {get; set;}
    public Integer height {get; set;}
    public List<SelectOption> results {get; set;}
    public Boolean showList {get; set;}
    Map<String,String> resultsMap;
 
    //This is our seach method that is called every time a character is entered
    public void search(){
        showList = true;
        results = new List<SelectOption>();
        resultsMap = new Map<String,String>();
 
        //Using dynamic SOQL we pass the object and the search value
        if(searchValue.length() > 0){
            String entry = '%' + searchValue + '%';
            String queryString = 'Select Id, Name from ' + objectType + ' where Name like :entry order by Name limit 1000';
 
            /*Next we add the results to our list of selectOptions. We also add the values to a map so that when a value
            is selected we can auto fill the search box with the selected value */
            for(sObject o : Database.query(queryString)){
                results.add(new SelectOption((String)o.get('Id'),(String)o.get('Name')));
                resultsMap.put((String)o.get('Id'),(String)o.get('Name'));
            }
        }
 
        //Next we inspect the results to set the height and contents of the selectList
        if(searchValue.length() == 0){
            showList = false;
        }else if(results.size() == 0 && searchValue.length() != 0){
            results.add(new SelectOption('','No matches found'));
            height = 2;
        }else if(results.size() == 1){
            height = 2;
        }else if(results.size() > maxHeight){
            height = maxHeight;
        }else{
            height = results.size();
        }
    }
 
    //When a value is selected we will auto complete the searchValue
    public void selectValue(){
        showList = false;
        searchValue = resultsMap.get((string)selectValue);
    }
 
    //simply hides the list of results
    public void hideResults(){
        showList = false;
    }
}

Almost there now. Up next the page and page controller:

<apex:page controller="coolLookUp">
    Try searching with 'e', 'oil', 'az', 'install', etc then click the Show button.
 
    <apex:form >
        <!-- This is only line need for the advanced lookup. The value is bound to an ID variable in this page's controller. When you click the button it 
        refreshes the detail panel and displays the selected opp -->
        <c:advancedLookup object="Opportunity" value="{!oppId}" maxHeight="20" width="250px" style="float: left"/> 
        <apex:commandButton value="Show" rerender="detail" style="float: left" status="status"/>
        <apex:actionStatus startText="elevator music...." id="status"/>      
        <apex:outputPanel id="detail">
            <apex:detail subject="{!oppId}"/>
        </apex:outputPanel>
    </apex:form>
</apex:page>

Doesn’t get much more simple than this:

public class coolLookUp {
    public Id oppId {get; set;}
}

FYI, there is an issue if you try to use this component more than once on a page but I have not yet had the time to investigate.

Categories: Apex, Visualforce
  1. Rajesh Shah
    July 27th, 2009 at 03:48 | #1

    Thanks Jason for this wonderful piece of code. This might help me in the current project I am doing.

  2. joborg
    August 5th, 2009 at 07:42 | #2

    This is a killer! Thanks for publishing this good piece of work!

  3. Thankful
    August 9th, 2009 at 02:59 | #3

    Thanks for such a cool lookup. I have a quick question – You mentioned that “You can have it lookup records on any object that has a Name field.”

    I am new to apex and visaulforce and I am wondering if this code can be customized for other field? A custom objects tile field?

  4. Shikibu
    October 21st, 2009 at 16:07 | #4

    This is really nice. I would like to rerender some other parts of the page when the bound value is changed, but not seeing how to do so.

  5. samd
    November 9th, 2009 at 10:58 | #5

    Awesome component, thanks! Have you done any further work on getting two components to work on the same page? I’m running into this issue and haven’t managed to figure out what’s causing it.

    Cheers
    Sam

  6. Shan
    March 8th, 2010 at 10:05 | #6

    Hi:
    Love this compenent… How would this work if you have a child object. Basically I have an object Course which has a lookup to Contact. I want to put a field out there which goes off the contact field in Course object??
    Otherwise awesome…

  7. Shan
    March 8th, 2010 at 11:05 | #7

    One more thing… When I select another Value… and hit Show, the value is not being picked up…
    I am basically using your component in a dynamic SOQL reporting design…
    Is there a way to refresh the value or something…

  8. naresh
    August 19th, 2010 at 04:31 | #8

    I used this code in my custom application.few days worked well. But suddenly stopped working. When I tested in a separate page its working. But as a field in a big form its not at all working