Show hierarchically related activities in subgrid

In this article I will demonstrate how to implement a plugin to extend the possibilities for showing activities that are related through account hierarchy in a subgrid on a Microsoft Dynamics CRM form.

In my previous article I showed how to create a simple plugin to show all directly related activities in a subgrid, and not just activities related through the Regarding field.

Objective

The goal is to be able to display all activities (yellow boxes) anywhere below any account (blue box) that is opened in CRM. Some of you may recognize this model – it is respectfully borrowed from MVP Jukka Niiranen’s blog post on this problem: CRM 2011 subgrids ain’t what associated views used to be. As this article indicates, this has been a problem ever since we left CRM 4.0 behind. Continue reading “Show hierarchically related activities in subgrid”

Show ALL related activities in a subgrid

Microsoft Dynamics CRM offers great capabilities for activity entities, both out of the box and custom activities.
This post will describe a way to extend those capabilities even more, using a quite simple plugin.

During eXtremeCRM in Warsaw last week, a fellow CRMian and frequent user of FetchXML Builder approached me to ask if I knew a way to create “dynamic” queries with some contextual awareness. He had used unsupported methods to inject FetchXML to subgrids, to be able to show all activities related to current record, not just those where the record is related through the Regarding field. As I will show in this post, this can be accomplished with a simple plugin and a custom view.

Background

image
image
image

Activities handle e-mail, phonecalls, tasks, meetings, and even faxes and letters. Common custom activities are sms, transactions, and any other date-bound entity that involves participants of some kind. Participants can be contacts, accounts, leads, users, and the activities can be regarding eny entity with the “Activities” option selected. When looking at an associated view of a record to display associated activities, this can display activities where the current record is either recipient, sender, participant or regarding the activity. This is done with specialized views built into CRM, as this gives a useful overview of all activities concerning the party. The problem is when you want to have a similar view embedded on the form in a subgrid. In this case, you can only add activities related to the record through the Regarding lookup on the activities. This will of course only show a fraction of all activities that may be related to the contact, account, or any other entity form the subgrid is placed on.  

Solution – the customizations

image
image
image

To solve this, we need to modify the query that is executed by CRM. Instead of filtering activities based on the value in the Regarding field, we want to verify that the current record is any kind of “party” to the activity. First, create a custom view for the activities, with a signature that can be identified by our plugin so that it does not trigger when we don’t want it to. Open the “All Activities” view, and select Save As to create your custom view. Add a filter criteria to only show records where Activity does not contain data. Save the view. The view is now pretty unusable. It will never show any records as activity is required for the activitypointer pseudo entity.   Next, add a subgrid to the form where you want to show all activities, and select the new view as the default and only view. The customizations are now in place, and when we view any record with this form, nothing will show in the added subgrid.

Solution – the plugin

To get the query we want to be passed to CRM, we will intercept the RetrieveMultiple message in the execution pipeline before it goes to CRM. By analyzing the request we see that the query is formed like this, after conversion to FetchXML:

<fetch distinct='false' no-lock='true' mapping='logical' page='1' count='4' returntotalrecordcount='true' >
   <entity name='activitypointer' >
     <attribute name='subject' />
     ...
     <filter type='and' >
       <condition attribute='isregularactivity' operator='eq' value='1' />
       <condition attribute='activityid' operator='null' />
       <condition attribute='regardingobjectid' operator='eq' value='633929A2-F2E1-E511-8106-000D3A22EBB4' />
     </filter>
     <order attribute='scheduledend' descending='false' />
     <link-entity name='systemuser' to='owninguser' from='systemuserid' link-type='outer' alias='activitypointerowningusersystemusersystemuserid' >
       <attribute name='internalemailaddress' />
     </link-entity>
   </entity>
 </fetch> 

A bunch of included attributes have been excluded for readability
So, let’s create a plugin that triggers Pre RetrieveMultiple of activitypointer, and investigate if it has our “signature”.

      public void Execute(IServiceProvider serviceProvider)
         {
             ITracingService tracer = (ITracingService)serviceProvider.GetService(typeof(ITracingService));
             IPluginExecutionContext context = (IPluginExecutionContext)serviceProvider.GetService(typeof(IPluginExecutionContext));
             if (context.MessageName != "RetrieveMultiple" || context.Stage != 20 || context.Mode != 0 ||                 !context.InputParameters.Contains("Query") || !(context.InputParameters["Query"] is QueryExpression))
             {
                 tracer.Trace("Not expected context");
                 return;
             }
             if (ReplaceRegardingCondition(query, tracer))
             {
                 context.InputParameters["Query"] = query;
             }
         } 

This code is pretty self-explaining. The interesting part however, comes in the ReplaceRegardingCondition method…

        private static bool ReplaceRegardingCondition(QueryExpression query, ITracingService tracer)
        {
            if (query.EntityName != "activitypointer" || query.Criteria == null || query.Criteria.Conditions == null || query.Criteria.Conditions.Count < 2)
            {
                tracer.Trace("Not expected query");
                return false;
            }

            ConditionExpression nullCondition = null;
            ConditionExpression regardingCondition = null;

            tracer.Trace("Checking criteria for expected conditions");
            foreach (ConditionExpression cond in query.Criteria.Conditions)
            {
                if (cond.AttributeName == "activityid" &amp;&amp; cond.Operator == ConditionOperator.Null)
                {
                    tracer.Trace("Found triggering null condition");
                    nullCondition = cond;
                }
                else if (cond.AttributeName == "regardingobjectid" &amp;&amp; cond.Operator == ConditionOperator.Equal &amp;&amp; cond.Values.Count == 1 &amp;&amp; cond.Values[0] is Guid)
                {
                    tracer.Trace("Found condition for regardingobjectid");
                    regardingCondition = cond;
                }
                else
                {
                    tracer.Trace($"Disregarding condition for {cond.AttributeName}");
                }
            }
            if (nullCondition == null || regardingCondition == null)
            {
                tracer.Trace("Missing expected null condition or regardingobjectid condition");
                return false;
            }
            var regardingId = (Guid)regardingCondition.Values[0];
            tracer.Trace($"Found regarding id: {regardingId}");

            tracer.Trace("Removing triggering conditions");
            query.Criteria.Conditions.Remove(nullCondition);
            query.Criteria.Conditions.Remove(regardingCondition);

            tracer.Trace("Adding link-entity and condition for activity party");
            var leActivityparty = query.AddLink("activityparty", "activityid", "activityid");
            leActivityparty.LinkCriteria.AddCondition("partyid", ConditionOperator.Equal, regardingId);
            return true;
        }
image

A bit of explanation:
Line 3-7: First sanity check of the query to verify that we might find our signature.
Line 13-29: Looping through the conditions to find our signature.
Line 15-19: Identifies the null-condition that was introduced in the view.
Line 20-24: Identifies the relation for the view, used to extract the record id.
Line 30-34: Verification of our signature.
Line 35: Extract the id of the parent record which is being displayed.
Line 39: Remove the null-condition used only to identify our signature.
Line 40: Remove the condition that the activities must have current record as Regarding.
Line 43: Create link-entity to activityparty, where the current record should be found.
Line 44: Add condition that the associated party shall be the current record.
As can be seen in the Execute method above, if the query is updated by our code, the updated query will be put back into the plugin execution context, and thus the updated query is what will be executed by CRM.
Finally, the plugin shall be registered in CRM.

Result

image


The subgrid on the form now contains all activities that are related to the current contact, in this case.
If you activate Plug-in Trace Log for all executions, you can now see logs with something like this in the message block:

Checking criteria for expected conditions
Disregarding condition for isregularactivity
Found triggering null condition
Found condition for regardingobjectid
Found regarding id: 633929a2-f2e1-e511-8106-000d3a22ebb4
Removing triggering conditions
Adding link-entity and condition for activity party

Resources

The complete source code for this plugin, including a bit more extra debugging information than shown here, can be downloaded here.
A CRM solution with the customized activity view, modified contact form, the plugin assembly and a plugin step can be downloaded here as managed version and unmanaged version.

OnChange part 3: Where is the clearOnChange method?

The Microsoft Dynamics CRM SDK comes with lots of functionality to affect how CRM behaves on the client side.
In a couple of articles I will discuss the opportunities ventilate my frustration over run-time event handling manipulation.

My first article in this series covered problems when trying to remove design-time defined onChange events in run-time using removeOnChange. The second one covered problems with context awareness after manipulating event handlers using addOnChange. To sum up the difficulties described in the two previous articles, it would be very useful to be able to say

Ok, I cannot remove the design-time event handlers, and there are some issues with manually added event handlers. So please, remove everything to give me a clean slate!

But there is no method to clear an attribute from all current event handlers.

To be able to remove an event handler, you must know its exact syntax. And you just can’t remove event handlers that were introduced design-time. It seems that introducing addOnChange and removeOnChange had a good intention and was a nice try, but they definitely didn’t go all the way.

OnChange part 2: addOnChange with context awareness

The Microsoft Dynamics CRM SDK comes with lots of functionality to affect how CRM behaves on the client side.
In a couple of articles I will discuss the opportunities ventilate my frustration over run-time event handling manipulation.

My first article in this series covered problems when trying to remove design-time defined onChange events in run-time. This article will cover some issues with “context awareness” that you may encounter when using addOnChange to alter the events firing from changes in the form. A javascript web resource for a form typically has some event handlers and some business logic methods, all contained in a javascript “namespace” according to recommendations in the SDK, see section Namespaced library names. It may look something like this:

Cinteros.JR.account = {

    // ---- Event handlers ---- //

    formLoad: function () {
        var attribute = Xrm.Page.getAttribute("numberofemployees");
        attribute.removeOnChange(Cinteros.JR.account.numberofemployees_onChange);
        attribute.addOnChange(Cinteros.JR.account.numberofemployees_onChange_with_vat);
    },

    numberofemployees_onChange: function () {
        var avgSalary = Xrm.Page.getAttribute("jr_avgsalary").getValue();
        var empCount = Xrm.Page.getAttribute("numberofemployees").getValue();
        var salaryCost = this.multiply(avgSalary, empCount);
        Xrm.Page.getAttribute("jr_salarycost").setValue(salaryCost);
    },

    numberofemployees_onChange_with_vat: function () {
        var avgSalary = Xrm.Page.getAttribute("jr_avgsalary").getValue();
        var empCount = Xrm.Page.getAttribute("numberofemployees").getValue();
        var vat = Xrm.Page.getAttribute("jr_vat_level").getValue();
        var salaryCost = this.multiply(avgSalary, empCount);
        var netsalaryCost = salaryCost - this.multiply(salaryCost, vat);
        Xrm.Page.getAttribute("jr_salarycost").setValue(netsalaryCost);
    },

    // ---- Business logic methods ---- //

    multiply: function (a, b) {
        return a * b;
    }
}

Notice how the internal method multiply is called by using syntax

  this.multiply(a, b);

This indicates that it is a method in the same (current) “namespace”. This works fine and as expected when the event handler (numberofemployees_onChange in this case) has been added in the form customizations.
But when the event handler is added using attibute.addOnChange, it is not the call to the method that is passed to the function, but rather the “contents” of it. This in turn does not have a clue about my namespace, when executed.
An alternative could be to try to wrap my my function in a caller when adding it:

attribute.addOnChange(function () { Cinteros.JR.account.numberofemployees_onChange_with_vat(); });

This works, but makes the code harder to read and easier to get wrong. Note that the same – exactly the same – wrapper method must be used if you want to un-hook the event handler using attribute.removeOnChange. And even when trying that, I failed to get the remove function to work as expected.

Alternative javascript strategy

The alternative strategy described in the SDK discuss making methods unique by simply defining a specific prefix for all functions, and declaring them somthing like this:

// ---- Event handlers ----
function Cinteros_JR_account_formLoad() {
    var nameAttribute = Xrm.Page.getAttribute("name");
    nameAttribute.removeOnChange(Cinteros_JR_account_name_onChange);
    nameAttribute.addOnChange(Cinteros_JR_account_name_onChange_special);
    nameAttribute.addOnChange(function () { Cinteros_JR_account_name_onChange_special(); });
};

function Cinteros_JR_account_name_onChange() {
    var avgSalary = Xrm.Page.getAttribute("jr_avgsalary").getValue();
    var empCount = Xrm.Page.getAttribute("numberofemployees").getValue();
    var salaryCost = Cinteros_JR_account_multiply(avgSalary, empCount);
    Xrm.Page.getAttribute("jr_salarycost").setValue(salaryCost);
};

function Cinteros_JR_account_name_onChange_special() {
    var avgSalary = Xrm.Page.getAttribute("jr_avgsalary").getValue();
    var empCount = Xrm.Page.getAttribute("numberofemployees").getValue();
    var salaryCost = Cinteros_JR_account_multiply(avgSalary, empCount);
    var vat = Xrm.Page.getAttribute("jr_vat_level").getValue();
    var netsalaryCost = Cinteros_JR_account_multiply(salaryCost, vat);
    Xrm.Page.getAttribute("jr_salarycost").setValue(netsalaryCost);
};

    // ---- Business logic methods ----

function Cinteros_JR_account_multiply(a, b) {
    return a * b;
};

This is a strategy I don’t like very much. The “JScript object to create a kind of namespace” (as Microsoft puts it) provides better source code management and maintainability, and is simply far more appealing.
But unfortunately, it does not work when you use the SDK functions to manipulate event handlers.
(Again, I wish someone will prove me wrong in this. Can’t wait to write that apology to the Dynamics team…!)

To Be Continued… OnChange part 3: Where is the clearOnChange method?

OnChange part 1: Remove design-time event handlers

The Microsoft Dynamics CRM SDK comes with lots of functionality to affect how CRM behaves on the client side.
In a couple of articles I will discuss the opportunities ventilate my frustration over run-time event handling manipulation.

Events reacting to changes in fields can be defined design-time by making customizations to forms, and run-time by adding onChange event handlers by calling attribute.addOnChange(eventHandler).
To change the behavior run-time, there is also a method to remove designated event handlers by calling attribute.removeOnChange(eventHandler).
Read more about this in the SDK.

It is however not possible to remove event handlers that were added design-time.

The definition of methods addOnChange and removeOnChange are quite straight forward. You pass a function pointer to the method you want to add or remove. Much like you do when you define event handlers design-time. account.name.onchange In a scenario where a field is designed to have a specific event handler and you want to remove it to be replaced by another one, you would think that you call removeOnChange with the method name as entered in the customizations. Sample form library:

Cinteros.JR.account = {
    formLoad: function () {
        var nameAttribute = Xrm.Page.getAttribute("name");
        nameAttribute.removeOnChange(Cinteros.JR.account.name_onChange);
        nameAttribute.addOnChange(Cinteros.JR.account.name_onChange_special);
    },

    name_onChange: function () {
        // Do standard things with name attribute
        Xrm.Utility.alertDialog("Normal function"); 
    },

    name_onChange_special: function () {
        // Do special things with name attribute
        Xrm.Utility.alertDialog("Special function"); 
    }
}

I discovered that the call to removeOnChange had no effect in this scenario. But the addOnChange added the new method as event handler. So I started digging using the F12 debugger in my browser. What does the function really do? And why does it fail silently?
All events firing on field changes in a form are stored in one of two arrays. The first one contains “Sys.EventHandlerList” items that are events defined by CRM itself or by customization. The second one contains the “dynamic events” added by the addOnChange method.
When removing events, both of these arrays are investigated for the function to remove. So why then does it not find and remove the function as I want it to?
The answer lies in what is actually contained in these arrays. When I step my way down the internal functions of CRM, I am able to investigate what an event handler from customization according to the image above will result in within that array, and how it is matched from the removeOnChange function.
The list of event handlers from customization above contains this:

[
    function name_onchange_handler(eventObj, eventArgs) {
        try {
            var eContext = Mscrm.FormUtility.constructExecutionObject(eventObj, 0, null, null);
            eContext = Mscrm.FormUtility.constructExecutionObject(eventObj, 0, null, eContext);
            var t_s = new Date().getTime();
            Cinteros.JR.account.name_onChange();
            var t_e = new Date().getTime();
            Mscrm.MetricsReporting.instance().addMetric('form_event_onchange_function_Cinteros.JR.account.name_onChange', t_e - t_s);
        } catch (e) {
            displayError('name', 'onchange', e.description);
        }
    }
]

As you can see the function name that was entered in the customizations is now wrapped to extract the context (which is not even used after extraction in this case) and some timing and metrics, and of course the try-catch-wrapper.
And the function reference passed to removeOnChange contains – not the name of the function in my javascript, but the contents, which equates to this:

function () { 
    // Do standard things with name attribute 
    Xrm.Utility.alertDialog("Normal function"); 
}

There is no hint at all to what the actual method to remove was, simply the code that would be executed.
So when the remove function calls Array.remove(list, item), there will of course be no match as the code block above is not found in the array and the customized event handler will still fire for every onChange event on the field.
The only way to actually remove a design-time event handler, I guess, would be to perform the same wrapping when you call the removeOnChange, something like this:

nameAttribute.removeOnChange(function name_onchange_handler(eventObj,eventArgs) { try { var eContext=Mscrm.FormUtility.constructExecutionObject(eventObj,0,null,null); eContext=Mscrm.FormUtility.constructExecutionObject(eventObj,0,null,eContext); var t_s = new Date().getTime(); Cinteros.JR.account.name_onChange(); var t_e = new Date().getTime(); Mscrm.MetricsReporting.instance().addMetric('form_event_onchange_function_Cinteros.JR.account.name_onChange', t_e - t_s); } catch(e) { displayError('name', 'onchange', e.description); } });

Unfortunately, I haven’t been able to come up with the correct formatting of this string to make it work. And it would probably not be very supported.
(I sincerely hope someone will give me a sarcastic comment on my grave misunderstanding, and a pointer to how this should be done, and I would have to post an official apology to the Dynamics team… please, please, correct and enlighten me! 🙂

To Be Continued… OnChange part 2: addOnChange with context awareness

Xrm.Utility methods in MS Dynamics CRM UR8

  • Have you ever used the unsupported javascript-function openObj to open a form in Microsoft Dynamics CRM 2011?
  • Have you ever cursed out loud over getting correct paths and parameters for URL Addressable Forms?
  • Have you ever implemented your own functionality to open a Microsoft Dynamics CRM 2011 webresource in a new window?

Stop that. Now. At last, in UR8 Microsoft has included supported javascript-functions for those actions, providing a better user experience as well as nicer code than using the functionality of URL Addressable Forms and Views. No new SDK version has been released yet, so you cannot read about it or find any examples there, it was just recently announced in The Microsoft Dynamics CRM Blog.

Basic description

There is a javascript library called Xrm.Utility which is available “everywhere” as long as you have a CRM context.

Xrm.Utility.openEntityForm(name, id, parameters)
Xrm.Utility.openWebResource(webResourceName, webResourceData, width, height)

Both functions return the window object that is created which allows you to e.g. move and resize the window.
The parameters parameter can be used to set default values when creating a new record and to specify which form to display.
One of the best things though – is that the openEntityForm function takes the LogicalName of the entity instead of forcing us to make a metadata request to get the ObjectTypeCode…!

Usage examples

openEntityForm
  • Open a record from a custom html or Silverlight displaying CRM data
  • Open a new record form from a custom ribbon button populating with default data
  • Create a new record in javascript and then opening that new record
openWebResource
  • Open a webresource from a custom ribbon button (e.g. html page with Bing map integration)
  • Prompt user for confirmation using your own nicely styled Confirm dialog (instead of ugly styled window.confirm(…))

Thank’s Markus for enlightening me about this news!