New custom application posted on Share: Location Hierarchy

Katherine4
Tera Expert

The Location Hierarchy application provides a table to store the following fields:

  • Location type - a choice list using the Location Type on the Location table
  • Child location types - a list of Location Type values 
  • Copy to child - list of fields from the Location table

This enables the relationship between parent and child location types to be enforced as a coherent hierarchy, plus provides for fields to be copied from the parent location when creating a new child location.Location Type enforced by Parent TypeLocation Type enforced by Parent Type

 

A visual representation of the type hierarchy is provided by a node map in the workspace.

Location HierarchyLocation Hierarchy

This post is not only to announce the availability of the application, but is also a vehicle to discuss some of the implementation/development choices made.
Full disclosure, the post describes extending the base system GlideAjax and GlideForm client-side functions.

For GlideAjax the prototype is extended, for ServicePortal and Workspace UI only the instance of g_form is extended.


[EDIT] For anyone reluctant to use the extended client scripts but is still interested in the AbstractClientController script include and the Form Behaviour Pattern, please refer to the new screenshot at the bottom of the post demonstrating how to use vanilla GlideAjax, a modified script include, and how to passes the values to g_processFormBehaviour.


TLDR, you can find the application here: Location Hierarchy 

 

1. Controlling the choice list values on the Location form

The valid list of location types needs to be set as follows:

  • when loading a Location form, and
  • when the parent value is changed on a Location form

Loading a Location form is implemented using:

  • a display business rule to populate the scratchpad
  • an onLoad client script to process the scratchpad values

Onload Script and Script Include called from the Business RuleOnload Script and Script Include called from the Business Rule

 

An onChange client script handles when the parent value is changed on a Location form

onChange Client Script and Script IncludeonChange Client Script and Script Include

 

 

Vanilla GlideAjax and AbstractAjaxProcessor requires repetitive boilerplate code. Adding successive parameters in the client script, and getting successive parameters in the server script.

 

What if GlideAjax simplified setting the function name to be invoked, and provided a single stringified parameter to pass all parameters to the Script Include?
What if the Script Include parsed the single stringified parameter and passed that object as an argument to the function in the Script Include?

Plus, what if the Script Include automatically stringified the return value?

 

2. Introducing GlideAjax Extensions and the AbstractClientController

        if (!(glideAjax.hasOwnProperty('setName'))) {
            glideAjax.setName = function( /* String */ functionName) {
                this.addParam('sysparm_name', functionName);
            };
        }

        if (!(glideAjax.hasOwnProperty('setParams'))) {
            glideAjax.setParams = function( /* POJO */ params) {
                this.addParam('sysparm_params', JSON.stringify(params));
            };
        }
var AbstractClientController = Class.create();

AbstractClientController.prototype = Object.extendsObject(AbstractAjaxProcessor, {

    getParams: function getParams() {
        var name,
            params = JSON.parse(this.getParameter('sysparm_params')) || {},
            paramNames,
            value;

        return params;
    },

    process: function process() {
        var answer,
            functionName = this.getName(),
            functionToCall = this[functionName],
            params,
            response;

        if (functionName && functionToCall && (typeof functionToCall == 'function')) {
            try {
                params = this.getParams();
                response = functionToCall.call(this, params);
                answer = JSON.stringify(response);

                return answer;

            } catch (error) {
                return response;
            }

        } else {
            return "";
        }
    },

    type: 'AbstractClientController'
});

 

This provides the simplified code shown below. Notice the Script Include no longer needs repetitive calls to getParameter() and no longer needs to stringify the return value.

GlideAjaxParamsExplicitParse.png

 

What if GlideAjax could also automatically parse the answer and pass the object to the callback function?

        if (!(glideAjax.hasOwnProperty('getJSONAnswer'))) {
            glideAjax.getJSONAnswer = function(callbackFn, additionalParams, responseParams) {
                this.getXMLAnswer(function parseXmlAnswer(jsonAnswer, responseParams) {
                    var response;

                    if (jsonAnswer) {
						response = jsonAnswer;

                        if (responseParams && responseParams.parseAnswer) {
                            response = JSON.parse(jsonAnswer);
                        }
						
                        callbackFn.call(callbackFn, response, responseParams);
                    }
                }, additionalParams, responseParams);
            };
        }

 

Line 35 below is using getJSONAnswer and is providing the necessary responseParams to have the function parse the answer to an object.

Implicit ParsingImplicit Parsing

 

The current solution works, but uses duplicate code to manipulate the choice options which is an opportunity to introduce errors.

 

3. Introducing the GlideForm Extensions UI Script

The first extension function enables the duplicate code to be encapsulated as a single function.

        if (!(glideForm.hasOwnProperty('replaceOptions'))) {
            glideForm.replaceOptions = function(fieldName, choiceList, selectedValue) {
                var uiForm = this;

                uiForm.clearOptions(fieldName);
                choiceList.forEach(function addOption(choice) {
                    uiForm.addOption(fieldName, choice.value, choice.label);
                });

                if (selectedValue) {
                    uiForm.setValue(fieldName, selectedValue);
                }
            };
        }

 

The client scripts below show using the replaceOptions() function:

Clients scripts using the GlideForm extension functionClients scripts using the GlideForm extension function

4. What about copying fields to the new Location records?

Including this functionality requires modifying the following:

  • the script include that serves the client scripts
  • the onLoad client script
  • the onChange client script 

IncludeFieldsToCopy.png

ClientScriptsCopyFields.png

 

Adding this functionality has identified a pattern of constructing objects in the server script and deconstructing those objects in the client scripts in order to achieve the desired form behaviour. Our example only required modifying two client scripts.

What if we could abstract the communication of that desired form behaviour to a simple consistent array?

 

5. Introducing the Form Behaviour Pattern

This is implemented as a global function published in the GlideForm Extensions UI Script.

   if (!(exports.g_processFormBehaviour)) {
        exports.g_processFormBehaviour = function(scratchPad, formBehaviour) {
            //Ensure the context of "this" is the current instance of g_form using bind() or call()
            var uiForm = this;

            try {
                extendGlideForm(uiForm);

                scratchPad.isProcessingFormBehavior = true;

                formBehaviour.forEach(function applyUpdate(update) {
                    try {
                        uiForm[update.name].apply(uiForm, update.params);
                    } catch (error) {}
                });
            } finally {
                scratchPad.isProcessingFormBehavior = false;
            }
        };
    }

The signature of this function is easy to .bind() as the common callback function in GlideAjax calls, or as an explicit .call() as shown below:

Bind or Call using g_form as the contextBind or Call using g_form as the context

 

The following image shows how close the previous data structure was to a more abstract form behaviour pattern.

Modified script includeModified script include

 

6. Food for thought

If the client scripts were using the form behaviour pattern before the functionality to copy fields was introduced, they would not have needed any changes. The script include would supply the additional form behaviour items to be processed by the client.


7. Form Behaviour Pattern without extended client scripts.

Vanilla GlideAjax and the g_processFormBehaviour  functionVanilla GlideAjax and the g_processFormBehaviour function

 

 

 

 

Thank you for reading the entire article. I look forward to further discussions.

1 REPLY 1

Katherine4
Tera Expert

Have modified the original post to include an example that does not require extending the GlideAjax and GlideForm client scripts.