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

Offical Documentation

Official Docs: