Filing e-mails against custom entities

Hints, Tips and Tricks

Technical Hints Tips and Tricks that cover customization and development using Sage CRM. API usage and coding are covered.

Filing e-mails against custom entities

  • Comments 7
  • Likes

* The code in this article has been updated to fix a bug that caused it not to work for any entity that did not have a prefix that matched the entity name *

A while back Jeff Richards wrote a blog post about how to add a button to the communications tab of a custom entity to send an e-mail just like a built-in entity.  But you may have noticed that when you send e-mails from custom entities they are not filed against the entity and so do not show up in the communications tab.  This is because there is no built-in support for e-mails with custom entities. 

I have created a hack to make this work.  The following is a detailed description of how I achieved this. At the end of this post are simple step-by-step instructions that you can follow to make this work in your installation.

PS - Apologies about the formatting of the source code in this post.  This site does some random annoying things when you try to post prettily formatted code and this is the best I could make it without losing my marbles.

I started by thinking we will use a table level script to populate the appropriate field on the Communications table. (BTW In my example scenario my entity is called test and the linking field on the communications table is called comm_testid). But a table level script has no way of knowing what custom entity was in context when the send e-mail action was taken. So we need to pass that information to the table level script some how. We’ll worry about how get that information out in a moment, for now we’ll create two new fields on the Communication entity called comm_entityName (a text field of which should be long enough to allow for your largest entity name) and comm_recordid (an integer field). 

Assuming these fields have been populated our TLS can now use this information to populate the correct field on the communication record. Here is an example TLS to do this:

function InsertRecord()
{
switch(Values('comm_customentity')) {
case 'test': Values('comm_testid')=Values('comm_recordid'); break;
case 'bob': Values('comm_bobid')=Values('comm_recordid'); break;
}
}

You can see that this script matches custom entity names of test and bob. Obviously real entity names and their linking fields have to go in here. Next we have the challenge of putting the correct values into the comm_customentity and comm_recordid fields. We willll do this in the E-mailFilingBox screen so the first thing we'll do is add the two fields to this screen. We’re going to put a script in the Custom Content of this screen to populate these fields for us. Oh and we’ll hide them from the user so they don’t mess with them with some code like this:

document.getElementById('_Captcomm_customentity').style.visibility = 'hidden';
document.getElementById('_Datacomm_customentity').style.visibility = 'hidden';
document.getElementById('_Captcomm_recordid').style.visibility = 'hidden';
document.getElementById('_Datacomm_recordid').style.visibility = 'hidden';

Our script is going to interrogate the querystring of the current document in the EWARE_MID frame, that’s the main content window, to figure out the current entity and it’s id.What makes this tricky is how we get this information is dependent on what option the user has selected in preferences "E-mail Screen Position" field. Now you may be saying you’ve hard coded the method into your button that you put on the communication tab but, did you think of what happens if the user has the entity in context and then clicks New -> E-mail? It’s reasonable that the user can expect this to work as this works for all other entities.

So how do we figure out what method to use?

Well we could do a database lookup in the UserSettings table but that’s just too complicated and costly.

There is an easier way:

1.     If top.document.getElementById('EWARE_MID') returns null then the setting is Popup

2.     If top.document.getElementById('EWARE_MID') is not null and top.document.getElementById('EWARE_MID').contentWindow.location.href contains Act=1500 then the setting is Normal

3.     If top.document.getElementById('EWARE_MID') is not null and top.document.getElementById('EWARE_MID').contentWindow.location.href does not contain Act=1500 then the setting is Split Screen

Simple huh?

OK so now we have to get the querystring from the custom entity screen so we can glean what entity it is and what record id it has. The query string looks like this:

http://allanj-dt/CRM62/CustomPages/test/testCommunication.asp?SID=27263499360516&Key-1=7&Key0=58&Key7=219&Key27=80&Key37=6001&Key58=6001&J=test/testCommunication.asp
&F=test/testCompany.asp&E=communication&test_testID=6001&T=test

You can see that it’s pretty simple to figure out which entity and id it has from this string. I’ve highlighted in yellow the interesting parts. Note that Key58 is not always available, but if it’s not available then the test_testID field will give us what we want (it’s not always available either but luckily it’s always there if Key58 is not).

But hold up… We have to get this querystring first before we can start stripping the values out of it.

