Sunday, June 28, 2009

CRM 4.0 Working with Strings


Many CRM development tasks involve manipulation and concatenation of strings, whether it’s concatenating a Field DavaValue from 2 or more fields, building messages that involve dynamic data, constructing FetchXml Requests and Soap messages or constructing strings that contain CSS or other meaningful html information, in one way or another you end up writing a script that looks like the following examples:


//Exampe 1
var someValue = “The account number: ” + crmForm.accountnumber.DataValue + “ is not valid\n”;
someValue+= “More helpful info on how to fill a valid account number here.”

alert(someValue);

or

//Example 2
var iLeft = 100;
var iTop = 100;
var cssText = “position:absolute;left:” + iLeft + “;top:” + iTop + “;”;
document.all[“”].style.cssText = cssText;

or

//Example 3
var iTop = 50;
var iLeft = 50;
var iWidth = 800;
var iHeight = 600;
var windowFeature = “toolbars=0;width=” + iWidth + “,height=” + iHeight + “,top=” + iTop + “,left=” + iLeft;
window.open( “” , “” , windowFeatures);

There are several disadvantages of using this simple concatenation technique:
1.The first which is the most obvious and discussed here is the lack of multi-lingual support for text messages. If for instance you need to translate the text that appears in the first example above, how can you accomplish the task and still keep the dynamic value concatenation in the correct context?

2. Another major disadvantage involves construction of large strings such as when you build soap message or a large FetchXml queries which can dramatically decrease IE responsiveness and cause performance issues.

3. Large string that are constructed in this manner sometimes make it impossible to read and debug

The following JavaScript objects facilitates these types of tasks and will help you avoid the problems mentioned above.

The first code snippet facilitates the concatenation of strings using a class called StringBuilder (similar to C# System.Text.StringBuilder mechanism)

/* JS String Builder */
StringBuilder = function()
{
var parts = [];
this.Append = function( text ){parts[ parts.length ] = text;return this;}
this.Reset = function(){parts = [];}
this.ToString = function(){return parts.join( "" );}
}

//Usage
function OnCrmPageLoad()
{
//Solving example 2
var iLeft = 100;
var iTop = 100;
var cssText = new StringBuilder();
cssText .Append(“position:absolute;left:”).Append(iLeft).Append( “;top:”).Append(iTop).Append( “;”);
document.all[“”].style.cssText = cssText.ToString();

//Solveing example 3
var iTop = 50;
var iLeft = 50;
var iWidth = 800;
var iHeight = 600;

var windowFeature = new StringBuilder();
windowFeature.Append(“toolbars=0,”);
windowFeature.Append(“width=”).Append(iWidth).Append( “,”);
windowFeature.Append(“height=”).Append(iHeight).Append( “,”);
windowFeature Append(“top=”).Append(iTop).Append(“,”);
windowFeature.Append(“left=”).Append(iLeft);

window.open( “” , “” , windowFeatures.ToString());
}

OnCrmPageLaod();

The second code snippet contains an extension function to the built-in JavaScript string object which helps you solve the problem mentioned in example 1 above and also makes it possible to write code similar to the String.Format c# method.

String.prototype.Format = function( args )
{
var result = this.replace( /\{(\d{1})\}/ig ,
function match()
{
return args[arguments[1]];
}
);

return result;
}

function OnCrmPageLoad()
{
//Solve Exampe 1
var someValue = new StringBuilder();
someValue.Append(“The account number: {0} is not valid\n”.Format([crmForm.accountnumber.DataValue]));
someValue.Append(“More helpful info on how to fill a valid account number here.”);

alert(someValue.ToString())

//Solve Example 2
var iTop = 50;
var iLeft = 50;
var iWidth = 800;
var iHeight = 600;

//Create string template with place holders
var windowFeature = “toolbars=0;top={0},left={1},width={2},height={3}”;
//Format the String
windowFeature = windowFeature.Format([iTop,iLeft,iWidht,iHeight]);
window.open( “” , “” , windowFeatures);
}

OnCrmPageLoad();

The last example extends the StringBuilder class and creates a StyleBulder that is used to concatenate CSS rules with ease

StyleBuilder = function()
{
var cssText = new StringBuilder();
this.Add = function( key , value ){cssText.Append( key ).Append( ":" ).Append( value ).Append( ";" );}
this.ToString = function(){return cssText.ToString();}
}

//Usage
function OnCrmPageLoad()
{
//Solve Example 2
var iLeft = 100;
var iTop = 100;
var cssText = new StyleBuilder();
cssText.Add(“position”,”absolute”);
cssText.Add(“left”, iLeft);
cssText.Add(“top”,iTop);

document.all[“”].style.cssText = cssText.ToString();
}

OnCrmPageLoad();

I hope you find this interesting and helpful.

Saturday, June 27, 2009

CRM 4.0 Adding a helper button to text fields




Sometimes you want to attach a click event to a text field. Since CRM does not provide a way to associate a button with a CRM field you need to implement the functionality using JavaScript. The following TextHelperButton object helps you achieve that goal for any given text field.

When creating an instance of the TextHelperButton set the following parameters:
Image width – This is used to adjust the image positioning.
MouseOver image URL – The image that is displayed when you go over the button.
MouseOut Image URL – The default image URL
Click – A function to call when the user clicks on the image.

Paste the code inside the entity onload event and enjoy...


TextHelperButton = function(fieldId)
{
var fldButton = this;

fldButton.Field = crmForm.all[fieldId];

if (!fldButton.Field)
{
return alert("Unknown Field: " + fieldId);
}

fldButton.Click = null;
fldButton.Image = new ButtonImage();
fldButton.Paint = function()
{
var field_d = document.all[fldButton.Field.id + "_d"];
if (field_d)
{
field_d.style.whiteSpace = "nowrap";
field_d.appendChild(fldButton.Image.ToObject())
}
}

fldButton.MouseOver = function()
{
event.srcElement.src = fldButton.Image.MouseOver;
}

fldButton.MouseOut = function()
{
event.srcElement.src = fldButton.Image.MouseOut;
}

function ButtonImage()
{
this.MouseOut = "";
this.MouseOver = "";
this.Width = 21

this.ToObject = function()
{
var img = document.createElement("IMG");
img.onmouseover = fldButton.MouseOver;
img.onmouseout = fldButton.MouseOut;
img.onclick = fldButton.Click;
img.src = this.MouseOut;

var cssText = "vertical-align:bottom;";
cssText+= "margin:1px;";
cssText+= "position:relative;";
cssText+= "right:" + (this.Width + 1) + "px";
img.style.cssText = cssText;
return img;
}
}
}

function OnCrmPageLoad()
{
/* pass the name of the crm field as parameter */
var actnButton = new TextHelperButton("name");
/* set the image button width */
actnButton.Image.Width = 21; //integer
/* supply image rollover URLS */
actnButton.Image.MouseOver = "/_imgs/lookupOn.gif";
actnButton.Image.MouseOut = "/_imgs/lookupOff.gif";
/* supply an function that is called when the image is clicked */
actnButton.Click = Accountnumber_Click;
/* add the image next to the field */
actnButton.Paint();
}

function Accountnumber_Click()
{
alert('Account Number Field Clicked');
}

OnCrmPageLoad();

CRM 4.0 Creating a JS Resource Manager

The purpose of this post is to present a simple and effective way of handling multi-lingual text resources on the client (CRM) Form.

In most projects an application is built to address a single language. Knowing that from the get go simplifies the way developers weave (hard code) the application messages into each entity form.

Let’s assume, for example, that you need to validate that the account number starts with the letters “ACT ”. Your code might look like the following example:


if (/^ACT\s{1}/.test(crmForm.accountnumber.DataValue) == false)
{
alert(”Account number must begin with ACT ”);
}


This is a very simple and strait forward way to present messages to the user. However since CRM is a multi-lingual application using this approach is far from being a best practice. The reason is that your client might decide (eventually) to add another language to the system and once he does that you must rewrite you application to support the new language. Assuming you have a small amount of messages you might consider changing your code as follows:


if (/^ACT\s{1}/.test(crmForm.accountnumber.DataValue) == false)
{
if (USER_LANGUAGE_CODE == 1033) //English United States
{
alert(”Account number must begin with ACT ”);
}
else if (USER_LANGUAGE_CODE == 1043) //Dutch
{
alert(”Rekeningnummer moet beginnen met ACT ”);
}
}


Now, as long as the application stays small this solution should hold. However, applications tend to grow over time and from a certain point the amount of messages will be too overwhelming to manage in this manner.

Another reason why this approach is a bad practice is that is obligates or ties the client to an ever lasting development phase. The best approach is to shift the responsibility of handling multi-lingual tasks to the client and the best way to do that is to create a resource manager that enables you to support multi-lingual messages from your first line of code.

So how do we shift responsibility of translating our application messages to the client?
The simplest approach is to create a new text attribute for each message that you need to display. The text attribute display name can hold the message it self. For example:

New Attribute: new_msginvalidaccountnumber
Display Name: The account number must begin with “ACT “
Searchable: No
Required:No

Now, put the new attribute on the CRM form under a new tab called Labels and hide it when the form loads. E.g.


function OnCrmPageLoad()
{
document.all.tab4Tab.style.display = “none” //assuming that the fifth tab is the Labels tab.
}

OnCrmPageLoad();


Now, let’s transform the above code so it would support any language


/* --< Resource Manager >-- */
ResourceManager = function()
{
var rm = this;
rm.GetString = function( resId )
{
var resource = document.getElementById( resId );
if ( !resource )
{
/* Show missing label */
return "[" + resId + "]";
}

return crmForm.GetLabel( resource );
}
}
/* Create an instance of Resource Manager */
RM = new ResourceManager();

if (/^ACT\s{1}/.test(crmForm.accountnumber.DataValue) == false)
{
alert(RM.GetString(“new_msginvalidaccountnumber”));
}


Integrate the resource manager to each entity onload event.

Good luck…

Friday, June 12, 2009

Summarizing an Appointment field on a Parent Entity


Imagine you have a custom field called number of participants on and appointment entity and you want to summarize this field for all the appointments under the same regarding parent.

This looks simple at first glance i.e. register a plug-in on the parent post Create and Update messages and you’re done. However, since this entity is part of dynamics scheduling engine and affects rescheduling you also need to register your plug-in on the Book and Reschedule messages.

Actually the Book and Reschedule messages somewhat replace the Create and Update messages. That means that when you create a new Appointment the Book message is fired and when you Update an existing appointment the reschedule message is fired.

So why do we need to also register the plug-in on the Create and Update messages?
The answer is that when you reschedule or book an appointment and the user is not available at that point in time the scheduling engine halts the execution and enables the user to cancel the operation. If the user decide to disregard (ignore) that warning and save anyway then the Create or Update are fired to complete the operation.

Since the summarizing of the field can be a long operation you should also register the plug-in for asynchronous execution.

Another thing that is worth mentioning is code synchronization. Since CRM does not lock record and two users can update the same appointment at the same time it is also advisable to lock the process that updates the parent field to avoid data corruption.

Last Important remark: the regarding lookup (parent entity) is required for this operation. Since the Book and Reschedule messages don’t allow you to register Post Images you need to send the regarding field in each operation. E.g.

//Put this code in the appointment onload event handler

if (crmForm.regardingobjectid.DataValue != null)
{
crmForm.regardingobjectid.ForceSumbit = true;
}


Plug-in Registered on the Reschedule, Book, Create and Update Asynchronous Parent Pipeline


using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;
using Microsoft.Crm.Sdk.Query;

namespace GI.Crm.Sandbox
{

public class SummarizeAppointmentFieldHandler : IPlugin
{
private Object SyncObject = new Object();

public void Execute(IPluginExecutionContext context)
{
/* Validates that the user is available */
if (context.OutputParameters.Contains(ParameterName.ValidationResult))
{
ValidationResult validationResult =
context.OutputParameters[ParameterName.ValidationResult] as ValidationResult;
if (validationResult.ValidationSuccess == false)
{
return;
}
}

/* Validate Target Entity */
if (!context.InputParameters.Contains(ParameterName.Target))
{
return;
}

DynamicEntity Target = context.InputParameters[ParameterName.Target] as DynamicEntity;

/*
We need both regarding (parent) entity id and number of participants field
Client side must Force Submit on regarding field
*/
if ( !Target.Properties.Contains("regardingobjectid") ||
!Target.Properties.Contains("gi_noofpart"))
{
return;
}

Lookup regardingObjectId = Target.Properties["regardingobjectid"] as Lookup;
/* Validate that the Appointment is Regarding the Correct parent entity */
if (regardingObjectId.type != "gi_custom")
{
return;
}

CrmNumber noofPart = Target.Properties["gi_noofpart"] as CrmNumber;

/* Validate the number of Participants */
if (noofPart.Value == 0)
{
return;
}

/* Synchronize access to this code block */
lock(SyncObject)
{
#region Retrieve all Regarding entity Appointments
QueryExpression allPartQuery = new QueryExpression();
allPartQuery.ColumnSet = new ColumnSet();
allPartQuery.ColumnSet.AddColumn("gi_noofpart");
allPartQuery.Criteria.AddCondition(
"regardingobjectid" , ConditionOperator.Equal , regardingObjectId.Value
);
allPartQuery.Distinct = false;
allPartQuery.EntityName = EntityName.appointment.ToString();

RetrieveMultipleRequest retMultiRequest = new RetrieveMultipleRequest();
retMultiRequest.Query = allPartQuery;
retMultiRequest.ReturnDynamicEntities = true;

RetrieveMultipleResponse retMultiResponse =
(RetrieveMultipleResponse)context.CreateCrmService(true).Execute(retMultiRequest);

#endregion

#region Summaries all Appointments Number of Participants
Int32 Summery = 0;
foreach(DynamicEntity appointment in retMultiResponse.BusinessEntityCollection.BusinessEntities)
{
if (appointment.Properties.Contains("gi_noofpart"))
{
Summery += ((CrmNumber)appointment.Properties["gi_noofpart"]).Value;
}
}
#endregion

#region Update Parent entity Number of Participants
/* Key Property */
Key parentEntityKey = new Key(regardingObjectId.Value);
KeyProperty parentEntityKeyProp = new KeyProperty("gi_customid", parentEntityKey);
/* Number of participants Property */
CrmNumberProperty noofPartProp = new CrmNumberProperty("gi_noofpart",new CrmNumber(Summery));

DynamicEntity parentEntity = new DynamicEntity(regardingObjectId.type);
parentEntity.Properties.Add(parentEntityKeyProp);
parentEntity.Properties.Add(noofPartProp);

TargetUpdateDynamic targetEntity = new TargetUpdateDynamic();
targetEntity.Entity = parentEntity;
UpdateRequest updateRequest = new UpdateRequest();
updateRequest.Target = targetEntity;
context.CreateCrmService(true).Execute(updateRequest);
#endregion
}
}
}
}

Thursday, June 11, 2009

CRM 4.0 Workflow Query Wizard


The WQW wizard is a specialized query builder that we integrated into dynamics workflow engine. The wizard facilitates the creation of aggregate queries without the need to write custom code for each requirement that we have. This helps us create various complex workflow rules that are not available by the out of box workflow designer. A good usage example is when you need to create Monitoring / Alerting workflows or need to take some action depending on existing or non-existing amount of records that answer a specific set of conditions.

The WQW also facilitates the creation of specific business counters on the parent entity. This is done by updating the parent entity with the WQW result using the workflow built in functionality (update record step). One of the biggest advantages of using the WQW and the business counters is that it can help you create not-in queries.

Consider, for example, a scenario where you need to find accounts without contacts or open incidents.
by creating a WQW workflow step and a new account attribute (counter attribute) called number of accounts / number of open incidents you can write the WQW result to each attribute counter. This enables the user to retrieve all accounts that have 0 contacts / or 0 open incidents in each respective account counter.

The video demonstration illustrates 2 simple scenarios that make the WQW such a wonderful workflow addition.
The first scenario monitors the amount of long duration outgoing calls that are made by a user. The second scenario alerts a manager when too many on-hold cases are open for a specific user. These are just examples. There is no limit on the type of Alerts or Actions that you can take using the wizard. As long as you are able to build a query around your requirement the wizard will facilitate the actions and enable you to easily create workflow rules depending on the their results.

Another feature that the WQW has to offer is the dynamic binding of values in the current workflow context. If you take a look at the second scenario you’ll notice that the WQW uses the owner attribute dynamically. Since the owner is not known at design time the values are taken from the context at runtime and integrated into the query.

I’m sure this addition will save you hours of tedious development effort. If you have question regarding the wizard fill free to post them here.
The wizard will be available on our website next week. Enjoy…

In order to rewind right click on the flash movie and select play

Wednesday, June 10, 2009

CRM 4.0 Queue Security Manager Wizard




As you know, Queues in CRM are organizational entities. This means that if the user role has read access rights on the Queue entity he can see all Queues. Organization that uses Queues as a concept usually search for a way to moderate Queue access based on Business Unit, Role and sometimes specific Users. This type of requirement is especially important for call centers and application that use queues as intermediary facilities or what I call bus stops for work that spin many users and business processes.

Our Product also makes substantial use of Queues throughout our solution. Knowing that the way CRM handles Queues will not serve the purpose we built a wizard that enables us to define Queue visibility for any security settings. If you’re looking for a wizard like solution that enables you to control Queue access with a few clicks (No Coding Required) then you’ll love this wizard.

The QUM wizard, as all our wizards, supports rollup4, IE8, IFD and can be installed on a 64 bit environment.



The wizard can be obtained as a stand alone product however we also included it in our security package and partner starter pack for no additional cost.

The wizard is now available on GI online website