Monday, July 21, 2008

http://blogs.msdn.com/crm/archive/2008/07/08/accessing-a-sql-database-from-a-microsoft-dynamics-crm-plug-in.aspx

Ajith Gande and Peter Hecke
Published Tuesday, July 08, 2008 11:20 AM


Have you ever had the need to access data in a non-CRM SQL database from within a plug-in? Let’s say that you register a plug-in with Microsoft Dynamics CRM that will pull additional data from another SQL database in order to pre-populate a newly created entity’s attributes or perform some calculation using the data from both databases.

The problem that you will run into is that the system account that the plug-in executes under needs to have login and data access to the SQL server and database, which is not enabled by default. In Microsoft Dynamics CRM, all plug-ins execute under the system account named “NT AUTHORITY\NETWORK SERVICE”. If you take a look at any Microsoft Dynamics CRM database, you will see that a login exists for the NETWORK SERVICE account.

peter01

Your SQL server administrator will need to create a SQL server login and assign database access permissions and roles for the NETWORK SERVICE account in order for your plug-in to be able to access the SQL database. Once this is configured you can connect to the database using a trusted connection string.

Data Source=myServer;Initial Catalog=myDataBase;Integrated Security=SSPI;

An alternate approach to creating a SQL server login account is to have your plug-in establish a connection to the SQL server using a connection string which includes login information. For example:

Data Source=myServer;Initial Catalog=myDataBase;User Id=myUsername; Password=myPassword;Integrated Security=false

Note that you must use Integrated Security=false and not Integrated Security=SSPI. This method has the disadvantage of sending login information in clear text over the network, which is less secure. You are also going to have to either hardcode the login information in the plug-in or pass the information to the plug-in’s constructor at run-time. For more information on how to pass data to a plug-in at run-time, refer to the Microsoft Dynamics CRM 4.0 SDK documentation under the topic Writing the Plug-in Constructor.

How to Execute SQL Commands from a Plug-in using Impersonation

Sometimes you may need to execute SQL stored procedures or SQL commands in the context of the user who caused a plug-in to execute instead of the Network Service system user. You can achieve this using the Execute AS command in SQL.

The NT AUTHORITY\NETWORK SERVICE login (see previous figure), or the user ID used to connect from the plug-in without using Integrated authentication, in the SQL database should be granted the sysadmin role in order for impersonation to work.

Peter02

The following steps describe the process that a plug-in should implement.

1. Retrieve the domain name of the caller from Microsoft Dynamics CRM through the CrmService Web service. The systemuser entity contains domain information. You can execute a Retrieve on that entity to obtain the information.

2. Create the SQL connection to the target SQL database using the connection string specified in the secure or unsecure configuration attribute of the step. You can use integrated authentication or a hard coded SQL connection string as explained in the previous section of this blog.

3. Start the impersonation as the caller.

4. Execute any SQL commands or stored procedure that you want.

5. Revert the SQL execution context back to the Network Service system user.

The following plug-in sample code implements the previously described steps.

using System;

using System.Collections.Generic;

using System.Text;

using Microsoft.Crm.Sdk;

using Microsoft.Crm.SdkTypeProxy;

using System.Xml;

using System.Data.SqlClient;

using Microsoft.Crm.Sdk.Query;

public class AccessDatabase : IPlugin

{

   string m_secureConfig;

   string m_connectionString;

public string SecureConfig

   {

      get { return m_secureConfig; }

      set { m_secureConfig = value; }

   }

// Pass the connection string to the plug-in’s constructor.

   // The string is defined during plug-in registration.

   public AccessDatabase(string config, string secureConfig)

   {

      m_connectionString = config;

      m_secureConfig = secureConfig;

   }

   public void Execute(IPluginExecutionContext context)

