Default email templates / signatures in CRM 4

I added some code to a customer’s system to set a default email template (signature). This code is avaiable from various places through Google. However, there were some strange inconsitencies when we added it to their live system – in some cases the signature would load, sometimes it wouldn’t.

Eventually I figured out that sometimes Microsoft’s own onLoad code was removing the signature I had set. Basically it was doing a query to find any email body (because the same form / code is used when looking at email history) and when it found none it was overwriting the email body (along with my signature).

In-fact, it was setting the body to something like…

I added a StripHTML function and a 500msec delay to my code (plus a bunch of additional null checks) which seems to have solved the problem.

Here’s the code for the onLoad of the email form. If you want to use it, don’t forget to change the GUID on line 7 for your specific template.

function sig() {

  if (crmForm.FormType != 1) {return;}

  // Pre-fill a template signature
  var drawSignature = false;
  var emailTemplateToLoad = "D802E626-4DB1-E011-81DD-0015C5FF4D30";   // sandbox "BAA8272F-F7AD-E011-93CB-0015C5FF4CDB"

  // Check if description is blank or similar
  var theDescription = crmForm.all.description.DataValue;
  if (theDescription == null) {
    drawSignature = true;
  } else if (theDescription == undefined) {
    drawSignature = true;
  } else if (stripHTML(theDescription) == "") {
    drawSignature = true;
  } else if (stripHTML(theDescription).length < 10) {
    drawSignature = true;
  }

  if (drawSignature) {

    // Get Regarding object details
    if (!crmForm.all.regardingobjectid) {return;}
    var RegardingItems = crmForm.all.regardingobjectid.DataValue;

    if (RegardingItems) {

      var regardingObjectId = RegardingItems[0].id;
      var regardingObjectType = RegardingItems[0].type;

      if (regardingObjectId == null || regardingObjectId == "") {return;}
      if (regardingObjectType == null || regardingObjectType == "") {return;}

      var command = new RemoteCommand("EmailTemplateService", "GetInstantiatedEmailTemplate");
      command.SetParameter("templateId", emailTemplateToLoad );
      command.SetParameter("objectId", regardingObjectId);
      command.SetParameter("objectTypeCode", regardingObjectType);
      var result = command.Execute();

      if (result.Success) {
        var o = new Object();
        o.EmailBody = "";
        o.EmailSubject = "";
        if(typeof(result.ReturnValue) == "string") {
          oXml = CreateXmlDocument(false);
          oXml.loadXML(result.ReturnValue);
          o.EmailBody = oXml.selectSingleNode("template/body").text;
          o.EmailSubject = oXml.selectSingleNode("template/subject").text;
          crmForm.all.description.InsertValue(o.EmailBody);
        }
      }

    }

  } // drawSignature == true

}

function stripHTML(xx) {
  var tmp = document.createElement("DIV");
  tmp.innerHTML = xx;
  return tmp.textContent||tmp.innerText;
}

var a = window.setTimeout(sig, 500);
Advertisements

Preview entity data in an iFrame (CRM 2011)

We wanted to be able to display a bunch of useful information when a user selected a record from a lookup. I wanted to use as much native CRM stuff as possible so non-developers in the team could change what fields are displayed and where without having to modify any code. I think it turned out pretty cool. Hope some people find it useful.

The result

In the screenshot, the relevant bit is under the “Op Site Data” heading. When a user selects an “Op Site”, the tab pops open and the grey box is displayed showing related data for that record.

So how is it done ?

IFrame

Add a new tab and iFrame to the relevant form. Set the URL of the iFrame to “about:blank”.
Make sure you un-tick “Restrict cross-frame scripting”.

Attach a JavaScript function to the onReadyStateChange event of the iFrame. In this case, I’ve called it iframe_onload.

Related Entity Form

You need to add a new form to the related entity. In this case it’s a custom entity called Op Site. It doesn’t matter what name you give it – I called it “Preview”.

Set the new form up to only display the fields you want. I found it best to use a three column layout.

Print Preview

Now here’s the science bit. Go and look at the new form you created and do a Print Preview. This will bring up a read-only version of the form – this is the basis of what we’ll display in the iFrame. Copy the URL of the preview to the clipboard.

We’ll have to get rid of those Print and Close toolbar buttons and the space-wasting headings and whitespace.

New form in print preview mode

JavaScript

NB This stuff requires jQuery. I was using v1.6.2.

There are two functions to perform this preview. The first handles the opening / closing of the tab and sets the URL for the iFrame. The second cleans up the content of the iFrame so it takes up less room.

First Function

I called this function ShowOpSiteData. It needs to be attached to the Form onLoad event as well as the Field’s onChange event (in this case the field is called Op Site). You will need the URL of the print preview page that you copied earlier.

