Name
global.JSUtil
Description
JavaScript utility functions.
Script
/**
* JavaScript utility functions.
*
* Tom Dilatush tom.dilatush@service-now.com
*/
var JSUtil = Class.create();
/**
* Returns a map (Object) that is the union of all the given maps.
*/
JSUtil.union = function(maps) {
var result = {};
for (var i = 0; i < arguments.length; i++) {
var map = arguments[i];
for (var name in map)
result[name] = map[name];
}
return result;
};
/**
* Removes entries from the given map. The second argument defines what will be removed. If it
* is an array, it is treated as an array of names to remove. If it is an object, the names of
* its properties are the names to remove. Otherwise, it is coerced to a string as the name of
* the single item to remove.
*/
JSUtil.removeFromMap = function(map, names) {
var nms;
if (names instanceof Array)
nms = names;
else if (names instanceof Object) {
nms = [];
for (var name in names)
nms.push(name);
}
else
nms = ['' + names];
for (var i = 0; i < nms.length; i++) {
if (nms[i] in map)
delete map[nms[i]];
}
};
/*
* Returns true if item is defined but has no properties or functions. (handy for associative arrays)
*/
JSUtil.isEmpty = function(item) {
var result=false;
if (JSUtil.notNil(item)) {
result=true;
for (var i in item) {
result=false;
break;
}
}
return result;
};
/*
* Returns true if the given item is not null and is not undefined.
*/
JSUtil.has = function(item) {
return (item != null) && (typeof item != 'undefined');
};
/*
* Returns true if the given item is null or is undefined (the logical inverse of .has(), above).
*/
JSUtil.doesNotHave = function(item) {
return !JSUtil.has(item);
};
/*
* Returns true if the given item is null, undefined, or evaluates to the empty string.
*/
JSUtil.nil = function(item) {
if (JSUtil.isJavaObject(item))
return GlideJSUtil.isNilJavaObject(item);
if (item == null || typeof item == 'undefined')
return true;
if (Array.isArray(item)) {
if (!item.length)
return true;
// check first element before converting the whole array to a String.
if (item[0] != null && typeof item[0] != 'undefined' && '' != item[0] + '')
return false;
}
return '' == '' + item;
};
/*
* Returns true if the given item exists and is not empty (the logical inverse of .nil(), above).
*/
JSUtil.notNil = function(item) {
return !JSUtil.nil(item);
};
/*
* Returns the Rhino global object.
*/
JSUtil.getGlobal = function(){
return (function(){
return this;
}).call(null);
};
/*
* Returns true if the given item is a member of the given class. For JavaScript objects, this method behaves exactly
* like the JavaScript operator "instanceof". However, this method (unlike the JavaScript operator) also tests Java
* objects.
*
* item: the object to be tested.
* klass: the class to be tested (for Java objects, must be the complete class name, like "java.util.ArrayList").
*/
JSUtil.instance_of = function(item, klass) {
if (JSUtil._isString(klass)) {
item = GlideRhinoHelper.getNativeFromRhino(item);
if (JSUtil.isJavaObject(item))
return GlideJSUtil.isInstanceOf(item, klass);
return false;
}
return item instanceof klass;
};
JSUtil._isString = function(val) {
return (typeof val == 'string' || val instanceof String);
};
/*
* Returns the type of the given value as a string, as follows:
* 'null' if the given value is null or undefined
* 'string' if the given value is a primitive string or a String wrapper instance
* 'number' if the given value is a primitive number or a Number wrapper instance
* 'boolean' if the given value is a primitive boolean or a Boolean wrapper instance
* 'function' if the given value is a function
* 'object' otherwise (including if it is a Java object)
*
* See also: typeOf() which returns these or for Objects implented with 'type:' (such as
* Script Includes that use our default boilerplate), this returns that type
* which is intended to be the Javascript 'className' of the object.
*/
JSUtil.type_of = function(value) {
if (value == null)
return 'null';
if (JSUtil.isJavaObject(value))
return 'object';
var t = typeof value;
if ((t == 'string') || (t == 'number') || (t == 'boolean') || (t == 'function'))
return t;
if ((value instanceof String) || ('' + value.constructor).match(/^function String\(\)/))
return 'string';
if (value instanceof Number)
return 'number';
if (value instanceof Boolean)
return 'boolean';
return 'object';
};
/**
* Returns the type of the given value.
*
* If 'x' is JavaObject, then this is the class name,
*
* If 'x' is a JavaScript object from a JS Class (like our Script Include boilerplate)
* then this is the value of the 'type' property which is meant to be the JavaScript
* class name,
*
* If 'x' is a JavaScript Array, then this returns 'array',
*
* If 'x' is a JavaScript Date, this returns 'date'
*
* Otherwise this returns the JavaScript type: string, number, boolean or object as per
* the type_of method (above).
*
* See Also: type_of
*/
JSUtil.typeOf = function(x) {
if (typeof x === 'undefined')
return 'undefined';
if (x instanceof Array)
return "array";
if (x instanceof Date)
return "date";
var t = JSUtil.type_of(x);
if (t === 'object' && !JSUtil.isJavaObject(x) && typeof(x.type) === 'string')
return x.type;
return t;
}
/*
* Returns true if the given value is an instance of a Java object.
*/
JSUtil.isJavaObject = function(value) {
if (value == null)
return false;
if (value instanceof Packages.java.lang.Object)
return true;
return SNC.JSUtil.isJavaObject(value);
};
/*
* Coerces the given item to a boolean. If the given item is a boolean, it is passed through. Non-zero numbers return true. Null or
* undefined returns false. Strings return true only if exactly equal to 'true'.
*/
JSUtil.toBoolean = function(item) {
if (!JSUtil.has(item))
return false;
if (typeof item == 'boolean')
return item;
if (typeof item == 'number')
return item != 0;
if ((typeof item == 'string') || ((typeof item == 'object') && (item instanceof String)))
return item == 'true';
// if we get here then we've got either a non-String object or a function; always return true for these...
return true;
};
/*
* Returns the value in a boolean GlideRecord field.
*/
JSUtil.getBooleanValue = function(gr, field) {
var val = gr.getValue(field);
return (val == 'true') || (val == '1');
};
/**
* Determines whether a value exists within an object or not.
* @param {} container The haystack to search within.
* @param {} value The expected needle value to compare against.
* @param boolean|undefined compareByIdentity If true, uses === for comparison; == otherwise.
* @return True if value exists in container, False otherwise.
*/
JSUtil.contains = function(container, value, compareByIdentity) {
if (compareByIdentity) {
// identity
for (var key in container) {
if (container[key] === value)
return true;
}
} else {
// equality
for (var key in container) {
if (container[key] == value)
return true;
}
}
return false;
};
/*
* Returns true if the two given values are equivalent, and optionally logs any differences. The two
* values may be any value - JavaScript primitives or objects. Objects of classes Object, Array, Date,
* String, Boolean, and Number are all compared correctly and (as necessary) recursively. Note that
* comparand types much match exactly - for the purposes of this comparison, 'abc' does NOT match
* new String('abc'). If differences are logged, they may be retrieved from JSUtil.areEqualLog.
*/
JSUtil.areEqualLog = '';
JSUtil.areEqual = function(val1, val2, logDiff) {
JSUtil.areEqualLog = '';
if (typeof val1 != typeof val2) {
log('Different type: ' + val1 + ' (' + typeof val1 + ') and ' + val2 + ' (' + typeof val2 + ')');
return false;
}
// if we have two undefineds, we're good...
if (typeof val1 == 'undefined')
return true;
// handle the awkward case of null...
if ((val1 === null) || (val2 === null)) {
if (val1 === val2)
return true;
log('Null and ' + ((val1 === null) ? typeof val2 : typeof val1));
return false;
}
// if we've got a primitive type, directly compare...
if (!(val1 instanceof Object)) {
if (val1 === val2)
return true;
log('Different primitive ' + typeof val1 + ' values: ' + val1 + ' and ' + val2);
return false;
}
// make sure we've got a object types here...
if (typeof val1 != 'object') {
log('Unexpected type: ' + typeof val1);
return false;
}
// handle any Java objects passed in...
if (isJavaObject(val1) || isJavaObject(val2)) {
if (isJavaObjectVal1() && isJavaObject(val2)) {
if (val1.equals(val2))
return true;
log('Different Java objects');
}
log('Java object and JavaScript object');
return false;
}
// make sure we've got two JavaScript objects of the same type...
var vc1 = val1.constructor.name;
var vc2 = val2.constructor.name;
if (vc1 != vc2) {
log('Different JavaScript object types: ' + val1.constructor.name + ' and ' + val2.constructor.name);
return false;
}
// handle case of two JavaScript objects in the same class that return primitives for valueOf()...
if ((vc1 == 'Boolean') || (vc1 == 'Date') || (vc1 == 'Number') || (vc1 == 'String')) {
if (val1.valueOf() == val2.valueOf())
return true;
log('Different ' + vc1 + ' primitive wrapper values: ' + val1.valueOf() + ' and ' + val2.valueOf());
return false;
}
// if we've got two arrays, compare recursively element by element...
if (val1.constructor.name == 'Array') {
// we'd better be the same size!
if (val1.length != val2.length) {
log('Different array lengths: ' + val1.length + ' and ' + val2.length);
return false;
}
// compare all our elements, in order...
for (var i = 0; i < val1.length; i++) {
if (JSUtil.areEqual(val1[i], val2[i], logDiff))
continue;
log('Different array element values: ' + val1[i] + ' and ' + val2[i]);
return false;
}
return true;
}
// if we've got two objects, compare elements recursively and check for leftovers...
if (val1.constructor.name == 'Object') {
// collect all the property names in val2...
var vp2 = {};
for (var vn2 in val2)
vp2[vn2] = true;
// see if we have exactly the same properties in val1...
for (var vn1 in val1) {
if (vp2[vn1]) {
delete vp2[vn1];
continue;
}
log('Different properties');
return false;
}
for (var vn2 in vp2) {
log('Different properties');
return false;
}
// ok, we have the same properties - but do they have the same values?
for (var vn1 in val1) {
if (JSUtil.areEqual(val1[vn1], val2[vn1], logDiff))
continue;
log('Properties have different values');
return false;
}
return true;
}
// if we get here, then we've got two objects of unknown object types...
log('Unknown object type: ' + val1.constructor.name);
return false;
function log(msg) {
if (!logDiff)
return;
JSUtil.areEqualLog += msg;
JSUtil.areEqualLog += '\n';
}
};
/*
* Logs all the properties (recursively) in the given object: name, type, and value. The optional second parameter is a name for the logged object.
*/
JSUtil.logObject = function(obj, name) {
gs.log(JSUtil.describeObject(obj, name));
};
/*
* Returns a string that recursively describes all the properties in the given object: name, type, and value.
* The optional second parameter is a name for the logged object.
*/
JSUtil.describeObject = function(obj, name) {
var result = [];
result.push('Log Object' + ((name) ? ': ' + name : ''));
if ((typeof(obj) != 'object' && typeof(obj) != 'string') || obj == null)
result.push(' null, undefined, or not an object: ' + typeof(obj) );
else
JSUtil._describeObject(obj, null, ' ', 0, result);
return result.join('\n');
};
/*
* Internal recursive object description string builder.
*/
JSUtil._describeObject = function(obj, name, lead, level, result) {
if (level > 25) {
result.push(lead + '<<< exceeded 25 recursion levels, ignoring any deeper levels >>>');
return;
}
var ns = (name == null) ? '' : name + ': ';
var value = obj;
var type = JSUtil.type_of(value);
if (type == 'function') {
result.push(lead + ns + type);
return;
}
else if (type != 'object') {
result.push(lead + ns + type + ' = ' + value);
return;
}
if (value instanceof Array) {
result.push(lead + ns + 'Array of ' + value.length + ' elements');
for (var i = 0; i < value.length; i++)
JSUtil._describeObject(value[i], '[' + i + ']', lead + ' ', level + 1, result);
}
else {
if (JSUtil.isJavaObject(obj)) {
var klassName = GlideJSUtil.getJavaClassName(obj);
result.push(lead + ns + 'Java Object: ' + klassName + ' = ' + obj);
}
else if (obj instanceof GlideRecord) {
var rec = (!gs.nil(obj.getDisplayValue())) ? '@ ' + obj.getDisplayValue() : '';
result.push(lead + ns + 'GlideRecord(\'' + obj.getTableName() + '\') ' + rec);
}
else {
if (typeof obj.explainLock == 'function') { // is this a GlideElement of some kind?
var nm = obj.getName();
var vl = obj.getDisplayValue();
result.push(lead + ns + 'GlideElement (or child class): ' + nm + ' = ' + vl);
} else
result.push(lead + ns + 'Object');
for (var nmo in obj)
JSUtil._describeObject(obj[nmo], nmo, ' ' + lead, level + 1, result);
}
}
};
/*
* NOTE: between this banner and the following banner, several string literals are specified in an odd way: by the contatenation of a single
* character ('&') and the remainder of the HTML entity (such as 'amp;'). This method was employed to avoid having the entities translated
* into the equivalent characters when the script include is edited in the instance.
*/
JSUtil.AMP = /\&/g;
JSUtil.GT = /\>/g;
JSUtil.LT = /\</g;
JSUtil.QT = /\"/g;
JSUtil.AMP_ENT = new RegExp( '\\&' + 'amp;', 'g' );
JSUtil.GT_ENT = new RegExp( '\\&' + 'gt;', 'g' );
JSUtil.LT_ENT = new RegExp( '\\&' + 'lt;', 'g' );
JSUtil.QT_ENT = new RegExp( '\\&' + 'quot;', 'g' );
JSUtil.escapeText = function(text) {
var ampRegex = new SNC.Regex('/&/');
var ltRegex = new SNC.Regex('/</');
var gtRegex = new SNC.Regex('/>/');
var result = ampRegex.replaceAll('' + text, '&' + 'amp;');
result = ltRegex.replaceAll(result, '&' + 'lt;');
return gtRegex.replaceAll(result, '&' + 'gt;');
};
JSUtil.unescapeText = function(text) {
var ampRegex = new SNC.Regex('/&' + 'amp;/');
var ltRegex = new SNC.Regex('/&' + 'lt;/');
var gtRegex = new SNC.Regex('/&' + 'gt;/');
var result = ampRegex.replaceAll('' + text, '&');
result = ltRegex.replaceAll(result, '<');
return gtRegex.replaceAll(result, '>');
};
JSUtil.escapeAttr = function(attr) {
var result = ('' + attr).replace(JSUtil.AMP, '&' + 'amp;');
return result.replace(JSUtil.QT, '&' + 'quot;');
};
JSUtil.unescapeAttr = function(attr) {
var result = ('' + attr).replace(JSUtil.QT_ENT, '"');
return result.replace(JSUtil.AMP_ENT, '&');
};
/** Render an expanded/evaluted string from a string that may contain one
* or more Javascript expressions, each wrapped in a dolloar-braces
* delimiter pattern.
*
* 'The timeis:${newGlideDateTime()}'
*
* will displaythecurrenttime.
*
* When used in specific contexts, such as inside a workflow context
* certain global variables might be usable such as 'current' or 'workflow':
*
* 'WF State:${context.state},rec:${current.sys_id}'
*
* and content can be substituted into data from various Javascripts:
*
* <CREATED>${newGlideDateTime()}</CREATED>
*
* WARNING: This is used heavily by workflows. If this is changed, then
* be sure to run all workflow tests. Test Log Message activity
* with ${workflow.variables.somevariable} and similar usages.
*/
JSUtil.strEval = function(str) {
var s = new String(str);
// if the entire string is within a ${} return the eval of it
// to allow getting an object back from this method
//
if (s.startsWith("${") && s.endsWith("}") && (s.indexOf("${", 2) == -1))
return eval(s.substring(2, s.length - 1));
// also replace anything with ${something} to eval(something)
//
s = s.replace( /\$\{\s*(.*?)\s*\}/g, function(str, p1) {
return (eval(p1) || "")
});
if (s.indexOf('javascript:') == 0)
s = eval(s.substring(11));
return s;
};
/*
* End of odd string construction...
*/
JSUtil.prototype = {
type: "JSUtil"
};
Sys ID
8d5571660a0a0b5000fc97b926f7f750