But wait … there’s more!

“Waiting is one of the great arts.”
Margery Allingham

Every once in a while, I will run into a situation where my code needs to wait for some other asynchronous process to complete. You don’t really want to go anywhere, and you don’t want to do anything. You just want to wait. On the client side, this is easily accomplished with a simple setTimeout. This is usually done with a basic recursive loop that repeats until the conditions are right to proceed.

function waitForSomething() {
	if (something) {
		itIsNowOkToProceed();
	} else {
		setTimeout(waitForSomething, 500);
	}
}

Unfortunately, on the server side, where this need usually arises, the setTimeout function is not available. However, there is a widely known, but poorly documented, GlideSystem function called sleep that you can use in the global scope to provide virtually the same purpose.

function waitForSomething() {
	if (something) {
		itIsNowOkToProceed();
	} else {
		gs.sleep(500);
		waitForSomething();
	}
}

Don’t try this in a scoped application, though, because you will just get a nasty message about how gs.sleep() is reserved for the global scope only. Fortunately, like a lot of things in ServiceNow, if you know the secret, you can get around such restrictions. In this case, all you really need is a Script Include that IS in the global scope, and then you can call that script from your scoped application and it will work just fine. Here is a simple example of just such as script:

var Sleeper = Class.create();
Sleeper.prototype = {
    initialize: function() {
    },

	sleep: function(ms) {
		gs.sleep(ms);
	},

    type: 'Sleeper'
};

Once you have your global-scoped Script Include set up, you can now call it from a scoped app and it will actually work.

function waitForSomething() {
	if (something) {
		itIsNowOkToProceed();
	} else {
		new global.Sleeper().sleep(500);
		waitForSomething();
	}
}

Reference Type System Properties, Part III

“Everything should be made as simple as possible, but not simpler.”
Albert Einstein

Last time, we got far enough along in the System Property value page modifications to demonstrate that we could replace the rendered input element with something else of our own design. Not having a design of our own for an adequate replacement, we implemented the popular creative avoidance strategy by working on all of the other parts and pieces first until we finally ran out of other parts and pieces. Now it is time to come up with a plan and finish this little project up.

I have to admit that I’m not all that proud of what I eventually came up with, but it does satisfy Rule #1, so at this point, I’ll take it and call it good. I tried a number of other things first, but none of those really got me what I wanted, so here we are. The basic plan is pretty simple, and consists of two parts: 1) a hidden input element to replace the text input element so that it can be submitted with the form, and 2) an iframe into which we will put our new input element via a stand-alone page designed for that purpose. I don’t really like the iframe approach, but it does have the benefit of being independently rendered, which gives us the opportunity to leverage the snRecordPicker for our input, which we really cannot do by simply modifying the main page directly after it has been delivered.

So let’s start out with the script that will build the HTML that we will use to replace the original text input element:

function buildHTML(prop) {
	var html = "";
	html += "<input type=\"hidden\" id=\"" + prop.property + "\" name=\"" + prop.property + "\"/>\n";
	html += "<div style=\"width: 100%; height: auto;\">\n";
	html += " <iframe id=\"frame." + prop.property + "\" src=\"/$sp.do?id=reference_properties&sysparm_property=" + prop.property + "&sysparm_table=" + prop.tableName + "&sysparm_column=" + prop.column + "&sysparm_value=" + prop.value + "\" style=\"border: none; height: 65px; width: 100%;\"></iframe>\n";
	html += "</div>\n";
	return html;
}

The hidden input element virtually replaces the original text input element, having the same name and same id. The iframe element is pretty vanilla stuff as well; the only thing of interest really is the src parameter, which points to the Portal Page that we are about to create, and passes along all of the various values needed to make the page do what we want. The Portal Page itself is just a single page with a single container filled with a single widget. The page is not really worth looking at, so let’s just jump right into the widget, as that’s where all of the magic happens. Here is the HTML:

<div id="pickerdiv">
  <sn-record-picker field="field" table="c.data.table" display-field="c.data.column" value-field="'sys_id'" search-fields="c.data.column" page-size="c.data.pageSize"></sn-record-picker>
</div>

Not much to see there, either. It’s just your standard snRecordPicker with pretty much every attribute defined by a variable. We’ll snag the values for those variables off of the URL for the page, which we populated when we constructed the src attribute for the iframe tag. The widget’s client-side script does most of the heavy lifting here:

