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














Snazzy work. Love the debug window too.
Nice, as always
Thanks for putting this together.
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.
Well played Richard!!
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.
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.
Wicked cool.
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.
Hi I am trying to filter contact records by user territory. Any thoughts of how to do it via custom code.
I like the header sort, however, your Technologies sort does not appear to work
I think it has something to do with the multi-select picklist field. Thanks.
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!
@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+’\”;
@Chris L
Thank you! That worked perfectly.
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?
@Christian, have you tried ordering the query results in some manner? Perhaps by name date created?
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
@David, I am actually working on a custom search dialog. Stay tuned.
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?
thnks for this coooll app..good work..
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
@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.
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.
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!
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
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?
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
@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?
@Rob, is your method even getting fired? Try debugging to see if any results are being returned from your query.
@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.
@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.
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
Just the stuff I was looking for, wonderful blog!
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
Awesome!!!
once again.. jeff. i have no words
thanks for the post.
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
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
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.
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
Have just recreated it all from scratch and definitely get the same problem. Very strange.
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
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
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.
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.
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
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?
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?
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.
@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.
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
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
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 ?
Click-sorting the ‘Technology’ column yields a blank screen…
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.
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
Awesome dude….. u are really a awesome salesforce coder…. i m learning salesforce through ur code only… good wrk….
It’s really superb……… but i have one doubt, is there any possibilities to display the Notes&Attachments while searching..
Anyone that have test controller for this?
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.
@Nanthagopal, perhaps but it would take some extra code and may become rather messy.
@Jame, I would check the field level security and make sure the running user has access.
@Charlie, look at using an apex:outputLink tag for each one.
Thanx a lot for Superb search page.I solved my problem.
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
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
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