Salesforce Javascript Remoting, jQuery and Autocomplete

I was very excited when I found out that Salesforce was releasing Javascript remoting with Spring 11. There have been several cases where having access to Apex classes and logic in Javascript would make my development much easier. One area that I wanted to try out was in an autocomplete component that I built a while ago. I had hacked together a component that used a jQuery autocomplete plugin. While it worked, I had a couple of problems: it didn’t work in IE8 (everybody uses Chrome or Firefox, right?) and the autocomplete consumed a separate Visualforce page, so when I had developer mode turned on, it would choke on the wrapper that developer mode uses.

The Javascript Remoting feature is currently in Developer Preview, so my first step was to get it enabled for my org. A quick tweet to Josh Birk solved that. [Edit: As of Summer '11, it is GA.] Using Josh’s blog post and the Salesforce documentation as a reference, I got to work.

First I created the controller. I made this controller as flexible as possible. You can pass it the API object name (Account, Contact, etc), the search string and any additional fields you want to include in the search. It will always search the Name field for the search string.

[Edit: I have created a new controller that uses apex-lang to build the Soql. You can find it in my blog post: http://verticalcode.wordpress.com/2011/02/21/using-apex-lang-to-build-soql-statements/.]

global class autoCompleteController {
    @RemoteAction
    global static SObject[] findSObjects(string obj, string qry, string addFields) {
        // more than one field can be passed in the addFields parameter
        // split it into an array for later use
        List<String> fieldList;
        if (addFields != null) fieldList = addFields.split(',');
       // check to see if the object passed is valid
        Map<String, Schema.SObjectType> gd = Schema.getGlobalDescribe();
        Schema.SObjectType sot = gd.get(obj);
        if (sot == null) {
            // Object name not valid
            return null;
        }
        // create the filter text
        String filter = ' like \'%' + String.escapeSingleQuotes(qry) + '%\'';
        //begin building the dynamic soql query
        String soql = 'select id, Name';
        // if an additional field was passed in add it to the soql
        if (fieldList != null) {
            for (String s : fieldList) {
                soql += ', ' + s;
            }
        }
        // add the object and filter by name to the soql
        soql += ' from ' + obj + ' where name' + filter;
        // add the filter by additional fields to the soql
        if (fieldList != null) {
            for (String s : fieldList) {
                soql += ' or ' + s + filter;
            }
        }
        soql += ' order by Name limit 20';
        List<sObject> L = new List<sObject>();
        try {
            L = Database.query(soql);
        }
        catch (QueryException e) {
            return null;
        }
        return L;
   }
}

Next I created the component. Note that the component requires some jQuery resources which I pulled from the jQuery UI website and then uploaded the entire zip file into my resources in my org. [EDIT - I've found it is easier to just use Google's CDN for the jquery files. You can replace the <apex:includeScript> section with this instead]:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"/>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.12/jquery-ui.min.js"/>
<apex:stylesheet value="http://ajax.googleapis.com/ajax/libs/jqueryui/1.8.12/themes/ui-smoothness/jquery-ui.css"/>

I also chose to use a spinner type indicator to queue the user that the autocomplete is working, which I uploaded as a resource.

<apex:component controller="autoCompleteController">
  <!-- JQuery Files -->
  <apex:includeScript value="{!URLFOR($Resource.jqueryui189, 'js/jquery-1.4.4.min.js')}" />
  <apex:includeScript value="{!URLFOR($Resource.jqueryui189, 'js/jquery-ui-1.8.9.custom.min.js')}" />
  <apex:stylesheet value="{!URLFOR($Resource.jqueryui189,'css/smoothness/jquery-ui-1.8.9.custom.css')}"/>
  <!-- Attributes Required For Component -->
  <apex:attribute name="objectname" description="The object name you want to look for."     type="String" required="true"/>
  <apex:attribute name="additionalfield" description="Any additional fields you'd like to search and include in the display."     type="String" required="false"/>
  <apex:attribute name="autocomplete_textbox" description="The ID for the Autocomplete List Textbox."     type="String" required="true"/>
  <style>
    .ui-autocomplete-loading { background: white url({!$Resource.circleIndicator}) right center no-repeat; }
  </style>
  <script type="text/javascript">
    var j$ = jQuery.noConflict();
    j$(document).ready(function() {

        var sObjects;
        var queryTerm;

        j$(esc('{!autocomplete_textbox}')).autocomplete({
            minLength: 2,
            source: function(request, response) {
                        queryTerm = request.term;
                        autoCompleteController.findSObjects("{!objectname}", request.term, "{!additionalfield}", function(result, event){
                            if(event.type == 'exception') {
                                  alert(event.message);
                            } else {
                                 sObjects = result;
                                 response(sObjects);
                            }
                        });
                   },
            focus: function( event, ui ) {
                    j$(esc('{!autocomplete_textbox}')).val( ui.item.Name );
                    return false;
                    },
            select: function( event, ui ) {
                        j$(esc('{!autocomplete_textbox}')).val( ui.item.Name );
                        j$(esc('{!autocomplete_textbox}_lkid')).val( ui.item.Id );
                        j$(esc('{!autocomplete_textbox}_lkold')).val( ui.item.Name );
                        return false;
                    },
         })
         .data( "autocomplete" )._renderItem = function( ul, item ) {
            var entry = "<a>" + item.Name;
            j$.each("{!additionalfield}".split(",") , function(key, value) {
                entry = entry + " " + item[value];
            });
            entry = entry + "</a>";
            entry = entry.replace(queryTerm, "<b>" + queryTerm + "</b>");
            return j$( "<li></li>" )
                .data( "item.autocomplete", item )
                .append( entry )
                .appendTo( ul );
        };
    });

    function esc(myid) {
           return '#' + myid.replace(/(:|\.)/g,'\\\\$1');
    }

  </script>
</apex:component>

Finally, I can use the component in any Visualforce page to autocomplete on any field. In the below example, I’m looking for account name or ticker symbol:

    <apex:page>
    <apex:form><br />
        <apex:inputText id="account">
            <c:AutoComplete2 objectname="Account" additionalfield="TickerSymbol" autocomplete_textbox="{!$Component.account}" />
        </apex:inputText>
    </apex:form>
</apex:page>

Edit: Added link to code for a new controller using apex-lang and a little bit more error handling.

53 Responses to Salesforce Javascript Remoting, jQuery and Autocomplete

  1. Gene says:

    I spent all week writing something like this myself…yours is better so I’m using yours. Thanks.

  2. Josh says:

    Very cool!

  3. Pingback: Using apex-lang to build Soql statements | Vertical Code

  4. Gene says:

    Daniel, Can you include the necessary items from your jQuery UI zip? That way, if I want to customize a download I don’t need to grab everything. TIA.

    • Daniel says:

      Hi Gene,

      I’m not sure of all the dependencies, but I think you can grab just the UI Core and the autocomplete widget. I used the smoothness theme, but you could use any theme or build your own. I just pulled the quick download because I figured I’d want to play with everything eventually.

      Daniel

  5. Mauricio says:

    After copying your code verbatim and modifying only the jQuery references, I get a brief spinning wheel when I enter text and then…nothing. No error, no results. Ideas? Anyone? Bueller? Bueller?

    • Daniel says:

      Hi Mauricio,

      If there are no matches, then that’s what happens. It sounds like you’ve got jQuery working if you have the spinning wheel starting and stopping. Since it starts and stops, it makes me think the javascript call is complete or failed. Did you ask Salesforce to enable Javascript Remoting for your org? I’d try putting it in debug and stepping through the javascript to see if any errors appear.

      • Mauricio says:

        Yes, JS remoting is enabled. The code did not compile until SFDC switched that on. And there ARE records that should be coming up. Thanks!

      • Daniel says:

        You can try looking at my other post with some updated code for this component using apex-lang. It has better error handling that you might be able to take out and use if you don’t want to use apex-lang to build the SOQL.

  6. Mauricio says:

    Weirdly enough – this code works if a debugger (Firebug) is attached but hangs if it is not.

  7. Mauricio says:

    So here’s the scoop: Works on IE8 and Chrome, hangs on Firefox 3.6.xx unless Firebug is running. Grr. NOT your bad!

    • Daniel says:

      Huh! That’s one of the reasons I used jQuery for this – they are supposed to have all those cross browser problems worked out. Good to know. I’ve got Firefox 4.0 and it works. Glad you got the code working. I hope it helps!

  8. Mauricio says:

    I presume that these lines are supposed to populate the named fields with the named values? If the fields exist on the page?
    select: function( event, ui ) {
    j$(esc(‘{!autocomplete_textbox}’)).val( ui.item.name );
    j$(esc(‘{!autocomplete_textbox}_lkid’)).val( ui.item.Id );
    j$(esc(‘{!autocomplete_textbox}_lkold’)).val( ui.item.name );
    return false;
    },

    Oddly, the first line works but the latter two do not.

    • Daniel says:

      Yes, that’s correct. The second two lines are used if it is a lookup field. These are hidden fields that if aren’t filled in and there are multiple matches on the lookup field, it will redisplay the page asking you to select the correct one. For example, say the auto complete for a contact returns back John Smith and you have two John Smiths in the database, it would redisplay asking which one you want to use if the lkid and lkold are not populated.

      I’m not sure why they don’t fill in for your case – I only know enough jQuery to get me in trouble!

  9. krish says:

    Hi Daniel,

    I used your code it is excellent. but in lookup if i enter name it was showing undefiend with name of account. How to remove that one already record exist with that name. And after selecting the value of searched dropdown list i want execute action funtion method, which event will fire after selecting the record value.I tried onselect,onchange not working.

    I didn’t get jquery files which were you used, i am using jquery-1.5.1.min.js,jquery-ui-1.8.14.custom.min.js,css/south-street/jquery-ui-1.8.14.custom.css

    Give me suggestion on this

    thank you in advance
    krish

    • Daniel says:

      Hi Krish,

      I’m on vacation for the next week and don’t have my computer with me. I’ll take a look at this when I get back and let you know.

      • krish says:

        Hi Daniel,

        I am unable to getting Product lookup related records in the searched dropdown list. see below my code :

        It is very urgent to client. please suggest on this .

  10. krish says:

    Hi Daniel,

    Previous I posted apex code but it is not visible see my code

    apex:inputField value=”{!a.Product__c}” onkeypress=”return noenter(event);” id=”prod”
    c:autoCompleteController objectname=”Product2″ additionalfield=”TickerSymbol” autocomplete_textbox=”{!$Component.prod}”
    /apex:inputField

    thanks for reply
    krish

    • Daniel says:

      The additionalfield parameter doesn’t look right. If you need to query a second field you use this, otherwise, leave it out. There is no field in Product2 called TickerSymbol.

      In regards to your question on jquery files, I’ve since found that it is easier to use Google’s hosted jquery code rather than use static resources. Replace it with this:

  11. Daniel says:

    Ah, WordPress ate my code too! I’ve edited the post with the additional info.

  12. Manan says:

    Hi
    I want to display Additional field in textbox which is coming from jquery.
    I have used the same code.
    But I get only the Name value of object.
    Please guide me.

    • Daniel says:

      The example code will the add additional fields to the end for the autocomplete, but when you select the item, it only puts the name of the object into the textbox. I’m doing this because in my case, I was using it on a lookup field and didn’t want it to cause problems on the save. If you don’t have a lookup and just a textbox, then you could add the additional fields You’d need to modify this part of the code. This is where I am putting the values of the selected item into fields on the page.
      select: function( event, ui ) {
      j$(esc('{!autocomplete_textbox}')).val( ui.item.Name );
      j$(esc('{!autocomplete_textbox}_lkid')).val( ui.item.Id );
      j$(esc('{!autocomplete_textbox}_lkold')).val( ui.item.Name );
      return false;
      },

  13. Bo Laurent says:

    The variable “cl”, referenced as below, seems to be undefined.

    var ele=document.getElementById(cl);
    ele.click();

  14. Pingback: Email AutoComplete (using jQuery) | Perspectives on Salesforce.com

  15. Ravi says:

    This is not working in IE8 . Any solution for this.

    Most of the users are using IE8. if any solution we can use this.

    thanks,
    RR.

    • Daniel says:

      Hi Ravi,

      I don’t have access to IE8 anymore, but it did work when I tested it a while ago. Have you tested in other browsers? Have you confirmed that JavaScript is enabled in your browser? Since it uses jQuery, I don’t have any alternatives. They usually do a good job of supporting all the browsers.

    • Daniel says:

      I was just able to test it on IE8 and the autocomplete worked for me. I’m not sure why it isn’t working for you. Sorry I can’t be more help.

      • G. Frrank says:

        Daniel, I had everything set correctly. What it ended up being is that I had to prefix the Apex class with my namespace. Working now!! Thanks for your help.

  16. G. Frrank says:

    Hello,
    Everything builds, but when I am executing the script on my webpage, I am receiving the JavaScript error “‘autoCompleteController’ is undefined” on the autoCompleteController.findSObjects(, I am running API Version 23.0. Any ideas on what this could be?

    Any help would be appreciate.

    G. Frank

    • Daniel says:

      Have you created your Apex class and made sure to call it autoCompleteController? JavaScript is case sensitive, so check your upper and lower case. If that isn’t it, then I’m stumped.

    • Mauricio Parra says:

      This is a SalesForce known issue that was introduced with W’12 that they have apparently yet to issue a patch for. They gave us two workarounds:

      1) “A workaround is for the customer to manually create the Javascript proxy w/ the known namespace.
      if (MyNs) {
      window.MyNs = {};
      window.MyNs.MyController = MyController;
      }”
      We did not use this one.

      2) Add empty constructors to the top of the class you are using to control the autocomplete (ie the one with the @RemoteAction notation). For example:

      // SFDC TEST – Added empty constructor to allow class to be used as a VF controller extension
      global AutoCompleteController() {}
      global AutoCompleteController(ApexPages.StandardController controller) {}

      where “AutoCompleteController” is YOUR class name, then add that class name to the extensions list on the VF page(s) you have the autocomplete “widget” on.

      We used this one and it works.

  17. Rafi says:

    Hello,
    Where can on get he circleIndicator resource from?

  18. Rafi says:

    Hello,
    It seems that the variable ‘cl’ in ‘var ele=document.getElementById(cl);’ is undefined..
    Am I missing something?

  19. Rafi says:

    Daniel,
    The list of items for the user to select from is generated as links below the input box.
    What is the reason that you didn’t choose a more “traditional” approach, such as the one demoed on http://docs.jquery.com/UI/API/1.8/Autocomplete#overview (or Google search for that matter)?

    Thanks,

    • Daniel says:

      Rafi,
      If you are seeing the list of items, then it sounds like the correct CSS isn’t being used. Make sure you’re either referencing the hosted style sheet by jQuery or uploading one to your your resources in Salesforce.

  20. Rafi says:

    Daniel,
    I found another interesting issue in the code below:
    entry = entry.replace(queryTerm, “” + queryTerm + ““);
    It is basically case sensitive, so the BOLD works only on exact match,where as the look-up is not case sensitive.
    so for example, if you type “un” the item “United Oil” will not get any bold, wheres “Get united” will.

    • Rafi says:

      I believe this fix may work for most cases:
      entry = entry.replace( new RegExp( “(” + queryTerm + “)” , ‘gi’ ), “$1” );

      Will not work well if special characters are used in the search string (such as &)

      • Daniel says:

        Thanks Rafi! I meant to put a disclaimer in that the replace wasn’t very robust. Great solution!

      • Guy says:

        I had to make it:

        entry = entry.replace( new RegExp( “(” + queryTerm + “)” , “gi” ), “$1” );

        to get it to work properly..

  21. Mausam Padhiyar says:

    i am not able to get circle-Indicator. Can anyone help me out?
    I have downloaded jquery zip file and set as static resource. But no luck.

    Error: Static Resource named circleIndicator does not exist. Check spelling

  22. Aldo says:

    Hi Daniel,

    I’m receiving this error:

    Uncaught TypeError: Cannot set property ‘_renderItem’ of undefined

    Something wrong?

  23. Aravinda says:

    Hi Daniel,

    When i omitted the additionalfield paramter, the query builder did not handle the space. It was forming a query with “,” immediately followed by “from”. So I added the condition in autoCompleteController for handling spaces and it worked.

    if (addFields != null && addFields != ”)

    Aravinda

  24. Herianto Marbun says:

    Hi Daniel,

    i have a custom object named Product_Master. when i use this to your

    it returns null, for this

    Map gd = Schema.getGlobalDescribe();
    Schema.SObjectType sot = gd.get(obj);
    if (sot == null) {
    System.debug(‘object null’);
    // Object name not valid
    return null;
    }

    i have no idea why this code returns null; for your note, when i change the objectname to Account

    it works.

    please help..

    thanx for your attention

    • Herianto Marbun says:

      sorry the apex code wasnt written in my recent comment

      “i have a custom object named Product_Master. when i use this to your”

      ||

      ||

      i have no idea why this code returns null; for your note, when i change the objectname to Account

      ||

      ||

    • Daniel says:

      Hi Herianto,

      Are you using the API name for the object? It should have __c at the end. Try using Product_Master__c.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.