Monday, July 21, 2008

Client Side Scripting - More JavaScript Code - Part 5 (STUNNWARE)

STUNNWARE

It's time to continue the "More JavaScript" series. Here's the fifth article containing more snippets and I hope that you find them as valuable as the four previous articles. I also updated the JavaScript Snippets Directory.

Formatting international phone numbers

The CRM SDK contains a sample to format US phone numbers and it works pretty well. However, there are customers outside the US and the sample doesn't work with international phone numbers. An easy formatting rule is replacing any occurrence of '(', ')' or a space with a dash. People can then enter the phone number in their preferred way, but get the same output.

var originalPhoneNumber = "+49 (89) 12345678";
var formattedPhoneNumber = originalPhoneNumber.replace(/[^0-9,+]/g, "-");
formattedPhoneNumber = formattedPhoneNumber.replace(/-+/g, "-");
alert(formattedPhoneNumber);

The first call to the replace method changes every character in the input string that is not a digit and not the plus sign (which is used for international
numbers) to the dash symbol. However, the output is +49--89--12345678, so the second call replaces all occurrences of multiple dashes with a single
one, giving a final result of +49-89-12345678.

Rounding numerical fields

Rounding fields is done similar to any other programming language. The following method rounds a float value using a precision of two decimal places:

function round(value) {
 return Math.round(value * 100) / 100;
}

The Math object defines three methods for rounding operations:

  • Math.ceil(arg): Returns an integer value equal to the smallest integer greater than or equal to its numeric argument.

  • Math.floor(arg): Returns an integer value equal to the greatest integer less than or equal to its numeric argument.

  • Math.round(arg): If the decimal portion of number is 0.5 or greater, the return value is equal to the smallest integer greater than number. Otherwise, round returns the largest integer less than or equal to number.

Be aware of null values in Boolean fields

When working with Boolean fields it's seems natural to compare the value to either true or false:

var value = crmForm.all.my_bool.DataValue;

if (value == true) {
    //do something
}

else {
    //do something else
}

However, the value may also be null and you should make sure that your code handles it correctly:

var value = crmForm.all.my_bool.DataValue;

if (value == null) {
    //do appropriate steps if there is no value
}

else if
(value == true) {
    //do something
}

else {
    //do something else
}

If you want to execute either the "true" part or the "false" part when no value is set, then I recommended the following (including the comment, to make it obvious):

var value = crmForm.all.my_bool.DataValue;

//Default to true if no value is set
if
(value == null) {
    value = true;
}

if (value == true) {
    //do something
}

else {
    //do something else
}

Reusing code in OnLoad and OnChange event handlers

I often see code like this:

OnLoad:

if (crmForm.all.my_lookup_field.DataValue != null) {
    crmForm.all.my_text_field.DataValue = crmForm.all.my_lookup_field.DataValue[0].name;
}

OnChange:

if (crmForm.all.my_lookup_field.DataValue != null) {
    crmForm.all.my_text_field.DataValue = crmForm.all.my_lookup_field.DataValue[0].name;
}   

The code is identical in both events: it copies the display name of the selected item in a lookup field to a text box. Later you notice that it doesn't remove an existing value in the text field when the user removes the lookup selection and change the OnChange code to this:

if (crmForm.all.my_lookup_field.DataValue != null) {
    crmForm.all.my_text_field.DataValue = crmForm.all.my_lookup_field.DataValue[0].name;
}

else {
    crmForm.all.my_text_field.DataValue = null;
}

It sometimes happens that you forget to change the OnLoad code as well, so instead of copy paste the code between OnChange and OnLoad, you should use one of the following implementation styles:

1. Implementing the code in the OnChange event handler and using FireOnChange to execute it in the OnLoad event:

OnLoad

crmForm.all.my_lookup_field.FireOnChange();   

OnChange

if (crmForm.all.my_lookup_field.DataValue != null) {
    crmForm.all.my_text_field.DataValue = crmForm.all.my_lookup_field.DataValue[0].name;
}

else {
    crmForm.all.my_text_field.DataValue = null;
}

2. Implementing the code in OnLoad and calling it from the OnChange event:

OnLoad

MyLookup_OnChange = function() {
    if (crmForm.all.my_lookup_field.DataValue != null) {
        crmForm.all.my_text_field.DataValue = crmForm.all.my_lookup_field.DataValue[0].name;
    }

    else {
        crmForm.all.my_text_field.DataValue = null;
    }
}

MyLookup_OnChange();

OnChange

MyLookup_OnChange();

The second implementation has the benefit that all of your code is in a single place.

Tip: The code can be rewritten to this:

var lookupValue = crmForm.all.my_lookup_field.DataValue;
crmForm.all.my_text_field.DataValue = (lookupValue == null) ? null : lookupValue[0].name;

Getting notified when the user selected an address in the address picker (quote, order, invoice)

When working with quotes, orders and invoices, you pick an address to specify the bill to and ship to address. Though the address fields are properly filled with the selected values, no OnChange event is executed in the CRM form. Here's an easy but unsupported way to be informed when the user closes the address lookup dialog:

if (document.all._MBLookupAddress != null) {
    document.all._MBLookupAddress.onclick = function() {
        LookupAddress();
        alert("Address lookup closed");
    }
}

