Help with a Service Portal custom widget script

yltsai
Mega Guru

Release family: San Diego

There is a simple widget but I cannot build out a client script for it.

If a selection as no, then the message widget will display. See the screenshot below.

0.png

I did all HTML and CSS for it but not able to build out a client controller getting the "x" button to work.

Below is the HTML.

 

<div class="no-permit-message-wrapper justify-between flex">
<!-- your widget template -->
<div class="message-authorization" tabindex="0" aria-label="Attach letter of authorization">
${Please attach your organization's letter of authorization at the bottom of this form.}
</div>
<!-- ng-click="close()" -->
<i class="fa fa-times pull-right wrapper-xs ng-scope closeX" ng-click="this.parentElement.style.display='none'" aria-label="Close information" tabindex="0" role="button"></i>
</div>

 

I searched within the ServiceNow instance, ServiceNow community, Stack overflow, and W3 school to find some answer but not much.

Please help. Thank you all.

 

1 ACCEPTED SOLUTION

1st of all I should mention that there is such a thing as variable message. One calls g_form.showFieldMsg() and a message just like you show in the screen-shot is added below the field. Its analogous g_form.hideFieldMsg() hides messages of the field. So maybe you could implement the requirements like that.

But if you insist on crafting the custom widget, the key is to set up an even listener that is called by the system whenever a field on the form changes. You need to add something like this to the Client controller:

 

api.controller = function ($scope) {
	var c = this;

	$scope.page.g_form.$private.events.on('change', g_formOnChange);

	function g_formOnChange (fieldName, initialValue, currentValue) {
		console.log('g_formOnChange', arguments);
	}
}

 

If you do that, whenever someone changes a field, function g_formOnChange will be executed with those 3 parameters. You could extent the Client controller something like this:

- add a property to the scope that indicates whether to display the blue message or not:

 

api.controller = function ($scope) {
	var c = this;

	$scope.displayMessage = false;
	$scope.page.g_form.$private.events.on('change', g_formOnChange);

	function g_formOnChange (fieldName, initialValue, currentValue) {
		console.log('g_formOnChange', arguments);
	}
}

 

Add a function that handles changes to the choice field (updates the scope's property that governs message visibility as needed) and call it when necessary:

 

api.controller = function ($scope) {
	var c = this;

	$scope.displayMessage = false;
	$scope.page.g_form.$private.events.on('change', g_formOnChange);

	function g_formOnChange (fieldName, initialValue, currentValue) {
		switch (fieldName) {
			case '<name of the "Is this permitted..." variable/question>': {
				isThisPermittedOnChange(initialValue, currentValue);
				break;
			}
		}
	}

	function isThisPermittedOnChange (initialValue, currentValue) {
		if (currentValue == 'No')
			$scope.displayMessage = true;
		else
			$scope.displayMessage = false;
	}
}

 

As a final step, modify the Body HTML template so as to link the visibility of the message to the newly defined property of the scope - add directive ngIf to the div container (ngShow would also be an option, but ngIf is slightly safer):

 

<div class="no-permit-message-wrapper justify-between flex" ng-if="displayMessage">
	<!-- your widget template -->
	<div class="message-authorization" tabindex="0" aria-label="Attach letter of authorization">
		${Please attach your organization's letter of authorization at the bottom of this form.}
	</div>
	<!-- ng-click="close()" -->
	<i class="fa fa-times pull-right wrapper-xs ng-scope closeX" ng-click="this.parentElement.style.display='none'" aria-label="Close information" tabindex="0" role="button"></i>
</div>

 

As a final touch you could also modify the closer X to update the same scope property on one hand by defining a new function on the scope:

 

api.controller = function ($scope) {
	var c = this;

	$scope.displayMessage = false;
	$scope.hideMessage = hideMessage;
	$scope.page.g_form.$private.events.on('change', g_formOnChange);

	function g_formOnChange (fieldName, initialValue, currentValue) {
		switch (fieldName) {
			case '<name of the "Is this permitted..." variable/question>': {
				isThisPermittedOnChange(initialValue, currentValue);
				break;
			}
		}
	}

	function isThisPermittedOnChange (initialValue, currentValue) {
		if (currentValue == 'No')
			$scope.displayMessage = true;
		else
			$scope.displayMessage = false;
	}

	function hideMessage () {
		$scope.displayMessage = false;
	}
}

 

on the other by updating the Body HTML template to make the closer X call that function:

 

<div class="no-permit-message-wrapper justify-between flex" ng-if="displayMessage">
	<!-- your widget template -->
	<div class="message-authorization" tabindex="0" aria-label="Attach letter of authorization">
		${Please attach your organization's letter of authorization at the bottom of this form.}
	</div>
	<!-- ng-click="close()" -->
	<i class="fa fa-times pull-right wrapper-xs ng-scope closeX" ng-click="hideMessage()" aria-label="Close information" tabindex="0" role="button"></i>
</div>

 

While at it, you could also make other text translatable in the Body HTML template, not just the main message:

 

<div class="no-permit-message-wrapper justify-between flex" ng-if="displayMessage">
	<!-- your widget template -->
	<div class="message-authorization" tabindex="0" aria-label="${Attach letter of authorization}">
		${Please attach your organization's letter of authorization at the bottom of this form.}
	</div>
	<!-- ng-click="close()" -->
	<i class="fa fa-times pull-right wrapper-xs ng-scope closeX" ng-click="hideMessage()" aria-label="${Close information}" tabindex="0" role="button"></i>
</div>

 

I have not tested this, so hopefully I haven't misspelled anything.

View solution in original post

6 REPLIES 6

-O-
Kilo Patron
Kilo Patron

Is this a variable in a Catalog Item or Record Producer of type Macro (or Custom in newer versions of SN)?

It is a variable of type Macro in a Record Producer. The business team does not need that Macro in the record, so there is no UI Macro and it is just configured to be a widget.

1st of all I should mention that there is such a thing as variable message. One calls g_form.showFieldMsg() and a message just like you show in the screen-shot is added below the field. Its analogous g_form.hideFieldMsg() hides messages of the field. So maybe you could implement the requirements like that.

But if you insist on crafting the custom widget, the key is to set up an even listener that is called by the system whenever a field on the form changes. You need to add something like this to the Client controller:

 

api.controller = function ($scope) {
	var c = this;

	$scope.page.g_form.$private.events.on('change', g_formOnChange);

	function g_formOnChange (fieldName, initialValue, currentValue) {
		console.log('g_formOnChange', arguments);
	}
}

 

If you do that, whenever someone changes a field, function g_formOnChange will be executed with those 3 parameters. You could extent the Client controller something like this:

- add a property to the scope that indicates whether to display the blue message or not:

 

api.controller = function ($scope) {
	var c = this;

	$scope.displayMessage = false;
	$scope.page.g_form.$private.events.on('change', g_formOnChange);

	function g_formOnChange (fieldName, initialValue, currentValue) {
		console.log('g_formOnChange', arguments);
	}
}

 

Add a function that handles changes to the choice field (updates the scope's property that governs message visibility as needed) and call it when necessary:

 

api.controller = function ($scope) {
	var c = this;

	$scope.displayMessage = false;
	$scope.page.g_form.$private.events.on('change', g_formOnChange);

	function g_formOnChange (fieldName, initialValue, currentValue) {
		switch (fieldName) {
			case '<name of the "Is this permitted..." variable/question>': {
				isThisPermittedOnChange(initialValue, currentValue);
				break;
			}
		}
	}

	function isThisPermittedOnChange (initialValue, currentValue) {
		if (currentValue == 'No')
			$scope.displayMessage = true;
		else
			$scope.displayMessage = false;
	}
}

 

As a final step, modify the Body HTML template so as to link the visibility of the message to the newly defined property of the scope - add directive ngIf to the div container (ngShow would also be an option, but ngIf is slightly safer):

 

<div class="no-permit-message-wrapper justify-between flex" ng-if="displayMessage">
	<!-- your widget template -->
	<div class="message-authorization" tabindex="0" aria-label="Attach letter of authorization">
		${Please attach your organization's letter of authorization at the bottom of this form.}
	</div>
	<!-- ng-click="close()" -->
	<i class="fa fa-times pull-right wrapper-xs ng-scope closeX" ng-click="this.parentElement.style.display='none'" aria-label="Close information" tabindex="0" role="button"></i>
</div>

 

As a final touch you could also modify the closer X to update the same scope property on one hand by defining a new function on the scope:

 

api.controller = function ($scope) {
	var c = this;

	$scope.displayMessage = false;
	$scope.hideMessage = hideMessage;
	$scope.page.g_form.$private.events.on('change', g_formOnChange);

	function g_formOnChange (fieldName, initialValue, currentValue) {
		switch (fieldName) {
			case '<name of the "Is this permitted..." variable/question>': {
				isThisPermittedOnChange(initialValue, currentValue);
				break;
			}
		}
	}

	function isThisPermittedOnChange (initialValue, currentValue) {
		if (currentValue == 'No')
			$scope.displayMessage = true;
		else
			$scope.displayMessage = false;
	}

	function hideMessage () {
		$scope.displayMessage = false;
	}
}

 

on the other by updating the Body HTML template to make the closer X call that function:

 

<div class="no-permit-message-wrapper justify-between flex" ng-if="displayMessage">
	<!-- your widget template -->
	<div class="message-authorization" tabindex="0" aria-label="Attach letter of authorization">
		${Please attach your organization's letter of authorization at the bottom of this form.}
	</div>
	<!-- ng-click="close()" -->
	<i class="fa fa-times pull-right wrapper-xs ng-scope closeX" ng-click="hideMessage()" aria-label="Close information" tabindex="0" role="button"></i>
</div>

 

While at it, you could also make other text translatable in the Body HTML template, not just the main message:

 

<div class="no-permit-message-wrapper justify-between flex" ng-if="displayMessage">
	<!-- your widget template -->
	<div class="message-authorization" tabindex="0" aria-label="${Attach letter of authorization}">
		${Please attach your organization's letter of authorization at the bottom of this form.}
	</div>
	<!-- ng-click="close()" -->
	<i class="fa fa-times pull-right wrapper-xs ng-scope closeX" ng-click="hideMessage()" aria-label="${Close information}" tabindex="0" role="button"></i>
</div>

 

I have not tested this, so hopefully I haven't misspelled anything.

I don't insist to use a custom Macro variable but the business team (users, managers, and UX/UI team) insisted to implement that way. My team and team manager could not convince them to change.

 

Thank you for the feedback and suggestions. I will give it a try.