CRM JavaScript Programming Best Practices in different versions

Microsoft has a very good page on Javascript programming in CRM – http://msdn.microsoft.com/en-us/library/hh771584.aspx

I was reading this today and I noticed it had a Javascript Programming Best Practices section.  Javascript is very useful but I often feel the code produced in Javascript is not alway the best quality because there is no type checking and the process of writing the code and uploading is a bit of a hassle.

 

I have selected a couple of the best Practices but to read the full list go to the article

Microsoft have also created best practices for different versions, links to below to the different pages

CRM 2011 – Javascript best practices

CRM 2013 – Javascript best practices

CRM 2015 – Javascript best practices

 

There are a lot of best practices and it’s a really good article, but below are my top 4 best practices from the article.  I would definitely recommend you read the different articles.

 

Use a Cross-browser JavaScript Library for HTML Web Resource User Interfaces

A cross-browser JavaScript library, such as jQuery, provides many advantages when developing HTML web resources that must support multiple browsers. JavaScript libraries like jQuery provide a unified development experience for all browsers supported by Microsoft Dynamics CRM. These capabilities are most appropriate when you are using HTML web resources to provide user interfaces. JavaScript libraries like jQuery provide consistent ways to interact with the Document Object Model (DOM).

Use Feature Detection When Writing Functions for Multiple Browsers

Even when you use a cross-browser library like jQuery, you need to be very aware of differences between browsers. You can generally detect which browser is being used by querying thenavigator.useragent property. This is called browser detection. Browser detection is not a good strategy for most cases because it can’t take into account what features newer versions of a browser have. Also, some browsers provide the capability to modify the navigation.useragent property so that they appear to be a different browser.

Feature detection is the recommended approach. By detecting what features are available, you can create code paths for the browsers you support without knowing exactly which browser is being used. For more information about feature detection, see How to Detect Features Instead of Browsers.

Define Unique Names for Your JavaScript Functions

When you are the only developer for an HTML page you can easily manage the names of the JavaScript functions you use. In Microsoft Dynamics CRM, other solutions may add JavaScript functions to the page where your function is used.

If two JavaScript functions on a page have the same name, the first function defined is overwritten by the second. For this reason, make sure that you define unique names for your JavaScript functions. For more information, see Creating Script Libraries.

Use Asynchronous Data Access Methods

When you access data by using the Microsoft Dynamics CRM web services that use the REST or SOAP endpoint for web resources, always use an XMLHttpRequest that is configured to execute asynchronously. The reason is that the browser operates on a single thread. If that thread is being used to execute a long-running process synchronously the browser will stop responding.

CRM 2011 – IFrame opening new page problem

I had a tricky problem, where I wanted to show a grid of records on an entity which didn’t have a direct relationship with those records.

Because there wasn’t a direct relationship I couldn’t show a grid of filtered records.

The way I got around this was to show a view inside an iFrame.  On the onload of the page I would adjust the view to use guid of the regarding item on my task.

This showed a view of items filtered by the guid of the record in the regarding.  The problem then occurred when I clicked on one of the items in grid.  Instead of taking me to the item, it loaded a new page which contained just the view and then I could click the item.

I couldn’t work out why this wasn’t working so I had a look at the properties of the iFrame and there is an option

Restrict cross-frame scripting

I noticed this was ticked, so I unticked and then it was working.

The reason I think it wasn’t working was because I was trying to click on a hyperlink which would take me to the record.  I guess this hyperlink was seen as cross frame scripting, so by allowing this it allowed the hyperlink to work.

CRM 2011 – Be careful removing fields from a form

I had written some javascript which retrieved values from a field which I was displaying on the form.

I had added an account field onto a quote product, so I could then use the account id to inject into some fetchXML in a subgrid so I only showed quote products which had only been sold to the the account selected on the quote.

I could have looked this up using a query but I thought I would add it onto the Quote Product form and pass the value using relationships.

It worked fine but as I came to release the code I thought I didn’t really need the account lookup field on the form so I took it off.

Then when testing my code it wasn’t working, so I pressed F12 and was ready to debug, except it wasn’t running my code because it was erroring whilst trying to get the account lookup field, with this error.

