Saturday, March 8, 2014

Extending the Salesforce LiveAgent pre-chat form through javascript and VF Remoting

A client of Seahorce Solutions recently requested a LiveAgent preChat integration with the following requirements:

1)  The Chat Link be hosted within their non-Salesforce website.
2)  The preChat form capture first name, last name, and email (all required) and default the Agent greeting to include the provided first name (and Agent Name).
3)  The preChat form search for a matching Contact and a matching Lead with some specific conditions based on the outcome:
  • If a matching contact is found, DO NOT create a new Lead.
  • If a matching contact AND lead are found, bring up both.
  • If NO matching contact is found, do NOT create a new contact, but DO create a new Lead (as long as a matching Lead is not found).
I have included my solution below and highlighted in red those areas where I had to make tweaks to more common use cases.

Satisfying the Request:
     #1 was relatively easily satisfied using the documented ChatOnline and ChatOffline resources in the Chat configuration.  The LiveAgent configuration wizard provides deployment code for your non-Salesforce website.

     #2 turned out to be a little tricky (unless I missed something), because when I used the documented id="preChatField" to identify the element for "Visitor_Name", I could no longer get it included in the New Lead data.  My work-around was the write the first name to a hidden field dedicated to the greeting field (javascript slight-of-hand).

     #3 turned out to be the most difficult problem to solve.  There are no documented approaches for CONDITIONALLY creating new records, although this seems to be a reasonable requirement.  The client did not want chat requests from existing contacts (customers) to create leads unnecessarily.  My work-around was to come up with a way to render the "liveagent.prechat.findorcreate.map.doCreate:Lead" command ineffectual when a contact was found.  The trick ending up being to independently query the Contacts object to look for a match before actually submitting the form (and letting the LiveAgent api discover the same contact).  To do this, I changed the standard submit to a regular button element.  This allowed me to "preCheck" the contact based on the email provided.  I did not want to use SOQL in javascript (injection threat), so I settled on creating an apex class to support a VisualForce remoting call.  The solution works by removing all the required field arguments from the "doCreate:Lead" element, thus preventing the successful creation of a lead (no errors are triggered)!  

Note:  The fact that there is no error feedback on incorrectly configuring the doCreate:Lead set of elements is probably the biggest pitfall for many folks trying to implement a preChat.  Make sure that you provide a value for ALL required fields on the object for which you are creating a new record (see my hidden LeadStatus field).  If you don't get it just right, you'll get no new records with even less insight into what went wrong.


Using VF Remoting within the LiveAgent preChat form enables the developer to make significant customizations (like auto-filling optional data for returning contacts).  

Enjoy!

*****************************
PRE-CHAT VISUALFORCE PAGE
*****************************
<apex:page showHeader="false"  standardController="Account" extensions="preChatRemoting_Con">
<!-- This script takes the endpoint URL parameter passed from the deployment page and makes it the action for the form -->

<script type="text/javascript">
     (function() {
     function handlePageLoad() {
       var endpointMatcher = new RegExp("[\\?\\&]endpoint=([^&#]*)");
       document.getElementById('prechatForm').setAttribute('action',
       decodeURIComponent(endpointMatcher.exec(document.location.search)[1]));
     } if (window.addEventListener) {
              window.addEventListener('load', handlePageLoad, false);
   } else { window.attachEvent('onload', handlePageLoad, false);
              }})();

  function SubmitForm(createLead) {

      if (!createLead) {  //We found a matching contact based on email provided, so DO NOT send parameters to create a new lead.
          document.getElementById("optionA").value="";
          document.getElementById("optionB").value="false";
      }
      else {   //No matching contact was found, so send parameters required to create a new lead.
          document.getElementById("optionA").value="FirstName,true;LastName,true;Company,true;Email,true;Description,true;Status;true;Type,true;LeadSource,true;Source_Type__c,true;";
          document.getElementById("optionB").value="true";
      }
      document.getElementById("prechatForm").submit();
  }

  function getRemoteContact()
    {
        var contactEmail = document.getElementById('contactEmail').value;
        Visualforce.remoting.Manager.invokeAction('{!$RemoteAction.preChatRemoting_Con.getcontact}', contactEmail, function(result, event){
                if (event.status) {
                    SubmitForm(false);  //contact found, don't create a lead
                } else if (event.type === 'exception') {
                    SubmitForm(true);  //contact NOT found, DO create a lead
                } else {
                    SubmitForm(false);  //unknown error, DON'T create a lead
                }
            },
            {escape: true}
        );
    }
