Archive for November, 2011

Email AutoComplete (using jQuery)

Share

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

Comments (21) comments feed