New custom application posted on Share: Location Hierarchy

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
a month ago - last edited a month ago
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 Type
A visual representation of the type hierarchy is provided by a node map in the workspace.
Location 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 Rule
An onChange client script handles when the parent value is changed on a Location form
onChange 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.
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 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 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
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 context
The following image shows how close the previous data structure was to a more abstract form behaviour pattern.
Modified 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 function
Thank you for reading the entire article. I look forward to further discussions.
- 712 Views

- Mark as New
- Bookmark
- Subscribe
- Mute
- Subscribe to RSS Feed
- Permalink
- Report Inappropriate Content
a month ago
Have modified the original post to include an example that does not require extending the GlideAjax and GlideForm client scripts.