Here’s how we do it:

  • If the e-mail setting is Split screen then we can get the querystring with top.document.getElementById('EWARE_MID').contentWindow.location.href
  • If the e-mail setting is Popup then we have to use opener.top.document.getElementById('EWARE_MID').contentWindow.location.href
  • If the e-mail setting is Normal… We’ll we’re actually in trouble because this information is nowhere to be found in the browser anymore because the new e-mail screen has completely replaced it!!!

So what are we going to do for those users who have the Normal setting? We could just tell them that they’re not normal for having the Normal setting and that the split screen option is much more normal and they should use that. But we’re not that totalitarian.

I thought about this problem for some time. The history back button must have this information because it knows to go back to the custom entity screen after we click into the "normal" new email screen. So maybe we can get the information from it… Well looking at it closer we can see that the history back button actually goes and asks the CRM Server for this information. If only we had some way of making a request of the CRM Server from client side code without changing the page we’re on?

AJAX to the rescue!

You may have already seen my blog post on AJAX, if not have a quick read of it
here.

I’ll save you all the horrible details of how we make the request of the server (whilst pretending to be the history back button) and how we glean the querystring from what the server sends back and just give you the code listing. Trust me, it works!:

function GetLastLocation() {
  var strQS = location.href.split(/\?/)[1];
  var strAddr;
  if (window.location.toString().toLowerCase().search('eware.dll')==-1) {
strAddr = window.location.toString().split('CustomPages')[0];
} else {
strAddr = window.location.toString().split('eware.dll')[0];
}
  var sid = window.location.href.match(/([?&]sid=)([^&]*)/i)[2];
var strURL = strAddr + 'eware.dll/Do?SID=' + sid + '&Act=521';

  XmlHttp = new XMLHttpRequest();
   XmlHttp.open('GET',strURL,false);
  XmlHttp.setRequestHeader('Content-Type', 'text/xml');
  XmlHttp.send(null);
  var strHtml = XmlHttp.responseText;
XmlHttp=null; // always clear the XmlHttp object when you are done to avoid memory leaks
var lastLocation=strHtml.match(/(parent.EWARE_MID.location=')([^']*)/i)[2];

// Go forward again so we don't lose our place in history ala Marty McFly
strURL = strAddr + 'eware.dll/Do?SID=' + sid + '&Act=522';
  XmlHttp = new XMLHttpRequest();
   XmlHttp.open('GET',strURL,false);
   XmlHttp.setRequestHeader('Content-Type', 'text/xml');
  XmlHttp.send(null);
  XmlHttp=null;

return lastLocation;
}

OK so now we have the querystring no matter what flavor of e-mail preference the user is using. Next we need to suck out the values and poke them into the hidden fields:

function GetArg(String, Arg){
var v=String.match(new RegExp('([?&]' + Arg + '=)([^&]*)', 'i'));
return (v)?v[2]:'';
}
if (lastLocation.search(/custompages/i)!=-1) {
var entityName = lastLocation.match(/(CustomPages\/)([^\/]*)/i)[2];
var recordId = GetArg(lastLocation, 'Key58');
if (recordId == '') {
// Key58 is not in the querystring so look for entity id field
var recordId = GetArg(lastLocation, entityName + '[^=]*');
}
document.getElementById('comm_customentity').value = entityName;
document.getElementById('comm_recordid').value = recordId;
}

Ok so that pretty much describes the path I took to solve this problem. Now for your ease of implementation here are some simple step by step instructions for making this work in your own CRM server:

1.     Create a text field on the Communication entity called comm_customentity.  Make sure it is long enough for all your custom entity names

2.
     Create an integer field on the Communication entity called comm_recordid.

3.
     Add these two fields to the E-mailFilingBox screen.

4.
     Add the following script into the Custom Content of this screen

<script>

function GetLastLocation() {

var strQS = location.href.split(/\?/)[1];

var strAddr;

if (window.location.toString().toLowerCase().search('eware.dll')==-1) {

strAddr = window.location.toString().split('CustomPages')[0];

} else {strAddr = window.location.toString().split('eware.dll')[0];

}

var sid = window.location.href.match(/([?&]sid=)([^&]*)/i)[2];

var strURL = strAddr + 'eware.dll/Do?SID=' + sid + '&Act=521'; if (window.XMLHttpRequest) {
    XmlHttp = new XMLHttpRequest();
} else {
    // IE6 and before
    XmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
}

XmlHttp.open(
'GET',strURL,false);

XmlHttp.setRequestHeader('Content-Type', 'text/xml');

XmlHttp.send(null); var strHtml = XmlHttp.responseText;

XmlHttp=null; // always clear the XmlHttp object when you are done to avoid memory leaks

var lastLocation=strHtml.match(/(parent.EWARE_MID.location=')([^']*)/i)[2];