   {

// Step 1. Get the domain name of the calling user.

      ICrmService crmService = context.CreateCrmService(false);

      systemuser callingUser = (systemuser)crmService.Retrieve(

EntityName.systemuser.ToString(), context.UserId,

new ColumnSet(new string[] { "domainname" }));

      // Step 2. Connect using a SQL connection string specified in the

      // configuration of step

using (SqlConnection conn =

new SqlConnection(m_connectionString))

      {

      conn.Open();

SqlCommand comm = conn.CreateCommand();

// Step3. Start SQL impersonation.

      comm.CommandText = @"Execute as Login='" +

         callingUser.domainname +"'; ";

      // Step 4. Run the SQL commands that need to be executed.

      comm.CommandText += "SELECT SUSER_NAME(); ";

// Step 5. Revert the context back to Network Service

      comm.CommandText += "revert;";

      comm.CommandType = System.Data.CommandType.Text;

// For demonstration purposes, display the username displayed

      // from the SELECT statement.

throw new InvalidPluginExecutionException(

         comm.ExecuteScalar().ToString());

      }

   }

}

For more information on the EXECUTE AS command, refer to http://msdn.microsoft.com/en-us/library/ms181362.aspx.

Cheers,

Ajith Gande and Peter Hecke

How it Works: SQL Server Reporting Services and Dynamics CRM

Barry Givens Published Friday, July 18, 2008 10:14 AM

At Tech-Ed Developer in Orlando a few weeks back I lead an interactive session on CRM and Business Intelligence. The session was open to any CRM and BI topic so I expected a lot of hard questions about data mining and the like but the topic of greatest interest turned out to be the CRM and SQL Server Reporting Services (SSRS) integration. This should be helpful to folks with questions about deployment practices or with an interest in exposing CRM reports to users outside of the CRM application.

First things:

  1. With CRM 4 reports are an entity within CRM. They have meta data and CRM security is applied to determine whether or not a user may view the report . Note that that is the report, not the underlying data. Users can have access to CRM data that a report points to and not have access to the report itself. Likewise a user may have access to a report but not the data that it would show (in which case the user could run the report but it would return no data).
  2. The SQL Reporting Services Report Viewer is an ASP.Net control which runs on the CRM 4.0 Web server. In CRM 3.0, and when you interact with the SSRS Report Manager, that control is running on the Web server fronting SSRS. When you choose to run a report from CRM 4.0 the ASP.Net control requests the report and data from the remote SSRS box. In practical terms: in CRM 3.0 the URL for a report was the URL for the SSRS Web server; in CRM 4.0 the URL for a report is the CRM Web server.

Because CRM 4.0 reports are always run in a delegated mode the CRM and SSRS integration has to handle security. There are two ways to do this in CRM 4.0. One way to do this is to use integrated authentication where trust for delegation is required between the CRM server, the SSRS server and the SQL server with the CRM db. This was the required configuration on CRM 3.0 and frankly, it was a bit of a headache for folks to manage [see HOW TO: Configure Kerberos authentication for Microsoft CRM 3.0 and Microsoft SQL Server Reporting Services and Microsoft CRM 3.0: Additional Setup Tasks Required if Reporting Services Is Installed on Different Server . ]

The other mechanism is to use the SQL Server Reporting Services MS CRM connector. This connector runs as an SSRS Data Processing Extension and handles all of the delegation for you. The use of the data connector is recommended for Internet facing deployments and anywhere users are not using NT Auth to connect to CRM. When using the Data Connector users of CRM cannot directly access the RDLs in SSRS – all management of reports must be done through the CRM reporting UI; users connecting to the SSRS Report Manager will get an access denied message if they try to browse Reports.

Choosing a deployment type is up to you and of course there are pros and cons either way. The following table describes some of those (if you have others throw them in the comments).

Capability

SQL Server Reporting Services Data Connector

Kerberos Authentication

Works with Internet Facing Deployments

Yes

No

Schedule reports using the Report Scheduling wizard in CRM

Yes

No

Uses NT credentials to connect to SQL Views

No

Yes

Access CRM reports outside of CRM

No

Yes

Use the CRM Report Wizard

Yes

Yes

Keeps CRM data secure

Yes!

Yes!