function($scope, $location) {
	var c = this;
	var qp = $location.search();
	c.data.property = qp.sysparm_property;
	c.data.table = qp.sysparm_table;
	c.data.column = qp.sysparm_column;
	c.data.pageSize = 20;
	c.data.fieldValue = '';
	c.data.fieldDisplayValue = '';
	if (qp.sysparm_page_size) {
		c.data.pageSize = qp.sysparm_page_size;
	}
	if (qp.sysparm_value) {
		c.data.fieldValue = qp.sysparm_value;
		c.server.update().then(function(response) {
			c.data.fieldDisplayValue = response.fieldDisplayValue;
			$scope.field = {
				displayValue: c.data.fieldDisplayValue,
				value: c.data.fieldValue,
				name: 'field'
			};
		});		
	} else {
		$scope.field = {
			displayValue: '',
			value: '',
			name: 'field'
		};
	}
	$scope.$on('field.change', function(evt, parms) {
		if (parms.field.name == 'field') {
			parent.updateReferenceProperty(c.data.property, parms.field.value);
		}
	});
}

The only reason for the server-side script is to fetch the display value of the property if the property is valued at the time that the page is delivered to the browser.

(function() {
	if (input) {
		if (input.fieldValue) {
			var gr = new GlideRecord(input.table);
			gr.get(input.fieldValue);
			data.fieldDisplayValue = gr.getDisplayValue(input.column);
		} else {
			data.fieldDisplayValue = '';
		}
	}
})();

That’s about all there is to it. For every property on the page where Type=reference, the standard text input element is replaced with a hidden input element and an iframe, and inside the iframe is a ServiceNow Service Portal page that contains a single widget containing a single snRecordPicker. The parameters for the picker are passed from the iframe to the portal page via URL parameters, which are picked up by the widget and used to configure the snRecordPicker. All changes to the snRecordPicker are copied over to the hidden input field, so when the form is submitted, the selected value is sent to the server and posted to the database.

There was a minor problem with this initial version when trying to figure out the optimum height for the iframe. The height of the snRecordPicker depends on whether or not the drop-down list of choices is present, and I couldn’t find a CSS-centric way of having the iframe automatically adjust for the change in height, nor could I find a way to have the drop-down list of selectable choices overlay the content below, which is outside of the iframe. Finally, I resorted to plain old Javascript, and set up a variable called c.data.expanded to indicate whether or not the pick list was present on the screen. With a little view selection source magic, I was able to figure out that the component to watch had an id of select2-drop-mask, and so I modified the widget’s client-side code to check the required iframe height every second and adjust if needed:

function($scope, $location) {
	var c = this;
	var qp = $location.search();
	c.data.property = qp.sysparm_property;
	c.data.table = qp.sysparm_table;
	c.data.column = qp.sysparm_column;
	c.data.pageSize = 20;
	c.data.fieldValue = '';
	c.data.fieldDisplayValue = '';
	c.data.expanded = false;
	if (qp.sysparm_page_size) {
		c.data.pageSize = qp.sysparm_page_size;
	}
	if (qp.sysparm_value) {
		c.data.fieldValue = qp.sysparm_value;
		c.server.update().then(function(response) {
			c.data.fieldDisplayValue = response.fieldDisplayValue;
			$scope.field = {
				displayValue: c.data.fieldDisplayValue,
				value: c.data.fieldValue,
				name: 'field'
			};
		});		
	} else {
		$scope.field = {
			displayValue: '',
			value: '',
			name: 'field'
		};
	}
	$scope.$on('field.change', function(evt, parms) {
		if (parms.field.name == 'field') {
			parent.updateReferenceProperty(c.data.property, parms.field.value);
		}
	});
	checkHeight();
	function checkHeight() {
		var elem = document.getElementById('select2-drop-mask');
		if (elem) {
			if (elem.style.display == 'none') {
				if (c.data.expanded) {
					c.data.expanded = false;
					setHeight('65px');
				}
			} else {
				if (!c.data.expanded) {
					c.data.expanded = true;
					setHeight('300px');
				}
			}
		}
		setTimeout(checkHeight, 1000);
	}
	function setHeight(newHeight) {
		parent.updateFrameHeight(c.data.property, newHeight);
	}
}

Once that code was in place, the unexpanded state looked like this:

Modified input element with choices collapsed

… and the expanded state looked like this:

Modified input element with choices expanded

It still disturbs my sense of The Way Things Ought To Be for the left-hand edge of the revised input element not to line up with the left-hand edge of all of the other original input elements, but a fix to that was not readily apparent to me, so I have managed to let that go for now and move on to more important things. One day, though, I am going to figure out a way to fix that!

Just to recap, we modified a choice list and added an additional field to a table to provide the capability to define properties of type reference. We then created a UI Script and a Script Include so that we could replace the original input element on the property UI page, and then we created a Service Portal page and associated widget to provide the replacement for the original input element. As soon as I get a chance, I will see if I can wrap all of that up into a single Update Set and get it posted out here in case anyone wants just grab the whole package. All in all, it was a fun little project, but one that I hope to throw away one day when ServiceNow actually supports reference type properties right out of the box and having this little tweak is no longer needed.

Update: Well, it took a little longer than I had hoped to get around to this, but here is that Update Set finally.

Reference Type System Properties, Part II

“I shall either find a way or make one.”
Hannibal Barca

Last time out, we modified the sys_properties table and associated form so that we could create System Properties with a field type of Reference. This was only half the battle, however, and the easy half at that. The more difficult task will be to figure out how to get the page used to set the value of System Properties to properly render the property for user input.

Even though there is a Value field on the default System Properties form, system property values are generally set using the system_properties_ui.do page, which takes a title and one or more categories as URL parameters. You can see several examples of this in the out-of-the-box left-hand navigation menu, such as the one pictured below from the Live Feed section:

Example System Property value page

You can see from the rendered form that properties of different Types have different input mechanisms. There is a checkbox for the boolean properties and text input for the string and number properties. All we need to do is figure out how to convince it to render the Reference type properties with a selection list from the specified table. How hard can that be?

Up to this point, everything that we have done has been fairly vanilla ServiceNow stuff. Modifying Choice Lists, adding columns to Tables, manipulating Form Layouts, and creating UI Policies are all bread and butter ServiceNow sysadmin activities. There has been no need to resort to any kind of creative hackery to accomplish what we wanted do, which is always a good thing. Unfortunately, that’s all about to change.

As far as I can tell, the system_properties_ui.do page is yet another one of those items that is an integral part of the product and is not defined in the system database in a way that you can alter it in any way. As with the Email Client, we will have to rely on a script to modify the page after it has been delivered to the browser. To get an idea of how we want to do that, let’s define a property of type reference and see what the out-of-the-box functionality does with it when it renders the page. Entering sys_properties.list in the left-hand navigation search box will get us to the full list of System Properties where we can click on the New button to create our new property. At this point, I am just trying to create something that I can use to see how the input screen turns out, so I just enter Test as the Name, Test as the Description, select reference as the Type, and then select the sys_user table as the Table. Once the property has been defined, I can go back in and assign it to the Category Test by scrolling down to the bottom of the form and clicking on the New button in the Category list.

Once assigned to a Category, I can bring up the standard property value maintenance page with the URL https://<instance>.service-now.com/nav_to.do?uri=%2Fsystem_properties_ui.do%3Fsysparm_title%3DTest%2520Properties%26sysparm_category%3DTest. The good news is that the page didn’t choke on the new, unknown property type, and simply rendered the property as if the type were string:

Out-of-the-box rendering of a reference type property

More important than how it looks, though, is what’s under the hood … let’s inspect that HTML:

<tr>
 <td class="tdwrap label_left" oncontextmenu="return showPropertyContext(event, 'Test');">
  <span>
   <label class="label-inline" for="98c30fe6db362300f9699006db961935">Test</label>
   <button type="button" data-toggle="tooltip" aria-label="Property name: Test" title="" class="btn btn-icon-small btn-icon-white icon-help sn-tooltip-basic" data-original-title="Property name: Test"></button>
  </span>
 </td>
</tr>
<tr>
 <td>
  <input name="98c30fe6db362300f9699006db961935" id="98c30fe6db362300f9699006db961935" value="" aria-label="" style="width: 700px" autocomplete="off">
  <br>
  <br>
 </td>
</tr>

Pretty straightforward stuff, just a couple of single cell table rows, with the label in one row and the input box in the other. It looks like both the name and id of the input element are the sys_id of the property, so it would appear that we have everything that we need to have our way with this code, once it has been delivered to the browser.

So, here’s the plan: given the property categories available from the URL of the page, we should be able to determine all of the properties in the specified category or categories where Type=reference. Looping through that list of properties, we can find the existing input field based on the property’s sys_id, and then replace it with a more appropriate input mechanism to support the selection of records from the specified table. The only question, really, is with what will we replace the input element? If this were a Service Portal widget, we could leverage the snRecordPicker tag, but tags are useless once the page has been delivered to the browser. We could emulate everything that it does, and generate all of the HTML, CSS, and Javascript on our own, but that seems like considerably more work than I care to contemplate right now. We’ll have to give this one a little thought.

In the meantime, let’s jump on that UI Script that pulls the categories off of the URL and makes the Ajax call to the guy that will find all of the properties where Type=reference. That one shouldn’t be much work at all …

if (window.location.pathname == '/system_properties_ui.do') {
	if (window.location.href.indexOf('sysparm_category=') != -1) {
		fetchReferenceProperties();
	}
}

function fetchReferenceProperties() {
	var thisUrl = window.location.href;
	var category = thisUrl.substring(thisUrl.indexOf('sysparm_category=') + 17);
	if (category.indexOf('&') != -1) {
		category = category.substring(0, category.indexOf('&'));
	}
	if (category > '') {
		var ga = new GlideAjax('Reference_Properties');
		ga.addParam('sysparm_name', 'fetchReferenceProperties');
		ga.addParam('sysparm_category', category);
		ga.getXML(processProperties);
	}
}

function processProperties(response) {
	var answer = response.responseXML.documentElement.getAttribute("answer");
	if (answer > '') {
		var property = JSON.parse(answer);
		for (var i=0; i<property.length; i++) {
			var prop = property[i];
			var elem = gel(prop.property);
			elem.parentNode.innerHTML = buildHTML(prop);
		}
	}
}

function buildHTML(prop) {
	// we'll need to figure this out one day ...
}

Well, that wasn’t so bad. While we are thinking about the HTML that we will need to build to replace the original input tag, let’s go ahead and create that Script Include that will gather up all of the properties where Type=reference.

var Reference_Properties = Class.create();
Reference_Properties.prototype = Object.extendsObject(AbstractAjaxProcessor, {

	fetchReferenceProperties: function() {
		var property = [];
		var catList = this.getParameter('sysparm_category');
		if (catList > '') {
			catList = decodeURIComponent(catList);
			var category = catList.split(',');
			for (var i=0; i<category.length; i++) {
				var gr = new GlideRecord('sys_properties_category_m2m');
				gr.addQuery('category.name', category[i]);
				gr.addQuery('category.sys_scope', gs.getCurrentApplicationId());
				gr.addQuery('property.type', 'reference');
				gr.query();
				while (gr.next()) {
					property.push({property: gr.getValue('property')});
				}
			}
		}
		for (var i=0; i<property.length; i++) {
			var sys_id = property[i].property;
			var gr = new GlideRecord('sys_properties');
			gr.get(sys_id);
			property[i].table = gr.getValue('u_table');
			property[i].tableName = gr.getDisplayValue('u_table.name');
			property[i].value = gr.getValue('value');
			property[i].column = this.getDisplayColumn(property[i].tableName);
			property[i].displayValue = this.getDisplayValue(property[i].tableName, property[i].value, property[i].displayColumn);
		}
		return JSON.stringify(property);
	},

	getDisplayColumn: function(table) {
		if (!this.displayColumn[table]) {
			this.displayColumn[table] = this.fetchDisplayColumn(table);
		}
		return this.displayColumn[table];
	},

	fetchDisplayColumn: function(table) {
		var displayColumn = 'sys_id';
		var possibleColumn = ['name','short_description','title','description'];
		for (var i=0; i<possibleColumn.length && displayColumn == 'sys_id'; i++) {
			if (this.columnPresent(table, possibleColumn[i])) {
				displayColumn = possibleColumn[i];
			}
		}
		return displayColumn;
	},

	columnPresent: function(table, column) {
		columnPresent = false;
		var gr = new GlideRecord('sys_dictionary');
		gr.addQuery('name',  table);
		gr.addQuery('element', column);
		gr.query();
		if (gr.next()) {
			columnPresent = true;
		}
		return columnPresent;
	},

	getDisplayValue: function(table, value, column) {
		var displayValue = '';
		if (value > '') {
			var gr = new GlideRecord(table);
			gr.get(value);
			displayValue = gr.getDisplayValue(column);
		}
		return displayValue;
	},

	displayColumn: {},

	type: 'Reference_Properties'
});

This one is pretty self-explanatory, but here is the basic premise for the code: for every category in the list, we search for properties where Type=reference, and then push each one into a single pile of property objects that contain the sys_id of the property record. When we are through creating the pile, we then loop through the pile and fetch the property records using the stored sys_id so that we can add additional data to the objects, including the reference table and the current value. One of the data points that we will undoubtedly need will be the name of the field on the table that contains the “display” value. Although we could have added yet another field to the sys_properties table and had the user provide that information, for now I just hunt for it using a few potential candidates, and then fall back to sys_id if nothing else is available.

At this point, we can actually try out what we have so far, even though we still haven’t figured out what we are going to use to replace the original input element. For now, we can just dump the values onto the screen and make sure that all of the parts and pieces that we have build so far are doing what we would expect them to do. We can do that by adding a little code to the buildHTML function of our UI Script:

function buildHTML(prop) {
	return JSON.stringify(prop);
}

Now we can another little test using our previously defined test property by pulling up the same page that we did at the start.

Modified rendering of a reference type property

What that proves is that we can grab the categories from the URL, use them to find all of the reference properties in those categories, use the list of reference properties to find their corresponding input elements on the page, and then replace those input elements with something else. Now all that we have to figure out is what we really want to use to replace those input elements so that we have a pick list from the records in the specified table.

Right about now, that sounds like an interesting exercise for next time out

Reference Type System Properties

“To succeed in life, you need two things: ignorance and confidence.”
— Mark Twain

Whenever I create a ServiceNow application, I invariably end up setting up one or more System Properties to control the behavior of the app, set up options for the app, or allow the app to function differently in different environments such as Test or Production. Recently, I wanted to create a property that would be selected from a table, or in ServiceNow terms, a Reference property. Unfortunately, when I scanned the list of available property types, Reference was not on the list.

Available System Property Types

Well, not to worry. Choice Lists are easily modified, so I went to sys_properties.list, then Configure -> Table, and clicked on the Type column to bring up the Dictionary Entry. From there, I clicked on the Choices (13) tab and then clicked on the New button to create a new choice. I typed the word reference into both the Label and Value fields and then clicked on the Submit button to save the new choice. Voila! Now my instance supports System Properties of Type Reference:

New Reference Type added to the Choice List

Of course, that was the easy part … there’s still much to do. First of all, we are going to need to add another field so that we can capture the Table we will be using for the Reference. And now that I am looking at that form, there are a couple of things that are irritating my sense of The Way Things Ought To Be that I am just going to have to clean up while I’m in there. For one, the need for the Choices field is dependent on the value of the Type field, so the Type field should come first. Additionally, the Choices field shouldn’t even appear on the form if the Type is not Choice List. The new Table field, which itself should be a Reference to the Table table, should only appear if the Type is Reference. If the Type is not Choice List and the Type is not Reference, then the very next field should be Value. Let’s see if we can’t clean all of that up next.

To begin, we can add the new column to the sys_properties table right from the form itself using the hamburger menu:

Updating the Table from the Form

This will take us to the list of columns where we can click on the New button to create a new column on the table. Select Reference as the Type, enter Table as the Label and then go down to the Reference Specification tab and select Table as the Reference.

New Table Column

Saving the new column will return us to the Table specification, and clicking on the return (<) icon up in the left corner will get us back to the form where we can use the hamburger menu to select Configure -> Form Layout to move things around. Doesn’t that look better!

Rearranged System Property fields

Now, with a little UI Policy magic, we can hide both the original Choices and the new Table unless the appropriate Type has been selected. Back again to the hamburger menu where we can select Configure -> UI Policies to implement the desired changes. We’ll need to add two new policies, one for the Choices field and one for the Table field. You first have to create the policies and save them, then you can go back in and add the actions to take when the conditions are met. The condition on the first will be Type = choice list and the condition on the second will be Type = reference. Be sure the check the Reverse if false option, as we will want these policies to toggle between the true and false conditions.

Once the policies have been created, you can go back in and add the appropriate action. On the Type = choice list policy, the action will be to make the field Choices visible. On the Type = reference policy, the action will be to make the new field Table visible. Because you selected the Reverse if false option on both policies, when the the Type is not set to the specified value, the result will be that the relevant field will no longer be present on the screen.

System Property form with neither Choice List nor Reference selected

There are still a couple of more things that we could do here to make this complete, but at this point I don’t really have a pressing need for either, so I am just going to let those go for now. The first would be a potential filter for the reference fields, as there are occasions where you don’t want the user choosing from the entire contents of the table. That would basically be handled like the Table field itself, appearing on the form only when reference was selected as the Type.

The other thing that could be done at this point would be to alter the Value field to behave in accordance with the selected Type. This would seen like the appropriate thing to do, but out of the box, the open text input box does not transition to a selection of choices when you select choice list as the Type. I think the main reason that it doesn’t do that is because this is not really the place where property values are set. Properties are defined on this form, but there is another form that is generally used to set the value. We’ll tackle that form next time, as that work will be a job in and of itself.