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 ?


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


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");

  // 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&formid=69144f31-713b-4eeb-9d89-096d893be6a2&id=" + escape(opSiteId) + "&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");

  // Grab the sub-iFrame within the preview iFrame.
  var IF2 = IF1.contents().find("iframe#printMain");
  // Remove the big header and icon
  // Remove the big whitespace at the top
  // Remove a bunch of whitespace around the second iFrame
  // Reinstate the whitespace next to the scrollbar
  // Remove the big whitespace at the bottom

  // 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("").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");



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


Triggering a Workflow from a Ribbon Button

Add Button to Entity Ribbon

Export the entity in question and open up customizations.xml. Add the following within the CustomActions tag…

      LabelText="Button Label"
      ToolTipTitle="Run Test Workflow"
      ToolTipDescription="Runs the test workflow to see if this works"
      Image32by32="/_imgs/ribbon/tools_32.png" />

Location sets which area of the ribbon the button will appear on.
Sequence sets where within that ribbon it will appear.
LabelText sets the text which appears under the icon on the ribbon.
TemplateAlias (who knows!)

Create the click action

Now we need to add the command Mscrm.RunSomeJS within the CommandDefinitions section.

    <EnableRule Id="Mscrm.Enabled" />
    <DisplayRule Id="Mscrm.CanWriteAccount" />
        Value="{guid of account entity}" />
        Value="{7D78531A-23EB-4042-9626-1D80DFC10A8D}" />

EnableRules sets if the button is greyed out on the toolbar (always enabled in this example)
DisplayRules sets if the button appears on the toolbar (if the user can write to the account entity in this example)
FunctionName sets the name of the function to run within the web resource
Library sets the name of the web resource JavaScript library to access
in this case are the guid of the account entity (not used currently) and the guid of the workflow to trigger

JavaScript to Fire the Workflow

function TriggerWorkflow(entityName,workflowGuid) {

  var xx = prompt(,;

  /*Generate Soap Body.*/
  var soapBody = "<soap:Body>" +
                 "  <Execute xmlns=''>" +
                 "    <Request xsi:type=\'ExecuteWorkflowRequest\'>" +
                 "      <EntityId>" + + "</EntityId>" +
                 "      <WorkflowId>" + workflowGuid + "</WorkflowId>" +
                 "    </Request>" +
                 "  </Execute>" +

  /*Wrap the Soap Body in a soap:Envelope.*/
  var soapXml = "<soap:Envelope " +
                "  xmlns:soap='' " +
                "  xmlns:xsi='' " +
                "  xmlns:xsd=''>" +
                GenerateAuthenticationHeader() +
                soapBody +

  /* Create the XMLHTTP object for the execute method.*/
  var xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");"POST", "/MSCRMservices/2007/crmservice.asmx", false);
  xmlhttp.setRequestHeader("Content-Type", "text/xml; charset=utf-8");
  xmlhttp.setRequestHeader("SOAPAction", "");

  /* Send the XMLHTTP object. */


What’s new in CRM 2011 plugins ?

I wrote a plugin to cascade changes from account addresses to their child contacts in 2011 then converted it back to v4. Let’s look at some of the differences I found.

CRM v4

public void Execute(IPluginExecutionContext context) {
  DynamicEntity target = (DynamicEntity)context.InputParameters[ParameterName.Target];
    if (target.Name.Equals("account")) {
      if (target.Properties.Contains("accountid")) {

CRM 2011

public void Execute(IServiceProvider serviceProvider) {
  IPluginExecutionContext context = (IPluginExecutionContext) serviceProvider.GetService(typeof(IPluginExecutionContext));
  Entity target = (Entity)context.InputParameters["Target"];
    if (target.LogicalName.Equals("account")) {
      if (target.Attributes.Contains("accountid")) {
  • Getting the context and target is slightly more complex.
  • .Properties has become .Attributes.


CRM v4

QueryByAttribute query = new QueryByAttribute();
query.EntityName = "account";
query.Attributes = new string[] { "accountid" };
query.Values = new Object[] { accountid };
query.ColumnSet = cols;

CRM 2011

QueryByAttribute query = new QueryByAttribute();
query.EntityName = "account";
query.ColumnSet = cols;
  • Queries no longer require string or object arrays


CRM v4

ICrmService service = context.CreateCrmService(false);
RetrieveMultipleRequest req = new RetrieveMultipleRequest();
req.Query = query;
req.ReturnDynamicEntities = true;
RetrieveMultipleResponse resp = (RetrieveMultipleResponse)service.Execute(req);
BusinessEntityCollection theAccounts = resp.BusinessEntityCollection;
if (theAccounts.BusinessEntities.Count > 0) {
  DynamicEntity theAccount = (DynamicEntity) theAccounts.BusinessEntities[0];

CRM 2011

IOrganizationServiceFactory serviceFactory = (IOrganizationServiceFactory)serviceProvider.GetService(typeof(IOrganizationServiceFactory));
IOrganizationService service = serviceFactory.CreateOrganizationService(context.UserId);
RetrieveMultipleRequest req = new RetrieveMultipleRequest();
req.Query = query;
RetrieveMultipleResponse resp = (RetrieveMultipleResponse)service.Execute(req);
EntityCollection theAccounts = resp.EntityCollection;
if (theAccounts.Entities.Count > 0) {
  Entity theAccount = theAccounts.Entities[0];
  • Accessing the service is done through a Factory class
  • No longer have to specify that you want dynamic entities
  • BusinessEntities have just become Entities
  • Treat dynamic entities the same as any other entity


CRM v4

TargetUpdateDynamic update = new TargetUpdateDynamic();
update.Entity = theContact;
UpdateRequest updatereq = new UpdateRequest();
updatereq.Target = update;
UpdateResponse updateresponse = (UpdateResponse)service.Execute(updatereq);

CRM 2011

UpdateRequest updatereq = new UpdateRequest();
updatereq.Target = theContact;
UpdateResponse updateresponse = (UpdateResponse)service.Execute(updatereq);
  • No longer a need for a TargetUpdateDynamic object


CRM v4

Guid accountid = ((Key)target.Properties["accountid"]).Value;
CrmBoolean trigger = (CrmBoolean)theAccount.Properties[accountTrigger];

CRM 2011

Guid accountid = (Guid)target.Attributes["accountid"];
bool trigger = (bool)theAccount.Attributes[accountTrigger];
  • Native C# data types, WOO!