The table speaks for itself and I think that for most organizations the Connector is probably the right way to go. But let me point out one item that is near and dear to me: “Access CRM reports outside of CRM”. One of the great things about SSRS is its direct URL access to reports; along with that are the ability the embed reports into Microsoft Office SharePoint sites, in Performance Point dashboards, on your own ASPX pages using the ASP.Net control or my favorite: embedded with forms of the CRM application itself. If you use the connector you won’t be able to use URL access for reports; this is so useful though that we made sure to give you a work around.

clip_image001If you have the “Add Reporting Services Reports” privilege you’ll see a command on the Action menu of the Report form titled “Publish Report for External Use”. This command will publish your report and any child reports to a directory in SSRS that is open to all CRM users. You can embed the URL to that report, along with any arguments on the query string, within CRM or the Report Viewer controls.

You won’t get any feedback that this worked so you’ll just have to trust but verify that it did. Doing this multiple times will also overwrite any existing report with the same name in the target directory so this isn’t the most… elegant… solution but there isn’t a demo environment that I have that doesn’t take advantage of it.

 

Cheers,

Barry Givens

PinPoint

Microsoft Dynamics CRM is a full participant in the beta for PinPoint, a unified online business marketplace for small and medium-sized business customers and partners. Pinpoint helps customers discover, discern and engage with Microsoft partners to fulfill their unique technology needs, while unlocking business opportunities for partners.

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.
 

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

STUNNWARE

I published the last article of the "More JavaScript" series more than half a year ago and thought that there wasn't too much more to say. Seems that I was wrong with that assumption, so here's the fourth part. I also updated the JavaScript Snippets Directory accordingly.

Maximizing a form

Put the following two lines of code into any OnLoad event to maximize the form:

window.moveTo(0,0);
window.resizeTo(screen.availWidth, screen.availHeight);

moveTo moves the window to the specified location and resizeTo resizes it. screen is a global object and give you the available screen width and height in the corresponding properties.

Using a toolbar button to open a referenced entity

Let's say you have added a lookup field to a form referencing one of your custom entities and you have added a toolbar button in isv.config.xml. When clicked it should open the entity shown in the lookup field, which basically is the same as clicking the link in the lookup itself.

Here's the code:

var lookup = crmForm.all.your_lookup_field;

if ((lookup != null) && (lookup.DataValue != null)) {
    var objectTypeCode = lookup[0].type;
    var objectId = lookup[0].id;
    var url = '/userdefined/edit.aspx?id=" + objectId + '&etc=' + objectTypeCode;

    window.open(url);
}

Note that when using a system entity, you have to replace /userdefined/edit.aspx with the appropriate edit URL of the system entity. The code first checks for the availability of the lookup field. If it's not included on the form (lookup will be null) or no data value has been set then no action is performed; otherwise the complete edit URL is stored in the url variable and passed to the window.open method.

Calculating the sum of two or more fields

Though it seems straightforward to sum up field values in a form, you can easily run into problems with null values. Here's a sample script that sums up three fields (your_field1, your_field2, your_field3) and stores the sum in your_sum:

//A field is accessed with crmForm.all.<the_field_name>
//A field value is accessed through it's DataValue property
var value1 = crmForm.all.your_field1.DataValue;
var value2 = crmForm.all.your_field2.DataValue;
var value3 = crmForm.all.your_field3.DataValue;

//The DataValue of an empty field is null, so in order to
//sum up the values, you have to check for null values
value1 = (value1 == null) ? 0 : value1;
value2 = (value2 == null) ? 0 : value2;
value3 = (value3 == null) ? 0 : value3;

//Setting a value follows the same rules used for retrieving
crmForm.all.your_sum.DataValue = value1 + value2 + value3;

Calculating the total charge based on actural duration, hourly rate, trip charge and tax rate

Instead of trying to make this one generic, I'm repeating the original question:

"I created 4 attributes under the Case entity as follows:

  • new_hourlyrate - picklist (95.00 and 125.00 for values)

  • new_taxrate - picklist (.06 and .07 for values)

  • new_tripcharge - picklist (0.00, 15.00, 30.00, 60.00 for values)

  • new_totalcharge - money

I need to populate the totalcharge field based on the actualdurationminutes attribute with the following math equation:
totalcharge = actualdurationminutes/60 (to get hours) multiplied by the hourly rate, add the tripcharge, multiplied by the tax rate. Take that value and add it to the hourly rate multiplied by the hours, and add the trip charge, to get total charge."