SCRIPT5007: Unable to get value of the property ‘getValue’: object is null or undefined

So the morale of the story is, you can only access fields in Javascript which are on the form.  The solution to this problem was to make the fields not visible.

This is logical when I stop to think about it because why would CRM load up all the variables which are not being used on the form, it would be a complete waste of time.

This was an early morning, not fully awake error but it does show you the danger of taking off variables from a form without seeing if any javascript uses those fields for something

CRM 2011 – Javascript to set the current date and time

A quick blog entry and a suprisingly easy and small piece of code

In the code below I have function which gets triggered when a user ticks the confirm Closed checkbox it then sets the current date and time.

if the user untick the checkbox I wipe the date and time value.

The date functionality in javascript holds the current time as well

 function confirmClosedOnChange() {
 var closedConfirmed = Xrm.Page.getAttribute("new_closedconfirmed").getValue()

if (closedConfirmed) {
var currentDateTime = new Date();
Xrm.Page.getAttribute("new_confirmedcloseddate").setValue(currentDateTime);
} else {

Xrm.Page.getAttribute("new_confirmedcloseddate").setValue(null);
}
 }
 

CRM 2011 – Displaying images for a contact

A quick blog post to point you in the direction of a couple of exellent tools to display images in for a contact (or other entities).

The timing of this was a day too late (for me) because I had written a basic way to display images yesterday and then today I find the solution today and it works better than mine because you can have multiple images and it resizes the images in a neat way, working out the aspect ratio from the available space.

Both solutions use the method of uploading picture files as a note for the contact and then display these.

For the html, jquery method then go to this blog and you can download the solution file

http://mscrmblogger.com/2012/03/31/imagebrowser-webresource/

You will need to add the image img_browser.html as a webresource onto your page.  This is very useful because I think (I haven’t tried it) use this to display images on other entities.

The MSCRM blogger team have also created a silverlight version, well actually they created that first, you can get that here

http://crmattachmentimage.codeplex.com/

A great contribution to the CRM community and will save everyone writing the same solution over and over.

CRM 2011 – displaying similar quote products in a subgrid by injecting FetchXML

I had a requirement this week where a customer was adding products to a quote and when they selected a product they then want to see previous quotes which featured the same product and the price and quantity it sold for.  Basically the sales person wanted to see what the product had been sold for before to make sure the price they were quoting wasn’t too radical.

The idea I came up with was to have a grid on the Quote Product form, which displayed quote products of the same type as the quote product selected.

This would mean I would have to dynamically change a view or show items in the grid.

I created a view showing the fields of the Quote Product and then specified a product I was searching for.

I found this excellent blog post

CRM 2011 – Change Subgrid View Java Script

This blog post explains how you can load in new fetchXML into a subgrid, you can inject the FetchXML on the form onload, which enables you to get some variables from the form and inject them into FetchXML which then is used to update the SubGrid.

So on my Quote Product form I checked to see if a product had been selected on the form OnLoad, on this particular form the subgrid doesn’t load until you have selected a product, unit and a quantity, which then reloads the form.

The code itself which I have taken and changed from the blog post referenced above, is quite clever.  The function is called and if the SubGrid has not been loaded, it calls the function again after a small time delay.  This ensures the SubGrid is loaded before you try and inject the fetchXML into the view.

The FetchXML is created, I got the fetchXML by doing an advanced find on my previously created view, there is a button which allows you to retrieve the FetchXM, which is very useful.

