Create field or widget to populate a variable set value

Facundo Prado
Tera Expert

I have a Multi Row variable set in a record producer, in this Variable set I have a variable named Total expenses. 

FacundoPrado_0-1679929967551.png

 

When I create a ticket on service portal, i can set multiple rows in the variable set.

FacundoPrado_1-1679929967568.png

 


So, in a new Variable on the record producer (not in the varaiable set), I need to populate de SUM of the differents rows. For example:

FacundoPrado_2-1679929967565.png

 

I have created an "ON CHANGE" Client Script, to make this, but in this case I need another field to be changed, so I create a new "Calculate Total" Field to change.

FacundoPrado_3-1679930102430.png

 

FacundoPrado_4-1679930107487.png


This is the Client Script I made

FacundoPrado_5-1679930120594.png

 

The thing is that is not good looking in the form to have a Field to calculate total, so, I was thinking on another approach to calculate, may be it could be a buttom, or widget, so when I press this buttom, make the SUM.

The question is, How can I handle or get this buttom?

Thanks

1 ACCEPTED SOLUTION

Markus Kraus
Kilo Sage

Unfortunately there is no real OOTB solution for this, just a workaround. 

The reason why there is no easy OOTB solution are the following:

  • in the Standard UI, the MRVS is a popup which is rendered via iframe and communication between MRVS (iframe) and Parent Form (parent frame) is technically only possible via events and string-data
  • in the Service Portal, while you are not separated via iframes, you are still forced to communicate using events (broadcast them to $rootScope)
  • there is a variable that was introduced some time ago called g_service_catalog which kind of acts as a "parent g_form", but it doesn't provide a setValue (yet)
  • There are some undocumented ways of getting the parent's g_form, but those ways are inconsistent for Standard UI and Service Portal and are less-OOTB and less-clean to the workaround I present below

The easiest and cleanest workaround is based on the CustomEvent, which is exposed to even scoped Client Scripts.

You need the following three client scripts:

1.) MRVS onLoad: Listen on the "MRVS onChange" event and transmit it to the parent window

2.) MRVS onSubmit: Broadcast the "MRVS onChange"
Note: Because we need the parent window, we use a small "hack" to get a handle to the "window" variable. This is a necessary evil.

3.) Catalog Item onLoad: Register on "MRVS changed" callback and update the total

 

In Detail:

1.) MRVS onLoad script:

 

function onLoad() {
	var $window;
	CustomEvent.on('x_scoped_mrvs_submitted', function () {
		if ($window) {
			// This is where the magic happens: let the parent g_form know of the MRVS item update.
			// Because we have to broadcast this event to window.parent, we need some way get a handle to
			// the window object.
			// Unfortunately a setTimeout in the 'onSubmit' function is too late as the iframe is already
			// destroyed when the setTimeout-callback is executed.
			$window.parent.CustomEvent.fire('x_scoped_update_total');
		}
	});
	
	setTimeout(function () {
		$window = this;
	});
}

 

2.) MRVS onSubmit:

 

function onSubmit() {
	CustomEvent.fire('x_scoped_mrvs_submitted');
}

 

3.) Catalog Item onLoad:

 

function onLoad() {
	CustomEvent.on('x_scoped_update_total', function () {
		setTimeout(function () {
			var total = JSON.parse(g_form.getValue('booking_details'))
				.map(function (x) { return parseFloat(x.total_expense); })
				.reduce(function (a, b) { return a + b; }, 0);
			
			g_form.setValue('grand_total', total);
		});
	});
}

 

View solution in original post

3 REPLIES 3

Markus Kraus
Kilo Sage

Unfortunately there is no real OOTB solution for this, just a workaround. 

The reason why there is no easy OOTB solution are the following:

  • in the Standard UI, the MRVS is a popup which is rendered via iframe and communication between MRVS (iframe) and Parent Form (parent frame) is technically only possible via events and string-data
  • in the Service Portal, while you are not separated via iframes, you are still forced to communicate using events (broadcast them to $rootScope)
  • there is a variable that was introduced some time ago called g_service_catalog which kind of acts as a "parent g_form", but it doesn't provide a setValue (yet)
  • There are some undocumented ways of getting the parent's g_form, but those ways are inconsistent for Standard UI and Service Portal and are less-OOTB and less-clean to the workaround I present below

The easiest and cleanest workaround is based on the CustomEvent, which is exposed to even scoped Client Scripts.

You need the following three client scripts:

1.) MRVS onLoad: Listen on the "MRVS onChange" event and transmit it to the parent window

2.) MRVS onSubmit: Broadcast the "MRVS onChange"
Note: Because we need the parent window, we use a small "hack" to get a handle to the "window" variable. This is a necessary evil.

3.) Catalog Item onLoad: Register on "MRVS changed" callback and update the total

 

In Detail:

1.) MRVS onLoad script:

 

function onLoad() {
	var $window;
	CustomEvent.on('x_scoped_mrvs_submitted', function () {
		if ($window) {
			// This is where the magic happens: let the parent g_form know of the MRVS item update.
			// Because we have to broadcast this event to window.parent, we need some way get a handle to
			// the window object.
			// Unfortunately a setTimeout in the 'onSubmit' function is too late as the iframe is already
			// destroyed when the setTimeout-callback is executed.
			$window.parent.CustomEvent.fire('x_scoped_update_total');
		}
	});
	
	setTimeout(function () {
		$window = this;
	});
}

 

2.) MRVS onSubmit:

 

function onSubmit() {
	CustomEvent.fire('x_scoped_mrvs_submitted');
}

 

3.) Catalog Item onLoad:

 

function onLoad() {
	CustomEvent.on('x_scoped_update_total', function () {
		setTimeout(function () {
			var total = JSON.parse(g_form.getValue('booking_details'))
				.map(function (x) { return parseFloat(x.total_expense); })
				.reduce(function (a, b) { return a + b; }, 0);
			
			g_form.setValue('grand_total', total);
		});
	});
}

 

Great!!! Thanks!!!
Just a question:
When you´re charging records, the fields work as expected.

FacundoPrado_0-1680012269161.png


But when you DELETE a record in the varaible set, the GRAND TOTAL is NOT changing.

FacundoPrado_1-1680012366472.png

 

Any Idea to modify this behavior?

Thanks again!!

I've taken a look at the ServiceNow Standard UI + Service Portal implementation, and unfortunately it seems that we cannot do it in any upgrade safe manner. 

Some observations:

  • GlideForm::onUserChangeValue triggers on Row Update, Row Create on Service Portal, but not on Row Removal
  • GlideForm::onUserChangeValue does not trigger at all in Standard UI
  • Standard UI uses the Utility Class TableVariableService which unfortunately doesn't propagate Row Create/Update/Removal to the parent GlideForm (g_form)

The only way to properly handle this is to by repeatedly parsing the variable set:

Catalog Item onLoad Script:

 

setInterval(function () {
    var total = JSON.parse(g_form.getValue('booking_details'))
        .map(function (x) { return parseFloat(x.total_expense); })
        .reduce(function (a, b) { return a + b; }, 0);

    g_form.setValue('grand_total', total);
}, 50);

 

 Note: This makes the previously posted Client Scripts obsolete...