The ability to modify the values within Option Set fields on a Dynamics CRM/Dynamics 365 for Enterprise (D365E) form is, depending on your business scenario, one of the best reasons to learn how to write basic JScript form functions. Microsoft makes available 3 methods via the Xrm.Page.ui control (which can be handily shortcutted to Xrm.Page.getControl):
- addOption - Lets you add a new Option Set value, with the ability to optionally specify its position within the index.
- clearOptions - Removes all options from an Option Set field.
- removeOption - Allows you to remove a specific Option Set field value.
With these options at your disposal, you can begin to leverage functionality to accommodate the following business logic:
- Only allow an Option Set to be populated with values (either all or just specific ones) once information has been entered into other fields.
- Dynamically change the options available for selection, based on information entered within the primary entity form, or even a related one.
Now the key takeaway with all of this is that, ultimately, your code cannot do anything that would make the “base” Option Set collection invalid. For example - let’s assume you have a field called Fruit with the Option Set values of Apple, Pear and Apricot. If you then tried to introduce the value Banana on the form level, your code will more than likely error when saving the record.
From a CRM/D365E point of view, there are two additional field types which are also technically classed as Option Sets, thereby allowing you to utilise the above methods with them - the Status and Status Reason fields. If you look closely at the Status Reason field within the Customizations area of the application, you can see why: just like option sets, you specify a label and underlying value for it, that is dictated by your solution publisher:
The only difference worth noting is that, unlike normal Option Set fields, you have no choice when it comes to which underlying value is used. If your organisation is prone to changing Status Reason values often for an entity, you may begin to notice large gaps in Option Set values over time; annoying, but I can understand why it’s in place.
All of the above code examples should be sufficient for common scenarios, such as when you want to remove single option set values or are interacting with the Status Reason field on the main part of the form. What I wanted to do as part of today’s blog post is highlight two non-standard scenarios for the above and illustrate a solution for each - which, when brought together, demonstrate an example I was recently involved in developing a solution for.
The Problem
The business in question was using an entity across multiple parts of their business. On the entity, there was then a flag field to indicate which side of the business the entity record belonged to. This would then assist when running reports and, when coupled with Business Unit segregation for the records, ensured that colleagues in the business only saw records that they needed to as part of their day-to-day usage of the application.
Because the entity was shared, fields within the entities - such as Status Reason - were also shared and, by implication, contained values that were only relevant to a specific part of the business. We were therefore tasked with finding a solution to ensure that the Status Reason value list was modified for each person to reflect the record type they were working with. Colleagues primarily worked within the Web Application and the Status Reason field was on each form, within the Header area.
Solution #1: Efficiently Removing Multiple Option Set Values
When we take a look at the code examples, we are given pretty much everything we need to start implementing a solution. So if we assume that our Status Reason field contains 15 values, 10 of which we want to remove, we may be tricked into writing the following code and doing the good ol’ copy & paste:
Xrm.Page.getControl('statuscode').removeOption(100000000);
Xrm.Page.getControl('statuscode').removeOption(100000001);
Xrm.Page.getControl('statuscode').removeOption(100000002);
Xrm.Page.getControl('statuscode').removeOption(100000003);
Xrm.Page.getControl('statuscode').removeOption(100000004);
Xrm.Page.getControl('statuscode').removeOption(100000005);
Xrm.Page.getControl('statuscode').removeOption(100000006);
Xrm.Page.getControl('statuscode').removeOption(100000007);
Xrm.Page.getControl('statuscode').removeOption(100000008);
Xrm.Page.getControl('statuscode').removeOption(100000009);
Now, this code will work fine and can be pretty clearly deciphered. The problem lies in the number of lines it is expressed in (thereby increasing load times/processing time when CRM is processing your function). We can get round this by thinking back to Programming 101 and making the correct assumption that JScript has the ability to loop through similar commands - in this case, via the for Loop. By introducing an array into the mix as well, we can then express our code much more simply:
var statusCodes = [100000000, 100000001, 100000002, 100000003, 100000004,
100000005, 100000006, 100000007, 100000008, 100000009];
for (index = 0; index < statusCodes.length; ++index) {
Xrm.Page.getControl('statuscode').removeOption(statusCodes[index]);
}
Solution #2: Working with Header Controls A.K.A. Why I Hate ‘Object reference not set to an instance of an object’ Errors
I perhaps should have prefaced the above when I said ‘Now this code will work fine’. 🙂 Going back to the task at hand, when I attempted to deploy a version of the above code into the target environment, I immediately got the dreaded error message referenced above. This is generally the hallmark error to steer you towards checking your code for any typos, as the error message is basically telling you it can’t find the thing you are asking it to - in this case, the field statuscode.
After triple-checking the exact spelling of statuscode and making sure the field was on the form, I did some further diving into Developer Tools with the form loaded to figure out just what the hell was going wrong. When performing a search for the statuscode field on the DOM Explorer, I noticed that I got a few hits for statuscode - but that it was prefaced with the value header_. I followed this up with some further research online, which confirmed that, if fields in the Header need to be referenced, then they should be prefaced with header_. So our for Loop example would need to look as follows instead:
for (index = 0; index < statusCodes.length; ++index) {
Xrm.Page.getControl('header_statuscode').removeOption(statusCodes[index]);
}
Bringing it All Together
After encapsulating all of the above into a single function, removeStatusCodes(), we can then call the following snippet as part of an OnLoad function to hide our Status Reason values if the flag field is set to the correct value:
if (Xrm.Page.getAttribute('new_myflagfield').getValue() == "100000000") {
removeStatusCodes();
}
This will then work as intended and ensure that end-users only see the Status Reason values that directly concern them; no doubt assisting greatly in making the system as streamlined as possible to use and to avoid any potential for data-entry problems further down the line.