function ShowOpSiteData() {

  // Collapse the preview tab
  var elTab = Xrm.Page.ui.tabs.get("tab_4");
  elTab.setDisplayState("collapsed");

  // Get the value (GUID) of the selected lookup
  var opSite = Xrm.Page.getAttribute("hpd_opsiteid").getValue();
  if (!opSite) { return; }
  var opSiteId = opSite[0].id;
  if (opSiteId == "") {return;}

  // Set the iFrame URL to the print preview URL.
  // Insert the GUID into the URL.
  if (opSiteId != "") {
    var el = document.getElementById("IFRAME_opsitedata");
    var el2 = document.getElementById("printArea");
    var url = "/_forms/print/print.aspx?allsubgridspages=false&amp;formid=69144f31-713b-4eeb-9d89-096d893be6a2&amp;id=" + escape(opSiteId) + "&amp;objectType=10004";
    el.src = url;
  }

}

Second Function

I called this function iframe_onLoad (originally I had it attached (via code) to the iFrame’s onLoad event). The function needs to be attached to the iFrame’s onReadyStateChange event. This function is where the jQuery comes in – mainly for the Sizzle selector system.

The function basically goes into the iFrame and removes the toolbar, the headings and the big whitespaces. See the comments for full details.

function iframe_onLoad() {

  // Grab the iFrame element.
  // Check it's fully loaded and we have the page is correct.
  var iFrame = document.getElementById("IFRAME_opsitedata");
  if (iFrame.readyState != "complete") {return;}
  if (iFrame.src.indexOf("/_forms/print/print.aspx") == -1) {return;}

  // Grab the preview iFrame as a jQuery object. Remove the toolbar.
  var IF1 = jQuery("#IFRAME_opsitedata");
  IF1.contents().find("tr#printHeader").remove();

  // Grab the sub-iFrame within the preview iFrame.
  var IF2 = IF1.contents().find("iframe#printMain");
  // Remove the big header and icon
  IF2.contents().find("tr.ms-crm-Form-HeaderContainer").remove();
  // Remove the big whitespace at the top
  IF2.contents().find("div.ms-crm-Tab-Print").first().remove();
  // Remove a bunch of whitespace around the second iFrame
  IF2.contents().find("td.ms-crm-Form-Page-Main-cell").css("padding","0px");
  // Reinstate the whitespace next to the scrollbar
  IF2.contents().find("td.ms-crm-Form-Page-Main-cell").css("padding-right","8px");
  // Remove the big whitespace at the bottom
  IF2.contents().find("td.ms-crm-Form-Footer").parent().remove();

  // Remove the header text. The text isn't within any specific tag so just performing a replace.
  // The only part of the code that isn't directly re-usable
  IF2.contents().find("div.ms-crm-Tab-Print").each(function() {
    jQuery(this).html( jQuery(this).html().replace("General","") );
    jQuery(this).html( jQuery(this).html().replace("Response Information","") );
    jQuery(this).html( jQuery(this).html().replace("Notes","") );
  });

  // Expand Op Site Data tab, revealing the fully rendered preview
  var elTab = Xrm.Page.ui.tabs.get("tab_4");
  elTab.setDisplayState("expanded");

}

Success

That’s it! Feel free to leave a comment if you found this useful, spot a problem or have a question.

Send E-Mail from custom entities in CRM 4

A load of customers are still running CRM 4 and still manage to come up with problems I can’t just find solutions to with Google!

This time it was automatically populating the To: field of a new email when initiated from a custom entity. In this case, the custom entity has a related Contact but would work just as well with an associated Account or User. Unfortunately you can’t just have it send directly to a email address without some fancy plugin footwork – it has to be a CRM record.

I have a plugin which attaches any files from notes to the email when it gets sent so I suppose this could be extended to support plain-text email addresses. Maybe one day I’ll get around to documenting it here – trying to keep the blog focussed on CRM 2011 as much as possible.

Yet again, this requires Daniel Cai’s excellent CRM Web Service Toolkit.

var regarding = crmForm.all.regardingobjectid.DataValue;
if (regarding) {
   if (regarding[0].type == 10038) {

    var venueId = regarding[0].id;

    var fetchXml = [
    "<fetch mapping='logical' count='1'>",
      "<entity name='msa_eventvenue'>",
        "<attribute name='new_contactid' />",
        "<filter>",
          "<condition attribute='msa_eventvenueid' operator='eq' value='" + venueId + "' />",
        "</filter>",
      "</entity>",
    "</fetch>"
    ].join("");

    var fetchData = CrmServiceToolkit.Fetch(fetchXml);
    if (fetchData.length > 0) {

      var lookupObject = new Object;
      lookupObject.id = fetchData[0].getValue("new_contactid");
      lookupObject.name = fetchData[0].getValue("new_contactid","name");
      lookupObject.typename = "contact";

      var lookupObjectArray = new Array();
      lookupObjectArray[0] = lookupObject;

      crmForm.all.to.DataValue = lookupObjectArray;

    }

  }
}