</script>

<form method="post" id="prechatForm">

<!-- Detail inputs -->
First Name: <input type="text" name="liveagent.prechat:leadFirstName" onchange="javascript: document.getElementById('prechat_field').value=this.value;" required="required"/><br />
Last Name: <input type="text" name="liveagent.prechat:leadLastName"  required="required"/><br />
Email: <input type="text" id="contactEmail" name="liveagent.prechat:leadEmail"  required="required"/><br />

<!--greeting field, copies from FirstName input-->
<input type="hidden" name="liveagent.prechat.name"  id='prechat_field'/>

<!--hidden fields written to the new lead-->
<input type="hidden" name="liveagent.prechat:leadStatus" value="Open" />
<input type="hidden" name="liveagent.prechat:leadType" value="Prospect" />
<input type="hidden" name="liveagent.prechat:leadSource" value="Word of Mouth" />
<input type="hidden" name="liveagent.prechat:leadSourceType" value="Inbound Chat" />
<input type="hidden" name="liveagent.prechat:leadCompany" value="[[Unknown]]" />

<!-- Creates an auto-query for a matching Contact record’s Email field based on the value of the liveagent.prechat:leadEmail field -->
    <input type="hidden" name="liveagent.prechat.query:leadEmail" value="Contact,Contact.Email" />


<!-- Map the detail inputs to the Lead fields -->
<input type="hidden" name="liveagent.prechat.findorcreate.map:Lead" value="FirstName,leadFirstName;LastName,leadLastName;Company,leadCompany;Email,leadEmail;Status;leadStatus;Type,leadType;LeadSource,leadSource;Source_Type__c,leadSourceType;" />

<!-- Map the detail inputs to the Contact fields -->
<input type="hidden" name="liveagent.prechat.findorcreate.map:Contact" value="FirstName,leadFirstName;LastName,leadLastName;Email,leadEmail;" />


<!-- Try to find Contact by email (exact match) -->
<input type="hidden" name="liveagent.prechat.findorcreate.map.doFind:Contact" value="Email,true;" />
<input type="hidden" name="liveagent.prechat.findorcreate.map.isExactMatch:Contact" value="Email,true;" />


<!-- Try to find the Lead by email (exact match) -->
<input type="hidden" name="liveagent.prechat.findorcreate.map.doFind:Lead" value="Email,true;" />
<input type="hidden" name="liveagent.prechat.findorcreate.map.isExactMatch:Lead" value="Email,true;" />

<!-- If the Lead is not found, then create one with the following fields set -->
<input type="hidden" id="optionA" name="liveagent.prechat.findorcreate.map.doCreate:Lead" value="FirstName,true;LastName,true;Company,true;Email,true;Description,true;Status;true;Type,true;LeadSource,true;Source_Type__c,true;" />

<!-- Save the Lead on the Live Chat Transcript -->
<input type="hidden" name="liveagent.prechat.findorcreate.saveToTranscript:Lead" value="Lead" />

<!-- Show the Lead when it is found or created -->
<input type="hidden" id="optionB" name="liveagent.prechat.findorcreate.showOnCreate:Lead" value="true" />

<!-- Show the Contact when it is found or created -->
<input type="hidden" name="liveagent.prechat.findorcreate.showOnCreate:Contact" value="true" />

<input type="button" value="Begin Chat Session" id="prechat_submit" onclick="javascript: getRemoteContact();"/>
</form>
</apex:page>

*****************************
APEX CONTROLLER
*****************************
public class preChatRemoting_Con 
{
    public preChatRemoting_Con(ApexPages.StandardController controller) 
    {

    }
    @RemoteAction
    public static contact getcontact(string contactemail)
    {
        Contact testContact=new Contact();
        testContact=[Select Id,Name from Contact where email=:contactemail limit 1];
        return testContact;
    }

}