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: https://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.

Advertisements

63 thoughts on “Salesforce Javascript Remoting, jQuery and Autocomplete

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

  2. 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.

    • 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

  3. 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?

    • 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.

      • 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!

      • 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.

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

    • 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!

  5. 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.

    • 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!

  6. 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

    • 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.

      • 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 .

  7. 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

    • 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:

  8. 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.

    • 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;
      },

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

    • 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, 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.

  10. 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

    • 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.

    • 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.

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

    • 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.

  12. 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.

    • 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 &)

  13. 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

  14. 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

  15. 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

    • 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

      ||

      ||

  16. Hi,
    I wanted to know which Piece to change If I have to make it work for the first character as well, currently I have to enter 2 characters to make it work.

  17. Hi

    Nice work on this. I had one question.

    On IE, FF, Safari the type ahead suggestion list works as expected (uses the Jquery theme to display in a list with colored background). On Chrome, it appears as a bulleted list with transparent background. Any ideas why the CSS does not pick up on Chrome (22.0.1229.94) on Win 7. I’ve also tried on a Mac with the same issues.

    I am using the following as includes:

    thanks
    Craig

    • I just tried it on Chrome on my Mac and it works. The comment cut out your includes, but try updating your includes.


      <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js"/>
      <script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/jquery-ui.min.js"/>
      <apex:stylesheet value="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.18/themes/ui-lightness/jquery-ui.css"/>

      • Hi

        Thanks for your quick reply. Its working now, I figured out the issue. In the include code you had in your original post, the Google CDN stylesheet include was http. Changing to https fixed the issue.

        Craig

  18. Pingback: Email AutoComplete (using jQuery) | WikiCloud

  19. Hi Daniel: Many thanks for sharing your solution. I was able to follow your instruction to implement the solution in our org although I am a 1000% dummy with both Salesforce and programming.

    Your solution works like a charm with Account auto-complete. However I was not able to make it working with other lookup fields.

    For example:
    This is the code that I use for Account auto-complete, which works fine

    This is the code that I use for Contact auto-complete, which doesn’t propose any auto-complete list

    Please kindly help!

    Thanks and wish you a very Happy 2013!

    Bing Maletz

    • Here again the code ( I removed all starting/ending brackets)

      This is the code that I use for Account auto-complete, which works fine

      apex:inputfield value=”{!eQuote__c.Soldto_Account__c}” id=”Account”
      c:AutoComplete objectname=”Account” additionalfield=”SAP_Customer_ID__c” autocomplete_textbox=”{!$Component.account}”
      apex:inputfield

      This is the code that I use for Contact auto-complete, which doesn’t propose any auto-complete list

      apex:inputfield value=”{!eQuote__c.Contact__c}” id=”Contact”
      c:AutoComplete objectname=”Contact” autocomplete_textbox=”{!$Component.Contact}”
      apex:inputfield

      • I found the solution.

        1) For the Contact lookup, I initially set “Account” as “additionalfield”, which doesn’t work. I changed it to “Email”, then it works. I assume that the “additionalfield” can not be a lookup field

        2) For the other lookup fields, I initially omitted “additionalfield”, then it doesn’t work. After I added an “additionalfield”, then it works.

        Now everything works and it looks really gorgeous..

        Thanks again!

  20. Your code works so perfectly !
    I am getting & for & and " for double quotes in account fields when i see them in autocomplete drop down.

    I dont know why these characters are getting converted automatically to the special character form and not getting converted back to their original form. Any clue?

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 )

Google+ photo

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

Connecting to %s