The CreatorCon Call for Content is officially open! Get started here.

petercawdron
Kilo Guru

It is possible for catalog client scripts to coexist and interact with portal widgets. All that's needed is access to the windows.angular object. 

In this scenario, I wanted to have a list of records the user could choose between. Once clicked, the rest of the form populates with data from that record.

find_real_file.png

To get this to work required cloning the simple list widget (as it needs some minor modifications) and adding it as a (Macro) field on the catalog item.

find_real_file.png

Catalog Client Script (on load)

When the service portal catalog item loads, a catalog client script sets up the interaction with the simple list clone (or any other widget you want to use this approach with)

function onLoad() {

	//Get the windows object
	var win = (0,eval)('this');
	var jQ = win.jQuery;
	var ngMain  = win.angular.element('main');
	var rootScope = ngMain.injector().get('$rootScope');
	var currentUser = ngMain.scope().user.sys_id;
	var whichData = {"table":"u_thisTable",
					 "filter":"u_affected_user=" + currentUser ,
					 "display_field":"u_leave_type",
					 "secondary_fields":"u_start_date,u_end_date",
					 "title":"Select one of your requests",
					 "maximum_entries":5};
	
	//As the widget loads after the catalog item, we'll poll to see when it's up and listening
	win.sessionStorage.setItem('broadcasting', 'sending');
	broadcastAngular(0);
	
	function broadcastAngular(timer){
		//broadcast every 100ms until the widget is listening or a max of 5 seconds has passed
		if(win.sessionStorage.getItem('broadcasting')=='sending' && timer<5000){

			//try to broadcast
			rootScope.$broadcast('getData',whichData);
			
			//loop to see if the broadcast was heard (as broadcasting will become received)
			win.setTimeout(function(){ 
				timer+=100; 
				broadcastAngular(timer);
			}, 100);
		}
	}
	
	//this runs when a user clicks on a record in the simple list
	rootScope.$on('$sp.list.click', function(event, details) {
		
		//change fields on the form using information from Simple List
		g_form.setValue('amend_type', details.record.display_field.display_value);
		
	});
}

The changes to the simple list widget were to allow it to be updated remotely rather than just when the page loads.

Widget Client Script

	/////////////////////////////////////////////////////////////////////
	//Listen for catalog client onload/onchange scripts 
	$rootScope.$on('getData', function(event, details) {
		//Let the catalog script know we've heard
		window.sessionStorage.setItem('broadcasting', 'received');
		//Interact with the widget server code
		c.server.get(details).then(function(response){
			c.data = response.data;
			c.options.title = details.title;
			c.options.color = "default"
		})
	});
	/////////////////////////////////////////////////////////////////////

I also found a few of the $location.url lines in the client script needed to be removed to prevent navigating away from the page when a user clicks on a record.

Widget Server Script

	/////////////////////////////////////////////////////////////////////
	//At the start of the server script listen for input from the client
	if(input.hasOwnProperty('table')){
		options.table            = input.table;
		options.filter           = input.filter;
		options.display_field    = input.display_field;
		options.order_by         = input.display_field;
		options.secondary_fields = input.secondary_fields;
		options.title            = input.title;
		options.maximum_entries  = input.maximum_entries;
		options.sp_page          = false;
		options.url              = '';
	}
	/////////////////////////////////////////////////////////////////////

 

...and that's really it... the rootScope.$on is the angular equivalent of an on change event, and the details contain information about the record that was clicked. 

I also included a catalog client script on change so if the user changed the simple list would update accordingly, but it's basically just a repeat of everything up to (but not including) the rootscope.$on statement above. The only difference was, once the catalog item has loaded there's no longer any need for the time out.

function onChange(control, oldValue, newValue, isLoading) {
	if (isLoading || newValue == '') {
		return;
	}

	var win = (0,eval)('this');
	var jQ = win.jQuery;
	var ngMain  = win.angular.element('main');
	var rootScope = ngMain.injector().get('$rootScope');
	var whichData = {"table":"u_thisTable",
					 "filter":"u_affected_user=" + newValue ,
					 "display_field":"u_leave_type",
					 "secondary_fields":"u_start_date,u_end_date",
					 "title":"Select one of your current leave requests","maximum_entries":5};

	rootScope.$broadcast('getData',whichData);
}

As with anything using angular or jQuery, be aware of the need for testing between major releases as if there are fundamental changes to the way ServiceNow manages its angular (such as changing the 'main' element), this could break. In practice, such a radical change isn't likely as it would cause significant rework for ServiceNow themselves.

Have fun

Version history
Last update:
‎05-28-2019 05:42 PM
Updated by: