Building a Dynamic Search Page in Visualforce

July 13th, 2010

I brushed this code off and thought it might be useful to someone as a starting point for a dynamic search page. It has some cool functionality including passing search criteria via Javascript to the controller, search as you type, sorting of results by clicking on the column header plus much more. Hope you find it useful.

You can run this demo at my developer site.

Some of the interesting features of the Visualforce page and Apex controller include:

  • Instead of the typical overhead of using getters/setters in the controller to maintain the variables for the search form, I used Dave Carroll’s blog as an example to pass the variables via Javascript.
  • Since I’m using Javascript to pass my parameters, I can get fancy and make the search form more dynamic by running the search as the user types or selects an option in the picklist. No submit button is required.
  • Users can click on the column headers to toggle the direction of the search results by using a CommandLink component and passing the column name.
  • Build the picklist using Dynamic Apex’s Describe functionality so that it’s maintenance free.
  • Perform the search against multiple fields including multi-select picklists using dynamic SOQL and preventing SOQL injection attacks.

ContactSearchController

Take a look at the Apex code below. The search interaction is split up between two separate but integral parts. When the search is fired from the Visualforce page via Javascript, the SOQL is constructed in the runSearch() method and is then passed to the runQuery() method to execute. The SOQL string is persisted so that when the user clicks on the table column the same SOQL can be issued again in the toggleSort() method but ordered by a different field name and sort direction.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
public with sharing class ContactSearchController {
 
  // the soql without the order and limit
  private String soql {get;set;}
  // the collection of contacts to display
  public List<Contact> contacts {get;set;}
 
  // the current sort direction. defaults to asc
  public String sortDir {
    get  { if (sortDir == null) {  sortDir = 'asc'; } return sortDir;  }
    set;
  }
 
  // the current field to sort by. defaults to last name
  public String sortField {
    get  { if (sortField == null) {sortField = 'lastName'; } return sortField;  }
    set;
  }
 
  // format the soql for display on the visualforce page
  public String debugSoql {
    get { return soql + ' order by ' + sortField + ' ' + sortDir + ' limit 20'; }
    set;
  }
 
  // init the controller and display some sample data when the page loads
  public ContactSearchController() {
    soql = 'select firstname, lastname, account.name, interested_technologies__c from contact where account.name != null';
    runQuery();
  }
 
  // toggles the sorting of query from asc<-->desc
  public void toggleSort() {
    // simply toggle the direction
    sortDir = sortDir.equals('asc') ? 'desc' : 'asc';
    // run the query again
    runQuery();
  }
 
  // runs the actual query
  public void runQuery() {
 
    try {
      contacts = Database.query(soql + ' order by ' + sortField + ' ' + sortDir + ' limit 20');
    } catch (Exception e) {
      ApexPages.addMessage(new ApexPages.Message(ApexPages.Severity.ERROR, 'Ooops!'));
    }
 
  }
 
  // runs the search with parameters passed via Javascript
  public PageReference runSearch() {
 
    String firstName = Apexpages.currentPage().getParameters().get('firstname');
    String lastName = Apexpages.currentPage().getParameters().get('lastname');
    String accountName = Apexpages.currentPage().getParameters().get('accountName');
    String technology = Apexpages.currentPage().getParameters().get('technology');
 
    soql = 'select firstname, lastname, account.name, interested_technologies__c from contact where account.name != null';
    if (!firstName.equals(''))
      soql += ' and firstname LIKE \''+String.escapeSingleQuotes(firstName)+'%\'';
    if (!lastName.equals(''))
      soql += ' and lastname LIKE \''+String.escapeSingleQuotes(lastName)+'%\'';
    if (!accountName.equals(''))
      soql += ' and account.name LIKE \''+String.escapeSingleQuotes(accountName)+'%\'';  
    if (!technology.equals(''))
      soql += ' and interested_technologies__c includes (\''+technology+'\')';
 
    // run the query again
    runQuery();
 
    return null;
  }
 
  // use apex describe to build the picklist values
  public List<String> technologies {
    get {
      if (technologies == null) {
 
        technologies = new List<String>();
        Schema.DescribeFieldResult field = Contact.interested_technologies__c.getDescribe();
 
        for (Schema.PicklistEntry f : field.getPicklistValues())
          technologies.add(f.getLabel());
 
      }
      return technologies;          
    }
    set;
  }
 
}

CustomerSearch Visualforce Page

The Visualforce page has three sections: 1) a search form, 2) a results blockTable and 3) a debug panel displaying the SOQL that was executed.

The search form has a number of fields that fire the Javascript search using the onkeyup and onchange events. Instead of each form field passing the current values to actionFunction’s Javascript method, for ease of use, each form field calls the doSearch() function that gathers up the values and submits them. When the actionFunction renders it creates a Javascript function that POSTs the values to the controller in the same manner as CommandLink or CommandButton.

The search results BlockTable is rerendered when the search is submitted to the controller so that the results display properly. Users can click on the column headers to reorder the results of the query. This uses a CommandLink to pass the sort field to the controller and toggle the sort direction.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
<apex:page controller="ContactSearchController" sidebar="false">
 
  <apex:form >
  <apex:pageMessages id="errors" />
 
  <apex:pageBlock title="Find Me A Customer!" mode="edit">
 
  <table width="100%" border="0">
  <tr>  
    <td width="200" valign="top">
 
      <apex:pageBlock title="Parameters" mode="edit" id="criteria">
 
      <script type="text/javascript">
      function doSearch() {
        searchServer(
          document.getElementById("firstName").value,
          document.getElementById("lastName").value,
          document.getElementById("accountName").value,
          document.getElementById("technology").options[document.getElementById("technology").selectedIndex].value
          );
      }
      </script> 
 
      <apex:actionFunction name="searchServer" action="{!runSearch}" rerender="results,debug,errors">
          <apex:param name="firstName" value="" />
          <apex:param name="lastName" value="" />
          <apex:param name="accountName" value="" />
          <apex:param name="technology" value="" />
      </apex:actionFunction>
 
      <table cellpadding="2" cellspacing="2">
      <tr>
        <td style="font-weight:bold;">First Name<br/>
        <input type="text" id="firstName" onkeyup="doSearch();"/>
        </td>
      </tr>
      <tr>
        <td style="font-weight:bold;">Last Name<br/>
        <input type="text" id="lastName" onkeyup="doSearch();"/>
        </td>
      </tr>
      <tr>
        <td style="font-weight:bold;">Account<br/>
        <input type="text" id="accountName" onkeyup="doSearch();"/>
        </td>
      </tr>
      <tr>
        <td style="font-weight:bold;">Interested Technologies<br/>
          <select id="technology" onchange="doSearch();">
            <option value=""></option>
            <apex:repeat value="{!technologies}" var="tech">
              <option value="{!tech}">{!tech}</option>
            </apex:repeat>
          </select>
        </td>
      </tr>
      </table>
 
      </apex:pageBlock>
 
    </td>
    <td valign="top">
 
    <apex:pageBlock mode="edit" id="results">
 
        <apex:pageBlockTable value="{!contacts}" var="contact">
 
            <apex:column >
                <apex:facet name="header">
                    <apex:commandLink value="First Name" action="{!toggleSort}" rerender="results,debug">
                        <apex:param name="sortField" value="firstName" assignTo="{!sortField}"/>
                    </apex:commandLink>
                </apex:facet>
                <apex:outputField value="{!contact.firstName}"/>
            </apex:column>
 
            <apex:column >
                <apex:facet name="header">
                    <apex:commandLink value="Last Name" action="{!toggleSort}" rerender="results,debug">
                        <apex:param name="sortField" value="lastName" assignTo="{!sortField}"/>
                    </apex:commandLink>
                </apex:facet>
                <apex:outputField value="{!contact.lastName}"/>
            </apex:column>
 
            <apex:column >
                <apex:facet name="header">
                    <apex:commandLink value="Account" action="{!toggleSort}" rerender="results,debug">
                        <apex:param name="sortField" value="account.name" assignTo="{!sortField}"/>
                    </apex:commandLink>
                </apex:facet>
                <apex:outputField value="{!contact.account.name}"/>
            </apex:column>
 
            <apex:column >
                <apex:facet name="header">
                    <apex:commandLink value="Technologies" action="{!toggleSort}" rerender="results,debug">
                        <apex:param name="sortField" value="interested_technologies__c" assignTo="{!sortField}"/>
                    </apex:commandLink>
                </apex:facet>
                <apex:outputField value="{!contact.Interested_Technologies__c}"/>
            </apex:column>
 
        </apex:pageBlockTable>
 
    </apex:pageBlock>
 
    </td>
  </tr>
  </table>
 
  <apex:pageBlock title="Debug - SOQL" id="debug">
      <apex:outputText value="{!debugSoql}" />           
  </apex:pageBlock>    
 
  </apex:pageBlock>
 
  </apex:form>
 
</apex:page>

Categories: Apex, Code Sample, Salesforce, Visualforce

Leave a comment

Comments Feed67 Comments

  1. wes

    Snazzy work. Love the debug window too.

  2. Hannes

    Nice, as always :) Thanks for putting this together.

  3. Richard Vanhook

    Good stuff. I did notice one thing in demo: when I type John for example, it seems like the auto-correct query for just “j” completed after “john” and as a result, I got more results for “john” than I should have. SoqlBuilder in apex-lang would simplify runSearch() also. :-)

  4. Jeff Douglas

    Well played Richard!!

  5. Sam

    Hi Jeff

    This is great! On the topic of searching, have you considered building a dynamic search page where the object and fields to be searched can be selected by picklists?

    e.g. first picklist to select object to be searched (opportunity/account etc.)
    second picklist to choose a field on the object
    third picklist to choose another field

    The idea would be to have a similar interface to the report builder where you can select search criteria.

  6. Jeff Grosse

    Jeff, thanks for posting this. I really like how simple it makes finding what you need. It’s great inspiration for other things the can be done like this.

  7. Mike Leach

    Wicked cool.

  8. Jeff Douglas

    Sam, I saw a small demo out there somewhere like that but it was essentially to choose an object and then view all of its fields. I think what you are describing may be doable but you’d also have to take into consideration the type of field as well for numbers, LIKE statements, multi-select picklists, etc.

  9. Testing SFDC

    Hi I am trying to filter contact records by user territory. Any thoughts of how to do it via custom code.

  10. Isaac Evans

    I like the header sort, however, your Technologies sort does not appear to work :-)

  11. Jeff Douglas

    I think it has something to do with the multi-select picklist field. Thanks.

  12. Jeremy

    Hi Jeff, thanks for this useful code. I customized the Interested Technologies field, I have it pulling from a custom picklist called Contact Role, but whenever I select a role to sort from, it says “Error: Ooops!” at the top. I’m able to hit the drop down and see all the different roles, but am not sure why it’s not pulling the records I specify. Everything is working fine. Any suggestions on where I may look to fix it?
    Thanks!

  13. Chris L

    @Jeremy:

    Try taking the code from line 68:

    soql += ‘ and interested_technologies__c includes (\”+technology+’\')’;

    and change it to:

    soql += ‘ and interested_technologies__c LIKE \”+technology+’\”;

  14. Jeremy

    @Chris L

    Thank you! That worked perfectly.

  15. Christian

    I really like your example, but there is an issue I am having trouble finding a decent solution for: There is no guarantee that the results of performing several queries in a row returns in the same order as they were made.

    For instance, when typing “foo” in the Account field three queries are made, one for “f”, one for “fo” and one for “foo”, and there is no guarantee that the last result to be displayed is the result for “foo”. So you can have the slightly odd situation where I have clearly typed “foo” in the search field, but the results I’m looking at is filtered on “fo” instead.

    I have been trying various ways of solving this, but can’t seem to come up with a good and dependable solution. Any thoughts?

  16. Jeff Douglas

    @Christian, have you tried ordering the query results in some manner? Perhaps by name date created?

  17. Daud

    Hi Jeff,

    thank you for this dynamic search page, can you also tell me if this can be achieved for the lookup dialog page for custom objects? If it can how should i go about it

    Thanks

  18. Jeff Douglas

    @David, I am actually working on a custom search dialog. Stay tuned.

  19. Pranay Joshi

    Awesome stuff… here… I just started SFDC coding…. am wondering how would go about creating the test for the controller….
    I tried myself but it seems something is amiss… probably due to the use of javascript to send the parameters… any thoughts?

  20. Sumit Shingavi

    thnks for this coooll app..good work..

  21. Ken Noll

    Jeff:

    Thank you for the framework. I think this will help me considerably.

    My challenge to this is I want to retreive the searched data from an external web application using a callout then parsing the XML. Do you thing the framework is valid for my scenario?

    Many Thanks.

    Ken

  22. Jeff Douglas

    @Ken, I don’t think this framework will work well for you. To make the call out it will have to be asynchronous and I don’t think users will want to wait around.

  23. Jason

    I built something very similar to this around the same time and here are a few issues I encountered that may be helpful for others.

    I was recently traveling internationally and tried to use a page like this. It performed terribly. The main reason is that salesforce.com’s servers leave something to be desired in the latency department and it’s even worse when accessing na servers internationally. GAE is much better in this area http://code.google.com/status/appengine/detail/serving/2010/11/15#ae-trust-detail-helloworld-get-latency compared to SFDC: http://trust.salesforce.com/trust/status/.

    The users type too fast and the server can’t responded and update the page quick enough. You get all sorts or flashing tables as the page tries to rerender results. The browser is also trying to send requests and receive responses at the same time which introduces a whole set of different challenges.

    Here are some potentials solutions I’ve had to use. On keyup call a js function that uses setTimeout which then calls an action method after about 200ms. This prevents the server from getting bombarded with requests but still provides almost real time search.

    Another issue Ive noticed is that if you typing “racecar” the page will send a request with “race” (“r”,”ra”rac”) and the response may be received when user is typing “car” and then the “racecar” input is never sent to the server. Once solution that I’ve come up with is to rerender a script tag with the {!searchValue} from the controller and compare that to current text of the input field. If they are different, run the search again.

  24. Kavmah

    Jeff,

    This code is great and works perfectly for what I needed and I’m no developer so Thank you!!!

    One question- The link to your demo website- how did you achieve that? That feature would be priceless and of tremendous value to me. Today i have to provide logins to the dev site to enable a preview but to expose it the way you did and test it would be fantastic!

  25. David

    We use a Report Builder-ish approach for Chatterbox and is something you may be after Sam.

    Give it a whirl its currently on AppExchange.
    http://sites.force.com/appexchange/listingDetail?listingId=a0N30000003GGQKEA4

  26. Rob Cowell

    I’m obviously missing something fundamental here, but I’m using this example to try to render out data from my own custom object, but there’s no initial data returned on page load, like it does in your example.

    I thought the constructor on the controller class would trigger this but I’m getting nothing.

    Any suggestions?

  27. Bharthi Ramsewak

    Hi Jeff,

    I was wondering do you perhaps know how to solve the following issue i am having.

    I have a related list on Page A that basically lists in a matrix grid vehicles and their history like serviced? yes or No, Due for service? Y or N.

    I want to add a search funtionality to this related list so that sales people can search by registration number and this list must be filtered to bring up only the one matching record.

    any ideas? I am fairly new to Salesforce and cloud computing and any help would be greatly appreciated.

    regards,
    Bharthi

  28. Bharthi Ramsewak

    @Jeff,

    I am looking for a solution to search/filter a related list.

    Basically i have a related list of records (vehicle reg numbers) and i want users to be able to enter a search string (reg No) and hit search and the list is filtered.

    Is this doable?

  29. Jeff Douglas

    @Rob, is your method even getting fired? Try debugging to see if any results are being returned from your query.

  30. Jeff Douglas

    @Bharthi, without know all of the details it sounds like you have a m:m junction object that is actually being search? One idea is to de-normalize that table with some of the string data that people may want to search on and just bring back results from that table.

  31. Jeff Douglas

    @Bharthi, you can definitely do this but you’ll need a visualforce page. You won’t be able to do it on a standard page layout.

  32. Asish

    hi Jeff,

    I would like to include a “lookup” field on my search screen. I dont want to refer any field for that. My requirement is :- I need to show list of users in a particular profile. So my custom VF search screen should have a lookup to show those users.

    Thanks in advance!

    thanks
    Asish

  33. Alaiah Chandrashekar

    Just the stuff I was looking for, wonderful blog!

  34. Bharthi Ramsewak

    Hi Jeff,

    Is there any code/pages that i can reference. I am very new to Visualforce and apex.

    What i have is a VF Page, that has a related list. I want users to search the related List.

    I was thinking of having your search above modified to return a related list instead of a normal list. Is this doable?

    regards,
    Bharthi

  35. sylar

    Awesome!!!

  36. ashok

    once again.. jeff. i have no words :) thanks for the post.

  37. Justyn Pride

    Hi

    We are a charity that use Salesforce with the Not for Profit app. I can get your excellent search page working in my force.com account, but when running it in the Sandbox for my org no records are shown even though there are records.

    I am fairly new to Visualforce and Apex classes so any thoughts very welcome. This coding is perfect for a search function I want on a custom object, but I would like to get it working on contacts first as it will help me learn more.

    If there is anything in particular I need to be aware of for getting it to work with a custom object then please let me know.

    Regards

    Justyn

  38. Justyn Pride

    Hi

    This is a great piece of code. I have a couple of questions that I hope you can help with. I have used the code successfully in my force.com account and all works fine. However when I install in my sandbox installs ok, but does not show any data although I now there is relevant data there. If it helps we run the Salesforce Not for Profit package.

    On a related note I would very much like to utilise this code on a custom object. Has anyone done this and is there anything in particular that I should be aware of? I am new to the coding side of things, but eager to learn!

    Thanks in advance

    Justyn

  39. Jeff Douglas

    The search UI displays the SOQL string that is being executed. If you copy that string and run it in something like Eclipse or SoqlXplorer, does it return results?

    I’ve actually modified this to run with a custom object so it isn’t a problem. You’ll just have to change the parameter inputs to match your custom object and then generate the correct SOQL. Good luck.

  40. Justyn Pride

    Hi

    I’ve run the string and it presents the two results that I would expect to see. However nothing is appearing in the visualforce page. Strange. I will recreate the page and controller from scratch to see if I did something wrong….

    Justyn

  41. Justyn Pride

    Have just recreated it all from scratch and definitely get the same problem. Very strange.

  42. Chris M

    Jeff,

    This is exactly what we needed! I was able to get it to work very quickly and it makes available over 12 fields to drill down on the search. However, I am having difficult with the test methods. Do you happen to have any examples for this controller?

    Thank you very much for all the great resources you provide!

    Chris

  43. Sathyanarayanan

    Hi Jeff,

    I have an issue in sorting list in Apex class.
    I have an inner class defined inside a controller and am calling that from the main class and have assigned a list of records to List lstsubclassname.

    In my UI i have bound the repeater to this list. I have to sort the list based on country field inside this repeater. How to sort this? can you please help me in showing the right approach for doing this? I have googled a lot but in vain. :(

    Regards
    Sathya

  44. Justyn Pride

    Just following up to see if anyone had any suggestions to my problem? I want to move onto custom objects but can’t until I know that I have the code right. The soql produces the correct results, but in the visualforce page no results are displayed before and after filtering.

  45. Justyn Pride

    Hi

    I have solved my problem. I followed Chris L’s suggestion of amending line 68 in the controller to

    soql += ‘ and interested_technologies__c LIKE \”+technology+’\”;

    This removed the Oops message.

    I then also viewed the page within my Salesforce as a custom tab with a visualforce page. This displayed all the data. I assume with Jeffs live example there was some permissions given on the fields and objects for the data to display?

    Hope this is of help for others. it’s a great piece of code.

    I wondering if it is possible to have clickable links on the data? I would like a field such as account to be clickable to go directly into the actual account record.

  46. Justyn Pride

    A couple of quick questions:

    1. How were you able to make your demo public? When I’ve tried on a custom object I do not get any records displayed. There are no error messages and the soql obviously works as well.

    2. I’ve been wanting to get each records name field to be clickable so as to open up the Salesforce record. I’ve had some useful feedback at this link, but it is definitely beyond my limited skills. Has anyone achieved this?
    http://boards.developerforce.com/t5/Visualforce-Development/Custom-search-page-development/m-p/291437/highlight/false#M36834

  47. Justyn Pride

    One big question – does this need apex testing to be deployed from a sandbox to production environment? is there any helpful kick start that could be provided for this?

  48. Oded

    Cool demo.

    I was trying to do the same and have it appear as the Home tab for guest users but couldn’t find how to achieve that.
    How was this done?

  49. Mike Melnick

    Justyn,

    Making the list editable is pretty simple if you are flexible. You can add a new column and allow the user to click on that column to edit the record. Something like this added to the VF page at line 68:

    Edit

    Will present the word “Edit” and allow your users to click and be taken to a new page with the contact’s details for editting.

    Sorry, I can’t help you with a test class, perhaps someone else has something to post here.

  50. Jeff Douglas

    @Oded, you are going to be severely limited to what you can put on the home tab. A custom tab is where you will want to put it.

  51. Charlie Lang

    Hi,
    I’m getting the following error while trying to create an apex class.

    Error: Compile Error: expecting a left angle bracket, found ‘contacts’ at line 6 column 14

  52. Charlie Lang

    Hi Jeff.

    Can you tell me how i would be able to turn each line of the results into a link that goes to the contact page?

    i resolved the last issue

  53. Jame

    Very awesome ! I am having an issue though. For some reason I cannot get any of my Account custom fields to display in the If I change to basic fields such as Phone or Name it will display them just fine. Any ideas ?

  54. Isaac Krig

    Click-sorting the ‘Technology’ column yields a blank screen…

  55. Isaac Krig

    Should have also mentioned this is super useful, I stay close, thanks so much for making this kind of thing available. I own most of your books ;-) keep up the good work.

  56. Jame

    Anyone able to come up with a test method for this. I have this working perfectly in a sandbox env but am having trouble with the test method

  57. Gopal

    Awesome dude….. u are really a awesome salesforce coder…. i m learning salesforce through ur code only… good wrk….

  58. Nanthagopal

    It’s really superb……… but i have one doubt, is there any possibilities to display the Notes&Attachments while searching..

  59. Relan

    Anyone that have test controller for this?

  60. Richard D

    I cribbed this code, made some minor changes (the name of the controller and some of the fields). I am having a bear of a time creating a test method. Does anyone have a sample file that I can look at and learn from? Thanks so very much.

  61. Jeff Douglas

    @Nanthagopal, perhaps but it would take some extra code and may become rather messy.

  62. Jeff Douglas

    @Jame, I would check the field level security and make sure the running user has access.

  63. Jeff Douglas

    @Charlie, look at using an apex:outputLink tag for each one.

  64. Annu

    Thanx a lot for Superb search page.I solved my problem.

  65. Justyn Pride

    I don’t know if this link is of help, but these is a copy of the test controller that someone created for me.

    http://boards.developerforce.com/t5/Visualforce-Development/Custom-search-page-development/m-p/292647/highlight/false#M36908

  66. Justyn Pride

    Any advice on the controller and visualforce page code to use for checkboxes? I have everything else working perfectly apart from these particular field types.

    Thanks

    Justyn

  67. Justyn Pride

    I’m wanting to make my working page visible to the outside work. Any helpful notes on the steps to go through?

    Thanks in advance

    Justyn

Leave a comment

Feed

http://blog.jeffdouglas.com / Building a Dynamic Search Page in Visualforce

WordPress Appliance - Powered by TurnKey Linux