// Go forward again so we don't lose our place in history ala Marty McFly

strURL = strAddr + 'eware.dll/Do?SID=' + sid + '&Act=522'; if (window.XMLHttpRequest) {
    XmlHttp = new XMLHttpRequest();
} else {
    // IE6 and before
    XmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
}

XmlHttp.open(
'GET',strURL,false);

XmlHttp.setRequestHeader('Content-Type', 'text/xml');

XmlHttp.send(null);

XmlHttp=null;

return lastLocation;

}

function GetArg(String, Arg){

var v=String.match(new RegExp('([?&]' + Arg + '=)([^&]*)', 'i'));

return (v)?v[2]:'';

}

window.attachEvent(
'onload', function () {

// Hide the comm_customentity and comm_recordid fields from the user

document.getElementById('_Captcomm_customentity').style.visibility = 'hidden';

document.getElementById('_Datacomm_customentity').style.visibility = 'hidden';

document.getElementById('_Captcomm_recordid').style.visibility = 'hidden'; document.getElementById('_Datacomm_recordid').style.visibility = 'hidden';

// To figure out the current entity and record we need to get the address of the screen before the e-mail screen

// was activated. There are three different methods for each type of e-mail screeen (Pop up, Split-screen & Normal)

var lastLocation; if (top.document.getElementById('EWARE_MID')==null) {

// Pop up window

lastLocation = opener.top.document.getElementById('EWARE_MID').contentWindow.location.href;

} else {

if (top.document.getElementById('EWARE_MID').contentWindow.location.href.search(/Act=1500/i)==-1) {

// Split Screen

lastLocation = top.document.getElementById('EWARE_MID').contentWindow.location.href; } else {

// "Normal" email

lastLocation = GetLastLocation();

}

}

if (lastLocation.search(/custompages/i)!=-1) { var entityName = lastLocation.match(/(CustomPages\/)([^\/]*)/i)[2];

var recordId = GetArg(lastLocation, 'Key58'); if (recordId == '') {

// Key58 is not in the querystring so look for entity id field

// The following line has been changed to fix a bug that was preventing this from working for most installations:
var recordId = GetArg(lastLocation, '[^_]*_' + entityName + '[^=]*');

}

document.getElementById(
'comm_customentity').value = entityName;document.getElementById('comm_recordid').value = recordId;

}

});

</script>

 

5.     Add a table level script to the Communication entity with the following code:

function InsertRecord() {

switch (Values('comm_customentity')) {

case 'test': Values('comm_testid') = Values('comm_recordid'); break;

case 'bob': Values('comm_bobid') = Values('comm_recordid'); break;

}

}

Of course you need to change this to suit your custom entities

And voilà!  Custom entities work just like bought ones.

 

Comments
  • Very Nice work around on this issue. Thank you for sharing.

  • Hi,

    I have used the above code, but my emails are still not getting filed in CRM. what can be the issue with this

  • Hi Faraz,

    There is a lot of code there that could potentially have gone wrong.  Best to isolate where the problem is.  First test your table level script is working by removing the custom content script and manually populating the comm_customentity and comm_recordid fields.  If that works then you have a problem in your custom script.

    Best to use something like the IE developer tools (in IE8 this is accessed by pressing F12, on previous versions you have to download and install them).

    Using these tools you can quickly spot syntax errors.

    Good luck!

  • Hi Jack,

    Thanks for the help. By the way for my understanding, when i create a new email (Email out) against my custom entity from the CRM this code will file it against that custom entity automatically and i dont have to manually file it. Correct me if i am wrong.

    Because i have added two new fields as you said above in the example and created a table level script, but for some reason i cant see anything happening or nothing is getting affected because of the code.

    Am i doing anything wrong here ?

    Regards,

    Faraz Afzal

  • That's what it should do yes.  Could you copy and paste your table level script here? Maybe there is an error there

  • Hi Jack,

    Thanks alot for your help, finally i managed to file the emails, actually there was a problem with Table Level Script of mine :)

    Thanks

    Faraz Afzal

  • I am assuming, this would work up until v7.1 as the frames have been removed from v7.2 onwards, I am not sure which part of the code is to be ignored.