Email AutoComplete (using jQuery)

For several years, I’ve been using an app called Email AutoComplete (it’s now a private listing) to add auto complete capabilities onto my email address text boxes in the Salesforce email editor.  It’s a nice little app.  However, Arrowpointe started using Email to Case Premium (an app I highly recommend) and I wanted to have similar capabilities on their forms.

The Email AutoComplete app works well, but it written using s-Controls and Yahoo User Interface Library v2, which adds a lot of confusing code into it. It was too hard for me to change and apply to the Email to Case Premium pages.  I looked for a jQuery approach and leveraged ideas from a great blog post on the Vertical Code blog. In my case, however, I was trying to add this capability onto an AppExchange “managed” (i.e. locked down) page and onto the standard email forms from Salesforce. I didn’t have the luxury of an Apex Controller and Visualforce.

Enter the AJAX API and jQuery!

Question: How do I obtain the Session Id?

Answer: Create a Visualforce page to act as a JavaScript file. In this case, I am creating a global JavaScript variable called _my_sfdcSession.

<apex:page sidebar="false" showHeader="false" contentType="application/javascript" cache="false">
  var _my_sfdcSession = "{!$Api.Session_Id}";
</apex:page>

Question: How do I enable API access in Salesforce and turn on jQuery so its usable whereever I am (almost) in Salesforce?

Answer: Use the sidebar to inject the code. I use the Messages & Alerts sidebar item, but the same could also be done using a custom sidebar component. The key is that the component always be in the sidebar.

The code below loads the Visualforce page above to get the Session Id. Then it loads the AJAX toolkit (from Salesforce), jQuery (from Google CDN) and jQueryUI (from Google CSN). Note that the jQuery instance is put into a new variable called $_org using the jQuery.noConflict() method. I chose that variable name because I am assuming it won’t conflict with other solutions that rename jQuery.

<!-- Load Session Id -->
<script src="https://na1.salesforce.com/apex/loadSessionId?core.apexpages.devmode.url=1"></script>
<!-- Load AJAX Toolkit -->
<script src="https://na1.salesforce.com/soap/ajax/23.0/connection.js" type="text/javascript"></script>
<!-- Load jQuery -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js"></script>
<script type="text/javascript">
var $_org = jQuery.noConflict();
</script>
<!-- Load jQuery UI -->
<link rel="stylesheet" href="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/themes/base/jquery-ui.css" id="theme" />
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.8.16/jquery-ui.min.js"></script>

NOTES:

  • If you use the Messages and Alerts sidebar component, do not put empty lines in your code because the system will add a p html tag in it
  • Use absolute references to your Salesforce pages because managed packages have a different url structure

Question: How do I implement the AutoComplete part?

Answer: Now that jQuery is loaded and we have access to the Salesforce API, we can get down to actual business. The code I am personally using is below. Not being super confident with jQuery selectors, I am hardcoding the element names to add auto complete to into my code. The downside of this is if those element names change, it will stop working, but it’s an easy fix.

This code gets a list of element Ids and then loops through them, adding autoComplete (from jQuery UI) to each one. The “source” property of autoComplete uses the Salesforce API to look for Contacts with a name or email address LIKE what is being typed in. In this case, it is handling the situation where you already have multiple names separated by ; in the email field. You will also notice the line saying sforce.connection.sessionId = _my_sfdcSession;. This line associates the Session Id from that initial page we created to the AJAX API.

I have this code stored as a Static Resource called “AutoComplete_Email”.

