We know that that “all interaction with Microsoft Dynamics 365 application pages must only be performed through the methods with the Xrm.Page or Xrm.Utilitynamespaces documented in the Client-side programming reference”. This is a direct quote from msdn:
https://msdn.microsoft.com/en-us/library/gg328350.aspx
However, what if we could extend/override the Xrm itself, and what if we did not have to modify any of the files for that?
The beauty (or the curse) of JavaScript is that there are not too many restrictions in that language, and, yet, it offers quite a few features you won’t easily find in other languages(such as C#).
Do you know about javascript prototypes? I think a seasoned javascript developer would be able to explain this in much more details, but, in my mind, a prototype is an object template. It’s a set of methods you can use on with that object.
For example, in the Xrm library we have form controls(control objects). They are all of different types (date time, grid, text, optionset, etc), but, within each type, they are sharing the same set of methods which you can officially use to work with the controls.
Right now I am interested in these two methods:
If I were able to replace those two with my own methods dynamically, I would suddenly have a solution that would extend out of the box functionality in an integrated way. What I mean is.. You may have a javascript in your environment that’s already using setDisabled. You may have a business rule which is locking/unlocking some fields on the form, and such business rules are using setDisabled/getDisabled behind the scene as well. If you wanted to make your own form engine to work with those out of the box features, overriding getDisabled/setDisabled looks like the most straight forward solution.
With JavaScript, we can do it easily:
if(typeof control.constructor.prototype.originalSetDisabled == 'undefined')
{
control.constructor.prototype.originalSetDisabled = control.constructor.prototype.setDisabled;
control.constructor.prototype.setDisabled = function (disabled) {
//DO WHAT YOU NEED HERE
this.originalSetDisabled(calculatedDisabled)
};
}
Now every piece of the out of the box or custom code that’s using setDisabled will be calling your own version of the setDisabled. You’ll do what you want in your own function to see if the control should be locked/unlocked, and, then, you will call the original setDisabled with an updated parameter value to finally lock/unlock the control.
Do the same with setVisible, and, out of a sudden, you can build your own engine which is completely integrated with the Xrm library.
Is it a supported solution? Well, technically it’s not accessing HTML directly, and it’s not doing anything else that would not be allowed. It’s just using JavaScript, and that’s allowed.
Can it break? Not very likely, since setDisabled/getDisabled are two of those methods which are fully supported, and, as long as those two methods are there, this kind of solution will keep working.
And, so, there is probably no need to walk you through the complete script – I’ll post the current version below, and you can also find it in the TCS Tools solution if you install that solution into your CRM organizations. The rest of that script should be very familiar (it’s disabling controls, disabling sections, disabling tabs.. etc):
var TCS = TCS || {};
TCS.FormSupport = {
EVENT_TYPE: {
OnFormLoad: 1,
OnFieldChange: 2
},
GetFormConfiguration: null,
WebResourceLock: null,
CurrentTick: null,
InternalDisableOnChange: false,
FormConfiguration: null,
Init: function () {
if (typeof Xrm == "undefined" || Xrm == null || typeof Xrm.Page == "undefined" || Xrm.Page == null) {
SetTimeout(function () { TCS.FormSupport.Init(); }, 200);
}
else {
TCS.FormSupport.AddPrototypes();
if (typeof TCS.FormSupport.GetFormConfiguration != undefined && TCS.FormSupport.GetFormConfiguration != null) {
TCS.FormSupport.FormConfiguration = TCS.FormSupport.GetFormConfiguration();
TCS.FormSupport.ProcessFormConfiguration(TCS.FormSupport.EVENT_TYPE.OnFormLoad);
}
}
},
AddPrototypes: function () {
Xrm.Page.ui.controls.forEach(function (control, index) {
if (typeof control.constructor.prototype.originalSetDisabled == 'undefined') {
if (typeof control.getDisabled != 'undefined') {
control.originalDisabled = control.getDisabled();
control.constructor.prototype.originalSetDisabled = control.constructor.prototype.setDisabled;
control.constructor.prototype.setDisabled = function (disabled) {
this.originalDisabled = disabled;
TCS.FormSupport.CalculateSettings();
TCS.FormSupport.LockControlAsNeeded(this);
};
}
if (typeof control.getVisible != 'undefined') {
control.originalVisible = control.getVisible();
control.constructor.prototype.originalSetVisible = control.constructor.prototype.setVisible;
control.constructor.prototype.setVisible = function (visible) {
this.originalVisible = visible;
TCS.FormSupport.CalculateSettings();
TCS.FormSupport.HideControlAsNeeded(this);
}
}
}
});
/*
Xrm.Page.ui.tabs.get(0).sections.get(0).constructor.prototype.setDisabled = function (isDisabled) {
this.controls.forEach(function (control, index) {
if (typeof control.getDisabled != 'undefined') {
control.setDisabled(isDisabled);
}
});
};
Xrm.Page.ui.tabs.get(0).constructor.prototype.setDisabled = function (isDisabled) {
this.sections.forEach(function (section, index) {
section.setDisabled(isDisabled);
});
};
*/
},
CreateOnChangeHandler: function (action) {
return function (context) {
if (TCS.FormSupport.InternalDisableOnChange != true) {
TCS.FormSupport.InternalDisableOnChange = true;
if (typeof action.action != 'undefined' && action.action != null) action.action();
TCS.FormSupport.InternalDisableOnChange = false;
TCS.FormSupport.ProcessFormConfiguration(TCS.FormSupport.EVENT_TYPE.OnFieldChange);
}
};
},
HideControlAsNeeded: function (control) {
if (typeof control.originalSetVisible == 'undefined') return;
if (typeof control.getAttribute == 'undefined') return;
var attributeName = control.getAttribute();
if (attributeName != null) attributeName = attributeName.getName();
var sectionName = null;
var tabName = null;
var section = control.getParent();
if (section != null) {
sectionName = section.getName();
var tab = section.getParent();
if (tab != null) {
tabName = tab.getName();
}
}
var controlHidden = control.originalVisible == false;
if (!controlHidden) {
for (var index = 0; index < TCS.FormSupport.FormConfiguration.settings.length; index++) { var currentSetting = TCS.FormSupport.FormConfiguration.settings[index]; controlHidden = ( currentSetting.hideFields.indexOf(attributeName) > -1 ||
currentSetting.hideSections.indexOf(sectionName) > -1 ||
currentSetting.hideTabs.indexOf(tabName) > -1) && currentSetting.conditionMet;
if (controlHidden) {
break;
}
}
}
if (control.getVisible() != !controlHidden) {
control.originalSetVisible(!controlHidden);
}
},
LockControlAsNeeded: function (control) {
if (typeof control.originalSetDisabled == 'undefined') return;
if (typeof control.getAttribute == 'undefined') return;
var attributeName = control.getAttribute();
if (attributeName != null) attributeName = attributeName.getName();
var sectionName = null;
var tabName = null;
var section = control.getParent();
if (section != null) {
sectionName = section.getName();
var tab = section.getParent();
if (tab != null) {
tabName = tab.getName();
}
}
var controlLocked = control.originalDisabled == true;
if (!controlLocked) {
for (var index = 0; index < TCS.FormSupport.FormConfiguration.settings.length; index++) { var currentSetting = TCS.FormSupport.FormConfiguration.settings[index]; controlLocked = (currentSetting.lockFields.indexOf(attributeName) > -1 ||
currentSetting.lockSections.indexOf(sectionName) > -1 ||
currentSetting.lockTabs.indexOf(tabName) > -1) && currentSetting.conditionMet;
if (controlLocked) {
break;
}
}
}
if (control.getDisabled() != controlLocked) {
control.originalSetDisabled(controlLocked);
}
/*if (typeof control.getObject != 'undefined' && TCS.FormSupport.WebResourceLock != null) {
TCS.FormSupport.WebResourceLock(control, controlLocked);
}*/
},
CalculateSettings: function () {
for (var index = 0; index < TCS.FormSupport.FormConfiguration.settings.length; index++) {
var currentSetting = TCS.FormSupport.FormConfiguration.settings[index];
if (typeof currentSetting.condition == 'undefined' || currentSetting.condition == null) {
alert('Form Support Script: Condition is not defined for ' + currentSetting.name);
return;
}
currentSetting.conditionMet = currentSetting.condition();
}
},
ProcessFormConfiguration: function (eventType) {
debugger;
if (eventType == TCS.FormSupport.EVENT_TYPE.OnFormLoad) {
if (typeof TCS.FormSupport.FormConfiguration.onLoadAction != 'undefined'
&& TCS.FormSupport.FormConfiguration.onLoadAction != null) {
TCS.FormSupport.FormConfiguration.onLoadAction();
}
for (var index = 0 ; index < TCS.FormSupport.FormConfiguration.onChangeActions.length; index++) {
var currentAction = TCS.FormSupport.FormConfiguration.onChangeActions[index];
var attrib = Xrm.Page.getAttribute(currentAction.attributeName);
if (attrib != null) {
attrib.addOnChange(TCS.FormSupport.CreateOnChangeHandler(currentAction));
TCS.FormSupport.InternalDisableOnChange = true;
if (typeof currentAction.action != 'undefined' && currentAction.action != null && currentAction.action != "") {
currentAction.action();
}
TCS.FormSupport.InternalDisableOnChange = false;
}
}
//TCS.FormSupport.ProcessFormConfiguration(TCS.FormSupport.EVENT_TYPE.OnFieldChange);
}
TCS.FormSupport.CalculateSettings();
/*
Xrm.Page.ui.tabs.forEach(function (tab, index) {
TCS.FormSupport.HideTabAsNeeded(tab);
});
*/
Xrm.Page.ui.controls.forEach(function (control, index) {
TCS.FormSupport.LockControlAsNeeded(control);
TCS.FormSupport.HideControlAsNeeded(control);
});
}
};