Extending the Xrm itself (Part 3 of how to lock/unlock form controls)

By | July 30, 2017

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:

  • setDisabled
  • getDisabled

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);
        });
    }

};

3 thoughts on “Extending the Xrm itself (Part 3 of how to lock/unlock form controls)

  1. Rik

    I have impression that the code in the forEach loop in the function AddPrototype will not work correctly, as the field “originalDisabled” will not be initialized for all control elements. The code that initializes the field:
    if (typeof control.getDisabled != ‘undefined’) {
    control.originalDisabled = control.getDisabled();
    }
    shouldn’t this be outside of the if-statement?
    with the original code I get behavior which I did not expect when using the “script configuration” to lock/unlock Tabs. fields that are locked in the original form, get unlocked.

    I changed the code (see below), and now the fields that are locked in the form stay locked.

    —————-
    if (typeof control.getDisabled != ‘undefined’) {
    control.originalDisabled = control.getDisabled();
    }
    if (typeof control.constructor.prototype.originalSetDisabled == ‘undefined’) {
    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();
    }
    if (typeof control.constructor.prototype.originalSetVisible == ‘undefined’) {
    control.constructor.prototype.originalSetVisible = control.constructor.prototype.setVisible;
    control.constructor.prototype.setVisible = function (visible) {
    this.originalVisible = visible;
    TCS.FormSupport.CalculateSettings();
    TCS.FormSupport.HideControlAsNeeded(this);
    };
    }

    Reply
    1. Alex Shlega Post author

      Hi Rik,

      it’s been a while since I posted that code… just looking at it now, it seems originalDisabled won’t be initialized only if that control does not support “setDisabled”/”getDisabled”, in which case originalSetDisabled won’t be initialized either, so, for that control, LockControlAsNeeded won’t do anything (return in the first line), and it should not matter if originalDisabled has been initialized or not. But I’ll need to retest to be sure – like I said, it’s been a while. Might try it a bit later.

      Reply
  2. Rik

    Hi Alex,

    Thanks for the reply.

    I also found another side-effect that was unexpected. In the dialog box to activate a record, the control buttons (confirm & cancel) despaired when I use the form -supporter code. It have impression that problem occurs, because all control items of form are adapted in AddPrototypes, an this also effects the controls on the dialog boxes. What seems to resolve it is to add check on control item to see if it is an Attribute:

    TCS.formContext.ui.controls.forEach(function (control, index) {
    if (typeof control.getAttribute != ‘undefined’) {
    ……..

    I have very limited knowledge and experience with MS Dynamics, so I might be wrong on this …

    Reply

Leave a Reply to Rik Cancel reply

Your email address will not be published. Required fields are marked *