And here's the code:

var hourlyRateField = crmForm.all.new_hourlyrate;
var taxRateField = crmForm.all.new_taxrate;
var tripChargeField = crmForm.all.new_tripcharge;
var totalChargeField = crmForm.all.new_totalcharge;
var actualDurationMinutesField = crmForm.all.actualdurationminutes;

//Sanity check: if at least one of the fields is not available on the form,
the following condition is not met

if (hourlyRateField && taxRateField && tripChargeField && totalChargeField && actualDurationMinutesField) {

    var hourlyRate = (hourlyRateField.DataValue == null) ? 0 : parseFloat(hourlyRateField.SelectedText);
    var taxRate = (taxRateField.DataValue == null) ? 0 : parseFloat(taxRateField.SelectedText);
    var tripCharge = (tripChargeField.DataValue == null) ? 0 : parseFloat(tripChargeField.SelectedText);
    var actualDurationMinutes = (actualDurationMinutesField.DataValue == null) ? 0 : parseFloat(actualDurationMinutesField.DataValue);

    var totalCharge = (actualDurationMinutes/60 * hourlyRate) + tripCharge;
    var totalTax = totalCharge * taxRate;

    totalChargeField.DataValue = totalCharge + totalTax;
}

Note that in the above code parseFloat uses the SelectedText of the picklists instead of the DataValue.

Changing error messages in CRM forms

Sometimes the error messages displayed when entering an incorrect value may not be correct. An example from a Dutch system: when a user tries to input a date by hand, for example 14/05/1998 an error is displayed because the correct format is 14-05-1998. However the error message tells you to specify the date as D/M/YYYY, which isn't correct.

You can change these error messages on the fly by simply replacing the appropriate variable in OnLoad. Note the error message and search for it in in the page source. You will find something like this:

var LOCID_ALERT_ENTER_VALID_DATE = "De opgegeven datum is ongeldig. voer een datum in met de notatie: D/M/YYYY.";

To change it, put the following line in your OnLoad event:

LOCID_ALERT_ENTER_VALID_DATE = "De opgegeven datum is ongeldig. voer een datum in met de notatie: DD-MM-YYYY.";

Setting a custom date field to another date minus 60 days

Date calculation problems are still popping up in the newsgroups, so here's another quick example. Let's say you want to calculate a date based on the value of the effectiveto field in your crmForm minus 60 days. Here's the code:

var effectiveTo = crmForm.all.effectiveto.DataValue;

var remindOn = new Date(
    effectiveTo.getYear(),
    effectiveTo.getMonth(),
    effectiveTo.getDate() - 60);

or

var effectiveTo = crmForm.all.effectiveto.DataValue;

var remindOn = new Date(
    effectiveTo.getYear(),
    effectiveTo.getMonth() - 2,
    effectiveTo.getDate());

The difference between the codes is that the first subtracts exactly 60 days, whereas the second subtracts two months, which is between 58 and 62 days.

Changing the default height of the lookup window

This is an unsupported change, but if you want to change the initial size of a lookup window, open /_controls/lookup/lookup.js in the CRM web and search for the function BuildFeatures(lookupStyle). Inside of this function search for the following:

switch (lookupStyle)
{
case "multi":
oFeatures.height = "460px";
oFeatures.width = "600px";
break;
case "single":
oFeatures.height = "488px";
oFeatures.width = "600px";
break;

oFeatures.height and oFeatures.width are the initial lookup dimensions. After changing them, clear your browser cache to reload the include files the next time a lookup is accessed, otherwise IE will still use the cached values and you don't see a difference.

Accessing the previous field value in OnChange

Sometimes you need to know the previous field value in an OnChange event or you need to know the initial value after the form has loaded. This information of course is lost in OnChange, as the field value already has changed. Here's a simple workaround:

// OnLoad event
// no var statement here to declare a global variable

_oldDateValue = crmForm.all.the_fieldName.DataValue;

// OnChange event
if (_oldDateValue == null) {
    //no previous value
}

else {
    var currentValue = crmForm.all.the_fieldName.DataValue;
    DoStuff(currentValue, _oldValue);

    //update the old value with the new value, if appropriate
    _oldDateValue = currentValue;
}

Automatically calculate the tax value in an invoice line (invoicedetail)

CRM does not calculate the tax amount for you and if you want to automate it you have to add custom script. Sp here's an example for the invoicedetail. In the OnLoad event of the invoicedetail form add the following:

CalculateTax = function() {

    var pricePerUnit = 0;
    var quantity = 0;
    var manualDiscount = 0;

    if (crmForm.all.priceperunit.DataValue != null) {
        pricePerUnit = crmForm.all.priceperunit.DataValue;
    }

    if (crmForm.all.quantity.DataValue != null) {
        quantity = crmForm.all.quantity.DataValue;
    }

    if (crmForm.all.manualdiscountamount.DataValue != null) {
        manualDiscount = crmForm.all.manualdiscountamount.DataValue;
    }

    crmForm.all.tax.DataValue = (pricePerUnit * quantity - manualDiscount) * 0.175;
}

In the OnChange events of priceperunit, quantity and manualdiscountamount add:

CalculateTax();

Change 0.175 (17.5%) to the tax you need to charge. You can also add a new field instead of using a fixed value in the script code.

Performing an action when a CRM form closes

If you want to execute your script whenever the form closes, whether it is saved or not, you can subscribe to the onunload event:

window.onunload = function() {
    //add code here
}

Hooking into the "Lookup Address" feature in the order form

To get notified when a user presses the "Lookup Address" button in the order form, put this code into the order's OnLoad event:

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

The alert pops up after the address lookup dialog closes but there's no way to distinguish if the user selected an address or canceled the dialog. Of course this is an unsupported customization as it uses undocumented functions.

Changing the form title (not the browser title)

A CRM form displays the primary field of an entity in a large bold font just below the toolbar buttons. If you want to change the displayed text, put the following code into your OnLoad event:

var cells = document.getElementsByTagName("td");

for (var i = 0; i < cells.length; i++) {
    if (cells[i].className == "formTitle") {
        cells[i].innerText = "Ticket: 123456";
        break;
    }
}

Passing parameters from a toolbar button to a CRM form

Sometimes you add buttons to a form's toolbar that simply create a new entity. However in the created entity you need to know if it was created from your toolbar button or not. Here's an easy solution to pass an additional parameter that you can check in the OnLoad entity of the new entity form.

First of all let's start with the toolbar button. It usually has a Url attribute like "/userdefined/edit.aspx?etc=10018". To differentiate add an additional parameter like "/userdefined/edit.aspx?etc=10018&template=1".

Note: using the entity type code is dangerous, as it may break when deploying your solution to another server.

In the OnLoad event of the target entity (with object type code 10018) use the following code to extract the template parameter from the query string:

var QueryString = ParseQueryString();
var template = QueryString["template"];

alert(template);

if (template == "1") {
    // "New Template" clicked
}

else {
    // "New" clicked
}

function ParseQueryString() {

    var dict = new Object();

    if ((document.location.search != null) && (document.location.search != "?")) {
        var qsParts = document.location.search.substr(1).split("&");
        var index;

        for(index in qsParts) {
            var keyValue = qsParts[index].split("=");
            dict[keyValue[0]] = unescape(decodeURIComponent(keyValue[1]));
        }
    }

    return dict;
}

The basic idea is passing additional parameters to the form and reading them in OnLoad. As the default "New" button does not add the template parameter, you can use it as an indicator which button initiated the creation of the new entity.

Aborting an OnChange operation

Again I'm posting the original question to better understand the solution: "I populated  a picklist with some values and add code to the OnChange() event. When users change the picklist value by selection in the dropdown, a message box will pop up and ask the user to confirm the change. If the user chooses No, how could I restore the original value selected in the picklist?"

// OnLoad
// Note that the "var" keyword is missing intentionally to declare prevPicklistValue as a global variable
prevPicklistValue = crmForm.all.the_picklist.DataValue;

// OnChange of prevPicklistValue

var currentPicklistValue = crmForm.all.the_picklist.DataValue;

if (prevPicklistValue == currentPicklistValue) {
 //Reaching this line when restoring the previous value
 return;
}

var answer = window.confirm("Click Ok to proceed or Cancel to abort the operation.");

if (answer) {
 //User selected OK -> Save the current value as the last accepted value
 prevPicklistValue = currentPicklistValue;
}

else {
    //User selected Cancel -> Restore the previous value.
    crmForm.all.the_picklist.DataValue = prevPicklistValue
}

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

STUNNWARE

It's been a while since I posted these small JavaScript snippets (More JavaScript Code, More JavaScript Code Part 2), so after half a year I'm adding the third part today, containing 23 new samples. Hope you find it as useful as the first two articles.

Setting the background color of a CRM form

If you want to make it easier for a user to note what type of entity a CRM form is displaying, you can change the background color with only one line of code:

document.body.style.backgroundColor = 'red';

Put it into the OnLoad event of the form you want to change and replace 'red' with the color of your choice.

Why can't we use VBScript to write client-side code?

As CRM is a web application, client-side code deals with DHTML objects and the supported languages in IE are JavaScript and VBScript. JavaScript is the standard used in the internet, while VBScript is IE only, so it will never work with any browser other than IE.

One could argue that CRM is tied to IE6 and above, but allowing and supporting VBScript would make it impossible for the product team to even think about supporting other browsers. I never saw an official statement as to why VBScript is not supported though.

Some calculated fields on the CRM form are not saved in the database

If the field is disabled, which is a common practice for calculated fields, it is not sent to the server when the form is saved. To override this behavior, add this line to your code:

crmForm.all.your_field.ForceSubmit = true;

Changing detailed tooltips when hovering over fields in a CRM form

This article explains in depth how to do it: http://blogs.msdn.com/crm/archive/2006/11/17/using-the-attachevent-method-to-show-users-context-sensitive-help.aspx

Fixing problems with orphaned OnChange event handlers

When changing entire field definitions in client-side script (e.g. Overcoming relationship restrictions in Microsoft CRM v3.0 or Converting the country field to a combobox) the OnChange event code may not be triggered.

To overcome this situation, place the following in your script after you have replaced the HTML code:

crmForm.all.your_field.onchange = function() {
    //add code here
}

If you need to initially call the OnChange code, use this instead:

your_field_OnChange = function() {
    //add code here
}

//attaching the event
crmForm.all.your_field.onchange = your_field_OnChange;

//calling the event (same idea as FireOnChange())
your_field_OnChange();

Setting the field required level at runtime

You can access the current required level of any field by using the RequiredLevel property:

var reqLevel = crmForm.all.your_Field.RequiredLevel.

However, as it is a read-only property, you cannot use it to change the required level. There is an undocumented (and therefore unsupported) method on the crmForm object allowing to make a field required or not required:

function SetFieldReqLevel(sField, bRequired)

If bRequired is set to 0 (false), it is not required. If bRequired is set to anything else (true), the field is required.

Note: you cannot use this method to make a field business recommended. Here's the code to set a field to any of the possible states:

//No Requirement
//------------------------------
crmForm.all.your_field.setAttribute("req", 0);
crmForm.all.your_field_c.className = "n";

//Recommended
//------------------------------
crmForm.all.your_field.setAttribute("req", 1);
crmForm.all.your_field_c.className = "rec";

//Required
//------------------------------
crmForm.all.your_field.setAttribute("req", 2);
crmForm.all.your_field_c.className = "req";

Replace "your_field" and "your_field_c" with the name of the field you want to set, e.g. to set the accountnumber field to business recommended, you specify

crmForm.all.accountnumber.setAttribute("req", 1);
crmForm.all.accountnumber_c.className = "rec";

Setting a default time in a date field

A date field in a CRM form always contains a date part and optionally a time part. Sometimes it is useful to set a default time, like 8:30am, but unless the user has entered a valid date, the time selection box is disabled. Here's the code to set the default time once the user has specified the date part:

OnLoad
----------------------------------------

//check if the field exists on the form
if (crmForm.all.your_DateField != null) {
    //save the value for future reference. Note that it is a global variable.
    _previousValue = crmForm.all.your_DateField.DataValue;
}

OnChange of your date field
----------------------------------------

var dateField = crmForm.all.your_DateField;
var currentValue = dateField.DataValue;

//If the user changes the date field from null to a valid date, set the
//time portion to 8:30
if ((currentValue != null) && (_previousValue == null)) {
    dateField.DataValue = new Date(currentValue.getYear(), currentValue.getMonth(), currentValue.getDate(), 8, 30);
}

//update _previousValue
_previousValue = currentValue;

I tried it with the scheduledend field of a task and it worked. The time combo of the scheduledend field is disabled as long as you put a date into the date field. This triggers the OnChange event and sets the default time.

User interaction with yes/no style message boxes

You can use window.confirm to present the user a yes/no-style dialog. The buttons are actually named "Ok" and "Cancel", so you have to use a good explanation in the message text. The result is either true (Ok) or false (Cancel):

var answer = window.confirm("Click Ok to proceed or Cancel to abort the operation.");

if (answer) {
    //User selected OK
}

else {
    //User selected Cancel
}

Testing if a lookup field has a value

It's the same as as for any other field type:

if (crmForm.all.your_field.DataValue == null) {
    //code here
}

Comparing date values

It is a common mistake to directly compare two date values like this:

var date1 = new Date(2007, 4, 30);
var date2 = new Date(2007, 5, 1);

if (date1 > date2) {
    //some code here
}

You may expect it to work, but you have to use the valueOf method of the Date object:

var date1 = new Date(2007, 4, 30);
var date2 = new Date(2007, 5, 1);

if (date1.valueOf() > date2.valueOf()) {
    //some code here
}

Getting notified when the user enters a form field

The OnChange event of a form field is fired when you are leaving a field (and of course have changed the field value). If you want to perform an action when the users enters the field, use the onfocus event:

crmForm.all.your_field.onfocusin = function() {
    alert("Received focus");
}

Overriding the click event of a lookup field

If you need to run code whenever a user clicks on a lookup button, use the following code to override the standard implementation of the click event:

//overrides the default click handler
crmForm.all.your_lookupField.onclick = function() {

    alert("Lookup dialog is opening now");

    //open the lookup dialog
   
crmForm.all.your_lookupField.Lookup(true);

    alert("Lookup dialog closed");
}

Be careful with this as some lookup fields specify additional settings in their click events.

Formatting a date to YYYYMMDD

There is no toString() implementation in JavaScript allowing you to format a data value, so you have to build it on your own:

getYear returns the current year
getMonth returns the month starting from 0 for January to 11 for December
getDate returns the day of the month (1-31)

A common error is to use getDay instead of getDate. getDate returns the day of the week.

var now = new Date();

var year = now.getYear().toString();
var month = (now.getMonth() + 1).toString();
var dayOfMonth = now.getDate().toString();

if (month.length == 1) {
    month = "0" + month;
}

if (dayOfMonth.length == 1) {
    dayOfMonth = "0" + dayOfMonth;
}

var yyyymmdd = year + month + dayOfMonth;

The DataValue of a picklist is a string!

The DataValue of a picklist is a string, not an integer. You would expect it to be an integer as a picklist value is stored as an integer in the database and the Picklist class in the WSDL also specifies an integer value. Anyway when comparing the value of a picklist in client-side code, make sure to use a string value:

if (crmForm.all.your_picklist.DataValue == "1") {
}

Setting a picklist's default value in code

If a default option is specified in the attribute definition of a picklist, CRM assumes that you don't want to allow an empty value. This may not be true in all circumstances, so here's the workaround: In the attribute definition change the default value back to unassigned. This adds back the empty option in the picklist. Open the form's OnLoad event and add the following code:

if (crmForm.ObjectId == null) {
    crmForm.all.your_picklist.DataValue = "1";
}

The code selects the first option in the picklist when inside a create form. It does not change the value once the form has been saved.

Starting an application from a CRM form

var shell = new ActiveXObject("WScript.Shell");

if (shell != null) {
    shell.Run("c:\\directory\\application.exe " + crmForm.ObjectId);
}

You may face security issues preventing your code from being executed. An alternative is to use a custom .NET assembly as outlined in Using .NET assemblies in JavaScript code.

Changing the available entity types in a lookup dialog

Use one of the following in your OnLoad event:

//Allow only accounts to be selected
crmForm.all.regardingobjectid.setAttribute("lookuptypes", "1");

//Allow only contacts to be selected
crmForm.all.regardingobjectid.setAttribute("lookuptypes", "2");

//Allow accounts or contacts to be selected
crmForm.all.regardingobjectid.setAttribute("lookuptypes", "1,2");

It does not change the behavior of the form assistant, but the lookup dialog will not display any other entity. The attribute values are entity type codes and are documented in the SDK help file. You can use the code for any field allowing multiple entity types (usually customer fields and the regarding field).

Setting the text of the "Save As Completed" button

To display the text "Save as completed" in the toolbar button performing this action, put this code into the OnLoad event:

document.all._MBSaveAsCompleted.children[0].innerHTML += "Save as completed";

This is unsupported and may not work in the future.

Calculating durations

The following code subtracts the current date from a date specified on a CRM form and calculates the remaining or elapsed days, hours or minutes.

var displayField = crmForm.all.<name of a string field>;
var formDate = crmForm.all.<name of a datetime field>.DataValue;
var now = new Date();
var ms = formDate.valueOf() - now.valueOf();
var minutes = ms / 1000 / 60;
var hours = minutes / 60;
var days = hours / 24;

if (days >= 1) {
    displayField.DataValue = Math.floor(days) + " day(s) left";
}

else if (hours >= 1) {
    displayField.DataValue = Math.floor(hours) + " hour(s) left";
}

else if (minutes >= 1) {
    displayField.DataValue = Math.floor(minutes) + " minute(s) left";
}

else if (days <= -1) {
    displayField.DataValue = Math.floor(-days) + " day(s) late";
}

else if (hours <= -1) {
    displayField.DataValue = Math.floor(-hours) + " hour(s) late";
}

else if (minutes <= -1) {
    displayField.DataValue = Math.floor(-minutes) + " minute(s) late";
}

else {
    displayField.DataValue = "NOW";
}

Hiding a single field

crmForm.all.<fieldname>_c.style.display = "none"; //hides the label
crmForm.all.<fieldname>_d.style.display = "none"; //hides the field

Setting a text field to the name of the entity referenced in a lookup control

if (crmForm.all.<lookupField>.DataValue == null) {
    crmForm.all.<textField>.DataValue = null;
}

else {
    crmForm.all.<textField>.DataValue = crmForm.all.<lookupField>.DataValue[0].name;
}

Getting notified when a user changes a checkbox value before leaving the field

The OnChange event of a bit field is fired when you leave the field. Often you want the event to be triggered as soon as the user clicks on a checkbox before tabbing out. Here's the code:

In the OnLoad event create a new event handler like this:

crmForm.all.your_checkboxfield.onclick = function() {
    crmForm.all.your_checkboxfield.FireOnChange();
}

Obviously the onclick event is raised when clicking on the checkbox. Not so obvious is the fact that it also fires when you change the value using the keyboard (space bar). The above code calls your OnChange event handler after the value changed but before the control looses focus, so OnChange will be triggered again when you tab out. If that's a problem, place the existing OnChange event handler into the onclick event:

crmForm.all.your_checkboxfield.onclick = function() {
    //add existing OnChange implementation here
}

Now you can deactivate your OnChange event.

Preventing an OnChange event handler from executing when the form closes

To prevent an OnChange event handler from running if the form is closing, add the following code to your OnLoad event:

//declaring a global variable
_windowClosing = false;

//onbeforeunload is called when you close the form but before the OnChange event is triggered
window.onbeforeunload = function() {
    _windowClosing = true;
}

In your OnChange script, do the following:

if (!_windowClosing) {
    //add your existing script code
}