- Post History
- Subscribe to RSS Feed
- Mark as New
- Mark as Read
- Bookmark
- Subscribe
- Printer Friendly Page
- Report Inappropriate Content
on 09-14-2018 04:50 PM
There have been a number of questions raised asking how we can control more than just the text of Field Messages. Here's how...
Intended results of this example:
When a customer types in a text box, I want to use the showFieldMsg function to dynamically list any glossary terms in a custom table, and when applicable, add hyperlinks to those sections of our intranet.
Architecture:
1 client script
1 script include (not necessary, but this complies with Service-Now's best practices, specifically that GlideRecord calls should only occur on the server)
Client Script:
function onChange(control, oldValue, newValue, isLoading, isTemplate) {
if (isLoading || newValue === '') {
return;
}
var fieldOfInterest = 'u_full_description_test';
var words = getWordsFromField(fieldOfInterest); // build an array of all words in our field
var abbreviationsUsed = []; // this will hold a list of all abbreviations info found in our field of interest
for (var i = 0; i < words.length; i++) { // loop through every word to test it as an abbreviations
try {
var ga = new GlideAjax('MyAJAXScript'); // make a call to 'MyAJAXScript' script
ga.addParam('sysparm_name', 'getAbbreviationInfo'); // invoke the 'getAbbreviationInfo' function in the 'MyAJAXScript' script
ga.addParam('sysparm_abbreviation', words[i]); // pass in the potential abbreviation within 'sysparm_abbreviation'
ga.getXMLWait();
var answer = ga.getAnswer();
answer = answer.evalJSON(); // we're expecting a multipart result with {abbreviation, fullterm, link} in JSON format
if (!answer || !answer.abbreviation) // this word is not an abbreviation
continue;
if (isAbbreviationUsed(answer.abbreviation, abbreviationsUsed)) // if we've already come across the abbreviation
continue;
g_form.showFieldMsg(fieldOfInterest, 'loading...'); // put a placeholder in the Field Message for now (we'll overwrite this once all Field Messages are set up)
var abbrevObject = {
abbreviation: answer.abbreviation,
fullTerm: answer.fullTerm,
link: answer.link
};
abbreviationsUsed.push(abbrevObject);
} catch (err) {
console.log(err);
}
}
if (abbreviationsUsed.length == 0) // no abbreviations found, so we can stop here
return;
var elements = getFieldMessageElements(fieldOfInterest); // get all Field Message elements for the given field
for (var j = 0; j < elements.length; j++)
elements[j].innerHTML = getText(abbreviationsUsed[j]); // update the given Field Message with one of our abbreviations found
}
// build an array of the words found in a given field
function getWordsFromField(field) {
var text = g_form.getValue(field); // get the original text from the given field
text = text.replace(/<(?:.|\n)*?>/gm, ''); // remove HTML tags
text = text.replace(/nbsp;/g, ''); // remove any " " strings (only remove the "nbsp;" if we include the "&", it'll be interpreted as a space)
text = text.replace(/[.,\/#!$%\^&\*;:{}=\-_`~()'"]/g, ' '); // replace punctuation with spaces (adding a space helps with text using embedded links)
var words = text.split(' '); // build array of all words
words = words.filter(Boolean); // remove all empty entries (ie: ['i', 'had', '', '', 'empty', 'entries'] becomes ['i', 'had', 'empty', 'entries'])
return words;
}
// check if the given abbreviation is already in our array of the abbreviations we've encountered
function isAbbreviationUsed(abbreviation, abbreviationsUsed) {
var abbrev = abbreviation.toLowerCase();
for (var i = 0; i < abbreviationsUsed.length; i++)
if (abbrev == abbreviationsUsed[i].abbreviation.toLowerCase())
return true;
return false;
}
// get all Field Message DOM elements for a given field
function getFieldMessageElements(field) {
try {
var inputElement = g_form.getControl(field); // ask Service-Now for a DOM reference to our field of interest
var parentElement = inputElement.parentElement; // get the field's parent because the Field Messages are siblings to inputElement
var childrenElements = parentElement.childNodes; // get all of the children (inputElement's siblings and itself)
for (i = 0; i < childrenElements.length; i++) {
if (childrenElements[i].id.indexOf('_fieldmsg') < 0) // we want to find the child with '_fieldmsg' in the ID
continue;
return childrenElements[i].childNodes; // we've found the container of the Field Message(s)
}
}
catch (err) {
console.log(err.message);
}
return null;
}
// build text and hyperlink (when one exists) for a given abbreviation object
function getText(abbreviationObject) {
var text = abbreviationObject.abbreviation + ' - ' + abbreviationObject.fullTerm;
if (abbreviationObject.link.length)
text = '<a href="' + abbreviationObject.link + '">' + text + '</a>';
return text;
}
Script Include:
var MyAJAXScript = Class.create();
MyAJAXScript.prototype = Object.extendsObject(AbstractAjaxProcessor, {
// gets abbreviation information for a given word
getAbbreviationInfo: function () {
var obj = {};
var abbreviation = this.getParameter('sysparm_abbreviation');
var gr = new GlideRecord('u_latham_abbreviations');
gr.addQuery('u_abbreviation', abbreviation);
gr.query();
if (gr.next()) { // if the word is a known abbreviation
obj.abbreviation = gr.u_abbreviation + ''; // add an empty string to implicitly cast as a string (otherwise it'll be an object)
obj.fullTerm = gr.u_full_term + '';
obj.link = gr.u_link + '';
}
var json = new JSON();
var encodedObj = json.encode(obj);
return encodedObj;
},
type: 'MyAJAXScript'
});
This all assumes a custom table with three fields:
-
u_abbreviation
-
u_full_term
-
u_link
To give a detailed explanation of the most important function...
// get all Field Message DOM elements for a given field
function getFieldMessageElements(field) {
try {
var inputElement = g_form.getControl(field); // ask Service-Now for a DOM reference to our field of interest
var parentElement = inputElement.parentElement; // get the field's parent because the Field Messages are siblings to inputElement
var childrenElements = parentElement.childNodes; // get all of the children (inputElement's siblings and itself)
for (i = 0; i < childrenElements.length; i++) {
if (childrenElements[i].id.indexOf('_fieldmsg') < 0) // we want to find the child with '_fieldmsg' in the ID
continue;
return childrenElements[i].childNodes; // we've found the container of the Field Message(s)
}
}
catch (err) {
console.log(err.message);
}
return null;
}
... what we're doing here is asking for the Field Message elements so we can change the content within each. We start by asking Service-Now to give us DOM access to the appropriate field. Unfortunately, it gives us access to a sibling element, so to get to the right DOM element, we request access to the parent and then to the parent's children (ie: all siblings). The child we want to work with has "_fieldmsg" in its ID value. Every Field Message will be a child of this particular sibling, so finally, we return the child nodes of the element containing "_fieldMsg".
Can this break? Not in the current version.
Is it future-proof? ...Is anything? No, but we're using Service-Now's built-in function to request DOM access, so it's as safe as Service-Now will allow!
I hope this helps,
Ryan