$_org(document).ready(function() {
	
	// The elements to bind email autocomplete to
	var elems = [];
	
	// Add Email to Case Premium elements
	elems.push('pg:addCommentF:addCommentPB:emailCustomerPBS:additionalEmailsPBSI:additionalEmails_TextBox');
	elems.push('pg:addCommentF:addCommentPB:emailCustomerPBS:additionalCCsPBSI:additionalCCs_TextBox');
	elems.push('pg:addCommentF:addCommentPB:emailCustomerPBS:additionalBCCsPBSI:additionalBCCs_TextBox');
	
	// Add default Salesforce email fields
	elems.push('p24'); // Additional To
	elems.push('p4'); // CC
	elems.push('p5'); // Bcc
	
	$_org(elems).each(function(index) {
		var thisElem = document.getElementById(elems[index]);
		$_org(thisElem).autocomplete({
			minLength: 1,
			delay: 250,
			
			source: function(request, response) {
						
						var retVal = []; // array to return
						
						var queryTerm = $_org.trim(request.term); // term to search for
						if (queryTerm.lastIndexOf(';') != -1){
							queryTerm = queryTerm.substring(queryTerm.lastIndexOf(';') + 1);
							queryTerm = $_org.trim(queryTerm);
						}
						
						if (queryTerm.length <= 1){ 
							$_org(thisElem).autocomplete("close");
						} else {
							sforce.connection.sessionId = _my_sfdcSession;
							var result = sforce.connection.query("SELECT Id, Name, Email FROM Contact WHERE (Name LIKE '%" + queryTerm + "%' OR Email LIKE '%" + queryTerm + "%') AND Email != NULL ORDER BY Name LIMIT 20");
							it = new sforce.QueryResultIterator(result);
							while (it.hasNext()) {
								var rec = it.next();
								var retValItem = new Object();
								retValItem.label = rec.Name + ' (' + rec.Email + ')';
								retValItem.value = rec.Email;
								retVal.push(retValItem);
							}
						}
						response(retVal);
					},
				   
			select: function( event, ui ) {
						var tmp = $_org(thisElem).val();
						if (tmp.lastIndexOf(';') == -1){
							$_org(thisElem).val( ui.item.value + '; ' );
						} else {
							$_org(thisElem).val( tmp.substring(0, tmp.lastIndexOf(';') + 1) + ' ' + ui.item.value + '; ');
						}
						$_org(thisElem).autocomplete("close");
						return false;
					},
					
			focus: function(event, ui) {
				return false; // added so keyboard navigation does not overwrite the value
			}

		 });
		 
	 });
	 
}); // end of $_org(document).ready()

Question: Now how do I get this autoComplete code injected into the page?

Answer: Add some more code to the sidebar. The key parts are lines 6-9 below. This injects the script into the page. I personally make sure its only injected into the pages I want it to, which are the standard email pages in Salesforce and the Email to Case Premium “New Comment” page.

<script type="text/javascript">
if (
document.URL.toLowerCase().indexOf("emailauthor")!=-1 || 
document.URL.toLowerCase().indexOf("/apex/new_comment")!=-1
){
	var elem = document.createElement('script');
	elem.type='text/javascript';
	elem.src='/resource/AutoComplete_Email';
	document.body.appendChild(elem);
}
</script>

Question: What does this look like in the end?

Answer: Below are 2 examples. There’s obviously more you can do with CSS. These are using the basic CSS from jQuery UI.

Salesforce Email Page

Email to Case Premium’s New Comment

21 Comments

  1. Jeremy Ross Said,

    November 18, 2011 @ 11:30 am

    I usually manage to get the “AJAX API” to self-init. The last few lines of connection.js:

    sforce.connection.serverUrl = (typeof window.UserContext != “undefined”) ? UserContext.getUrl(“/services/Soap/u/22.0”) : “/services/Soap/u/22.0”;

    if (typeof(__sfdcSessionId) != “undefined”) {
    sforce.connection.sessionId = __sfdcSessionId;
    }

  2. Scott Hemmeter Said,

    November 18, 2011 @ 11:34 am

    @jeremy, nice comment. When I implemented it, I was thinking I should try and make this instance of the AJAX toolkit completely independent of any other that could get loaded onto a page (e.g. an AppExchange app uses it too). That’s why I made my own Session Id variable and also my own jQuery instance. Not sure if I did it the “right” way or not. I couldn’t figure out how to make my sforce object instance into a unique name, but I would if I could. Just to make this implementation of jQuery and AJAX toolkit independent of any other possible instances. Thoughts?

  3. Chad Meyer Said,

    November 18, 2011 @ 11:56 am

    Scott,

    Great post, thank you! We’ll be looking at rolling this (or something similar) into Email to Case Premium in Q1 2012 after we get some other feature requests out of the way.

    Cheers,

    Chad

  4. Jeremy Ross Said,

    November 18, 2011 @ 11:59 am

    The AJAX toolkit creates the global variable “sforce” and that’s what everyone uses. I think this is only an issue if different versions of the script are loaded; They all use the same global variable name.

    I’m also not sure you’re creating your own instance of jQuery. When you use noConflict(), you’ll still be using the same jQuery instance that any scripts that came before you used. If something comes along later and reloads jQuery with a new tag, it will get its own instance, I think. The fact that you’ve used noConflict() is what really matters here.

  5. Daniel Hoechst Said,

    November 18, 2011 @ 2:54 pm

    That’s some nice JavaScript trickery! Definitely tucking this away for future reference. I’m glad my post on autocomplete helped and thanks for the mention. Does JavaScript remoting not work in this situation because of the managed package? On the one hand, it is nice to have it all in JavaScript, but then you can’t write tests to make sure everything works from release to release.

    I’ve also found that with my blog posts with pure JavaScript, people have a real hard time debugging problems since there is no compile at page save to catch problems.

  6. Scott Hemmeter Said,

    November 18, 2011 @ 3:43 pm

    @Daniel, in this situation I am trying to add auto-complete onto someone else’s work. In one case, it’s the standard Salesforce pages. In another it’s an AppExchange app that is managed and whose page is locked down. This solution is acting more like a greasemonkey script where it modifies someone else’s app pages.

  7. Andrew Waite Said,

    November 19, 2011 @ 11:50 am

    Please keep in mind this solution is subject to breakage whenever there is a change to the standard salesforce UI, this partner’s UI implementation or the ability to inject script.

  8. Scott Hemmeter Said,

    November 19, 2011 @ 12:10 pm

    @andrew yes, the solution is pretty hardcoded, but easy to manage for a Dev if they choose to. Is Salesforce considering stopping script injection like this? If so, what would stop a browser extension or greasemonkey script from doing the same thing?

  9. Force Feed 11-21-2011 | Force Architects: Delivered Innovation Blog Said,

    November 21, 2011 @ 11:19 am

    […] Email AutoComplete (using jQuery) @arrowpointe Q&A for using Email to Case Premium email application. […]

  10. Scott Hemmeter Said,

    November 28, 2011 @ 8:40 am

    I updated the code in the post on 11/28 to add a small bit of code to the autocomplete’s “focus” event. Without this, the value in the email field gets overwritten when the items are navigated with the keyboard.

  11. teejay Said,

    February 21, 2012 @ 8:58 am

    This is great stuff! I got this working on my org, however it won’t work when using the new customer service console! Any ideas how to get it working?

  12. Scott Hemmeter Said,

    February 21, 2012 @ 9:25 am

    @teejay, there are a few hardcoded areas in the code. You’ll need to look into the equivalents in the console. In the sidebar code, it has an IF statement checking on the URL so it only runs on the right pages. You’d need to include the URL of the console into that mix. In the main JavaScript, you’ll need to do more elems.push statements to include the IDs of the fields you want auto-complete on so that code actually applies autoComplete to those.

    The solution I did is pretty hardcoded to the current pages and needs to be modified if Salesforce changes the UI or you want it on new pages.

  13. teejay Said,

    February 21, 2012 @ 1:42 pm

    I had already modified the code to work on some different fields, using firebug to find the id of the field. However when the email edit page is shown in the cloud console it seems to be wrapped in an iframe which prevents firebug from finding the id for the field. Also the javascript in the sidebar probably doesnt work in the cloud console as the url in the addressbar never seems to change. In fact with the cloud console I’m asking myself if the javascript in the sidebar still works at all, as there is no option to display the sidebar. …

  14. Scott Hemmeter Said,

    February 21, 2012 @ 3:17 pm

    @teejay, the sidebar is the trick to getting the entire bit of code executed at all. If the sidebar is not executed, you can manually execute it in Firebug to make sure it works. Then its a matter of finding a place to put code so that it executes in the console. I’ve never really used the console so I am not sure where to put that.

  15. Force Feed 11-21-2011 | Delivered Innovation Blog Said,

    July 17, 2012 @ 12:26 pm

    […] Email AutoComplete (using jQuery) @arrowpointe Q&A for using Email to Case Premium email application. […]

  16. John G Said,

    February 8, 2013 @ 9:16 am

    Great article.

    One thing. I don’t get how you inject the code into the sidebar?

    Also, for
    elems.push(‘pg:addCommentF:addCommentPB:emailCustomerPBS:additionalEmailsPBSI:additionalEmails_TextBox’);

    Obviously I would have to change this to the form field in my page. But why is the string so long. Could you just enter the control Id?

  17. Paul Said,

    August 12, 2013 @ 10:29 am

    I’ve set this up in a dev org and it works great for the send email page in Salesforce. I’m trying to add it to a custom visualforce page and avoid using the ID, since the ID of the field could change if I move it around on the visualforce page (thanks to visualforce’s strange automatic IDs).

    How do I change it so that it could select using classes instead? I’ve tried the following but I’m getting a DOM Exception 12.

    Here’s the section of the AutoComplete_Email resource that I changed:

    // Add default Salesforce email fields
    elems.push(‘p24’); // Additional To
    elems.push(‘p4’); // CC
    elems.push(‘p5’); // Bcc

    // Visualforce pages can just put the class .EmailAutocomplete on any inputs
    elems.push(‘.EmailAutocomplete’);

    $_org(elems).each(function(index) {
    var thisElem = document.querySelectorAll(elems[index]);

    Here’s the error:
    Uncaught Error: SyntaxError: DOM Exception 12 AutoComplete_Email:20
    (anonymous function) AutoComplete_Email:20
    e.extend.each jquery.min.js:2
    e.fn.e.each jquery.min.js:2
    (anonymous function) AutoComplete_Email:19
    n jquery.min.js:2
    o.add jquery.min.js:2
    e.fn.e.ready jquery.min.js:2
    (anonymous function) AutoComplete_Email:1

  18. Scott Hemmeter Said,

    August 12, 2013 @ 10:49 am

    Paul, the key is the line that says

    var thisElem = document.getElementById(elems[index]);

    That is looking for the element using its index. If you use classes instead, you’d go about determining thisElem using jQuery’s class selectors.

  19. Paul Said,

    August 12, 2013 @ 11:01 am

    For the sake of time and my lack of javascript experience I added another section after the for loop with this:

    var Elements = document.querySelectorAll(‘.EmailAutocomplete’);
    var thisElem = Elements[0];
    $_org(thisElem).autocomplete({
    // Everything after here is the same as inside of the loop of indexes

    Obviously this only gets the first item that has the Autocomplete class, but that is all I need for now.
    Now I can add the EmailAutocomplete task to any visualforce page and it’ll add autocomplete functionality!

    Thanks for doing the hard part!

  20. Scott Hemmeter Said,

    August 12, 2013 @ 11:05 am

    Paul,

    I like your class selection approach best for custom pages. Nice job! The ones I was dealing with were Salesforce default ones and one from a AppExchange app, so I could not edit the HTML to get the classes in there.

  21. Paul Said,

    August 12, 2013 @ 11:09 am

    Yeah, and those were very helpful – I left those in there so it works for those standard pages as well as any visualforce pages that I develop.

    Finally, auto-complete will be on all pages where users can send emails.

RSS feed for comments on this post