You don't know if the user selected an address or canceled the operation though. You can use the same idea to override other built-in functions, but as said, it's unsupported and may break in future releases.

What is "if (condition) ? statement1 : statement2"?

I'm using the above notation a lot because it's an easy way to set a value to one out of two values. This construct is available in C, C++, Java, C# and I'm sure that most other languages have similar commands. I noticed though that it's not as as clear as I thought, so here's the same using a standard if/then/else:

if (condition) {
    statement1;
}

else {
    statement2;
}

And here's a real example:

var s = (crmForm.all.my_lookup.DataValue == null) ? null : crmForm.all.my_lookup.DataValue[0].name;

And the same with an if/then/else:

var s;

if (crmForm.all.my_lookup.DataValue == null) {
    s = null;
}

else {
    s = crmForm.all.my_lookup.DataValue[0].name;
}

Copying the display name of a selcted lookup value into a textbox

See the sample above.

Retrieving all fields inside a CRM form

If you want to loop over all fields (input fields) on a CRM form, you can use the following script as a starting point:

for (var index in crmForm.all) {
    var control = crmForm.all[index];

    if (control.req && (control.Disabled != null)) {
        //control is a CRM form field
    }
}

The conditions mean that a control must have the "req" attribute and the "Disabled" method. This seems a good indicator for a CRM form field.

Knowing if you are running in CRM 3.0 or CRM 4.0

It's fairly easy to differentiate if your code is running on CRM 4.0 or not. Just pick a method or variable that did not exist in CRM 3.0 and check if it is available:

if (typeof(GenerateAuthenticationHeader) == "undefined") {
    alert("Version 3");
}

else {
    alert("Version 4");
}

GenerateAuthenticationHeader was introduced in CRM 4.0 and is a global function available in all forms.

Showing/Hiding tabs based on the selection in a picklist

The next script shows one out of three tabs based on the selection in the new_combo field. It hides all tabs if no selection is made or a different value is selected.

OnLoad:

//Sanity check: if new_combo is not present on the form, then don't call FireOnChange
if
(crmForm.all.new_combo != null) {
    crmForm.all.new_combo.FireOnChange();
}

OnChange:

//Check for create, update, read-only or disabled form
if ((crmForm.FormType >= 1) && (crmForm.FormType <= 4)) {

    var value = crmForm.all.new_combo.DataValue;

    crmForm.all.tab1Tab.style.display = (value == "1") ? "none" : "";
    crmForm.all.tab2Tab.style.display = (value == "2") ? "none" : "";
    crmForm.all.tab3Tab.style.display = (value == "3") ? "none" : "";
}

Changing the background color of a form (CRM 4.0)

Instead of explaining in detail, simply copy the following code into the OnLoad event:

document.all.areaForm.style.backgroundColor = 'yellow';
document.all.tab0.style.backgroundColor = 'red';
document.all.tab1.style.backgroundColor = 'blue';
document.all.tab2.style.backgroundColor = 'green';
document.all.tab3.style.backgroundColor = 'cyan';

The above is for an entity with 4 tabs, like the default account form. If you have less tabs, then remove some of the lines at the end (tab0 = the first tab, tab1 = the second tab, ...).

Instead of using color names, you can also specify RGB values, e.g.

document.all.tab0.style.backgroundColor = '#A040FF';

Calculating the difference of two numerical fields

Seems easy enough:

var result = crmForm.all.num_field1.DataValue - crmForm.all.num_field2.DataValue;

It will break though if either of the two fields is null. Use the following instead:

var fieldValue1 = (crmForm.all.num_field1.DataValue == null) ? 0 : crmForm.all.num_field1.DataValue;
var fieldValue2 = (crmForm.all.num_field2.DataValue == null) ? 0 : crmForm.all.num_field2.DataValue;
var diff = fieldValue1 - fieldValue2;

You can use a different notation to check for the null value:

var fieldValue1 = crmForm.all.num_field1.DataValue ? 0 : crmForm.all.num_field1.DataValue;
var fieldValue2 = crmForm.all.num_field2.DataValue ? 0 : crmForm.all.num_field2.DataValue;
var diff = fieldValue1 - fieldValue2;

It depends on your coding style which version you prefer. The second is smaller but doesn't really tell what you are comparing, while the first explicitly cheks for a null value.

Disable all fields on a form

This is just a variation of the code shown in the "Retrieving all fields inside a CRM form" sample:

for (var index in crmForm.all) {
    var control = crmForm.all[index];

    if (control.req && (control.Disabled != null)) {
        control.Disabled = true;
    }
}

When events do not fire anymore

If your OnLoad, OnSave or OnChange code isn't executed at all, make sure that you have enabled the event first. It's always a good idea to check the obvious things first. If you have enabled the event and are not testing the code in the form preview, then ask yourself if you have published the changes.
If your code still isn't executed, place an alert('TEST'); as the first line into your code and try again. If you don't see the alert message, then you have a syntax problem in your code. The most common reason is a missing curly brace somewhere in your code and it may be in any event you have added to the CRM form. It may also be related to an inline comment using the "// my comment" notation. Try using "/* my comment */" instead.
 

No comments: