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…