function UpdateSubGrid() {
var quoteGrid = document.getElementById("Quote_Products");
var productId = Xrm.Page.getAttribute("productid").getValue();
if (productId != null){
//If this method is called from the form OnLoad, make sure that the grid is loaded before proceeding
 if (quoteGrid == null) {
 //The subgrid hasn't loaded, wait 1 second and then try again
 setTimeout('UpdateSubGrid()', 1000);
 return;
 }

var fetchXml = ""
+ " <entity name='quotedetail'>"
+ " <attribute name='productid' />"
+ " <attribute name='productdescription' />"
+ " <attribute name='priceperunit' />"
+ " <attribute name='quantity' />"
+ " <attribute name='extendedamount' />"
+ " <attribute name='salesrepid' />"
+ " <attribute name='uomid' />"
+ " <attribute name='manualdiscountamount' />"
+ " <attribute name='baseamount' />"
+ " <attribute name='new_quoteid' />"
+ " <attribute name='quotedetailid' />"
+ " <order attribute='productid' descending='false' />"
+ " <filter type='and'>"
+ " <condition attribute='productid' operator='eq' uiname='1 Function Easy Clean Mini Kit' "
+ " uitype='product' value='" + productId[0].id +"' />"
+ " </filter>"
+ " <link-entity name='quote' from='quoteid' to='quoteid' visible='true' link-type='outer' alias='a_ff5a49bd001a45f7a14dd99a28f37f91'>"
+ " <attribute name='customerid' />"
+ " <attribute name='name' />"
+ " <attribute name='quotenumber' />"
+ " <attribute name='quoteid' />"
+ " </link-entity>"
+ " <link-entity name='product' from='productid' to='productid' visible='false' link-type='outer' alias='a_e0b689bded554f07ab0b241a932d482e'>"
+ " <attribute name='productnumber' />"
+ " </link-entity>"
+ " </entity>"
+ "</fetch>";
 // alert(fetchXml);
 //Inject the new fetchXml
 quoteGrid.control.setParameter("fetchXml", fetchXml);
 //Force the subgrid to refresh
 quoteGrid.control.refresh();
}

CRM 2011 – Setting a user lookup with the logged in user with Javascript

Sometimes you need to log the current user in a User Lookup.

Xrm.Page.context.getUserId()

if you wanted to set a user lookup field you would need to do this


var setUservalue = new Array();
 setUservalue[0] = new Object();
 setUservalue[0].id = Xrm.Page.context.getUserId();
 setUservalue[0].entityType = 'systemuser';
//setUservalue[0].name = retrievedUser.FullName;

Xrm.Page.getAttribute("meta_aurarrangedby").setValue(setUservalue)

meta_aurarrangedby is a user lookup field

This will set the correct guid but the user name will be blank until the page is reloaded if you want to add the user name then you need to retrieve the user name using the guid the example code below will do this using an OData query but you will have to add the json2.js file to the entity so it can be called from your javascript.

function Getinfo() {
 var context;
 var serverUrl;
 var UserID;
 var ODataPath;
 context = Xrm.Page.context;
 serverUrl = context.getServerUrl();
 UserID = context.getUserId();
 ODataPath = serverUrl + "/XRMServices/2011/OrganizationData.svc";
 var retrieveUserReq = new XMLHttpRequest();
 retrieveUserReq.open("GET", ODataPath + "/SystemUserSet(guid'" + UserID + "')", true);
 retrieveUserReq.setRequestHeader("Accept", "application/json");
 retrieveUserReq.setRequestHeader("Content-Type", "application/json; charset=utf-8");
 retrieveUserReq.onreadystatechange = function () {
 retrieveUserReqCallBack(this);
 };
 retrieveUserReq.send();

}

function retrieveUserReqCallBack(retrieveUserReq) {
if (retrieveUserReq.readyState == 4 /* complete */) {

if (retrieveUserReq.status == 200) {
 var retrievedUser = this.parent.JSON.parse(retrieveUserReq.responseText).d;
 if (retrievedUser.FullName != null)

var setUservalue = new Array();
 setUservalue[0] = new Object();
 setUservalue[0].id = Xrm.Page.context.getUserId();
 setUservalue[0].entityType = 'systemuser';
 setUservalue[0].name = retrievedUser.FullName;

Xrm.Page.getAttribute("meta_aurarrangedby").setValue(setUservalue)
 }

else {

 }
 }
}

The Xrm.Page.context has quite a few useful functions the functions are listed on this page

The getServerUrl is very useful as is the getUserRoles

Xrm.Page.context provides access to the following functions:

  • getAuthenticationHeader: A deprecated method that returns the encoded SOAP header necessary to use Microsoft Dynamics CRM Web service calls using JScript.
  • getCurrentTheme Returns a string representing the current Microsoft Office Outlook theme chosen by the user.
  • getOrgLcid: Returns the LCID value that represents the Microsoft Dynamics CRM Language Pack that is the base language for the organization.
  • getOrgUniqueName: Returns the unique text value of the organizations name.
  • getQueryStringParameters: Returns an array of key value pairs representing the query string arguments that were passed to the page.
  • getServerUrl: Returns the base server URL. When a user is working offline with Microsoft Dynamics CRM for Microsoft Office Outlook, the URL is to the local Microsoft Dynamics CRM Web services.
  • getUserId: Returns the GUID value of the SystemUser.id value for the current user.
  • getUserLcid: Returns the LCID value that represents the Microsoft Dynamics CRM Language Pack that is the user selected as their preferred language.
  • getUserRoles: Returns an array of strings representing the GUID values of each of the security roles that the user is associated with.
  • isOutlookClient: Returns a Boolean value indicating if the user is using Microsoft Dynamics CRM for Microsoft Office Outlook.
  • isOutlookOnline: Returns a Boolean value indicating whether the user is connected to the Microsoft Dynamics CRM server while using Microsoft Dynamics CRM for Microsoft Office Outlook with Offline Access. When this function returns false, the user is working offline without a connection to the server. They are interacting with an instance of Microsoft Dynamics CRM running on their local computer.
  • prependOrgName: Prepends the organization name to the specified path.

CRM 2011 – defaulting the Customer lookup to contacts on the case form

There are a few lookups in CRM call Customer lookups and can be either Accounts or Contacts.

I have to say I often find these annoying.  Often on the Case form I have to add an account lookup so the user can specify an account and a contact.  The problem is when I use the Customer lookup it defaults to account lookup.

The Customer record is locked onto the form so you can’t get rid of it (you can hide it and assign it a value if you wanted but that’s a bit messy and extra fields etc.)

So on this form I added an account lookup and then used the customer lookup to specify a contact and I wanted a way to default the lookup to be a contact lookup.

I found some code shown below on the blog MSCRM Bing’d which is written by CRM MVP Rhett Clinton.

document.getElementById("customerid").setAttribute("defaulttype", "2");
<pre>Xrm.Page.getControl("customerid").setDefaultView("a2d479c5-53e3-4c69-addd-802327e67a0d");

To get this solution to work I had to go and get the guid of the contact view and in this case I wanted the Active Contacts and replaced the SetDefaultView guid with my guid and then I put the code into the onload event.

Then when I clicked on the customer lookup it defaults to contacts and the view I specified in the guid.

The only downside is if you search in the box without pressing the lookup button it does still search contacts and accounts.

So a big thanks to Rhett for helping resolve a problem which had annoyed me for quite a while.


CRM 2011 – adding validation to the Resolve Case button

I had a scenario where the customer wanted the helpdesk user to fill in some call analysis fields when a case was resolved.

Initially I made a new status called Completed, I created some new fields for the users to fill in regarding customer satisfaction, if they resolved the case within the time period and some other fields.

When the user changed the status to completed, some JavaScript made those fields required so the user had to fill them.

This worked fine unless the user press the Resolve Case button and this just closed the case without them having to fill in call analysis fields.

I initially looked into changing the case resolution form.  The case resolution form pops up when you press the case resolved button and you have to fill in a few fields, the perfect place for some call analysis.  Perfect except you can’t modify it, Microsoft don’t let you modify this Activity.  I have no idea why but there you go.

I briefly thought about adding some rules to enable the case resolution button only when you clicked on the status completed.

I then saw some Javascript which did the job.  It’s quite clever and I learnt some thing new in CRM.

The solution to my problem was some javascript which is triggered on the OnSave event on the Case form, it checks what type of save has happened and if it was triggered from pressing the Case Resolution it validated the form.

The answer although was shown on many forums originated from the blog post below.  The blog post has a link to the javascript file you can download so I would definitely go there to get it.  Also this is a great site for CRM developers with lots of other great blog posts like how to trigger a workflow using javascript

The original Javascript and example is taken from this blog post but has now been created as a wiki page blog post below, you need to look at the original article because it explains the whole solution ands walks you through it which I am not going to do.

http://social.technet.microsoft.com/wiki/contents/articles/4122.dynamics-crm-2011-dynamics-crm-2011-perform-jscript-validations-on-entity-form-before-execution-of-special-events.aspx

// Use the following function on Form Save Event,
// CRM will pass the execution context in function parameter prmContext

function FrmOnSave(prmContext) {
 // Local variable to store value indicating how the save event was initiated by the user.
 var wod_SaveMode, wod_SaveEventVal;

// Change the Save Event Value as per required Save Event
 wod_SaveEventVal = <Value>;

if (prmContext != null && prmContext.getEventArgs() != null) {

wod_SaveMode = prmContext.getEventArgs().getSaveMode();

// 1 will pass on Recalculate button click
 if (wod_SaveMode == wod_SaveEventVal) {
 // Write your validation code here

alert("Write your validation code here");

// Use the code line below only if validation is failed then abort function save event
 prmContext.getEventArgs().preventDefault();

}
 }
}

You can download the code from the blog link to a skydrive, click here for that

The code above is a neat solution to the problem.  In the OnSave event it gets the code to pass variables, this then passes the context.  Using the context you can then get the save mode prmContext.getEventArgs().getSaveMode()  this gives you a number.  If the number is 5 then you know the save has been triggered by pressing the resolve case button.

This was interesting because I didn’t know that save events had different numbers depending on where they were triggered.  The blog post has a list of all the save events

Entity
Event Mode
Value
Activities
Save as Completed
58
Activities
Close Activity Note 2
5
Activities
To Opportunity Note 2
5
Activities
To Case Note 2
5
Activities
To Lead Note 2
5
All
Save Note 2
1
All
Save and Close
2
All
Deactivate
5
All
Reactivate
6
Article
Submit
10
Article
Approve
12
Article
Reject
11
Article
Unpublish
13
Campaign Activity
Close Campaign Activity
5
Campaign Activity
Distribute Campaign Activity
4
Campaign Response
Convert Campaign Response (Create new lead or Create new record for a customer)
54
Campaign Response
Convert Campaign Response (Convert Existing Lead -> Qualify)
16
Campaign Response
Convert Campaign Response (Convert Existing Lead-> Disqualify)
15
Campaign Response
Close Response
5
Case
Resolve Case
5
Case
Cancel Case
40
Case
Reactivate Case
6
Contract
Invoice Contract
38
Contract
Copy Contract
39
Contract
Recalculate Note 2
1
Contract
Hold Contract
31
Contract
Renew Contract
34
Contract
Cancel Contract
33
E-mail
Send
7
Goal
Recalculate
66
Goal
Close Goal
5
Invoice
Invoice Paid
57
Invoice
Cancel Invoice
27
Invoice
Recalculate
1
Invoice
Get Products
44
Invoice
Lock Pricing
52
Lead
Qualify
16
Lead
Disqualify
15
Opportunity
Close as Won Note 2
5
Opportunity
Close as Lost Note 2
5
Opportunity
Recalculate Opportunity Note 2
1
Order
Create Invoice
19
Order
Fullfill Order
56
Order
Cancel Order
26
Order
Recalculate Note 2
1
Order
Get Product
43
Order
Lock Pricing
50
Product
Convert to Kit
35
Product
Convert to Product
36
Queue
Approve E-mail Note 1
4
Queue
Reject E-mail Note 1
4
Quote
Recalculate Note 2
1
Quote
Get Products
28
Quote
Activate Quote
29
Quote
Create Order
17
Quote
Revise
24
Quote
Close Quote
25
User
Approve E-mail Note 1 & Note 2
4
User
Reject E-mail Note 1 & Note 2
4
User
Change Business Unit Note 1
4
User or Team Owned Entities
Assign
47

 

Although I was capturing the save event, when I changed some fields to be required it didn’t seem to stop the page from saving, so I had to add some Javascript which stopped the Case form from saving and popped up a javascript message warning the user to fill in some Call analysis fields.  I added some validation which checks to see if certain fields are null, if they are then I stopped the form from saving.

prmContext.getEventArgs().preventDefault();
alert(“You must enter Call Analysis information”);

 

There are probably other ways to do this but it shows you the power of CRM 2011 that you have quite a few options