Making attachment mandatory from client script service portal catalog

lauri457
Giga Sage

Ever had the requirement of conditionally making the attachments mandatory on the portal catalog item page?  There is the attachment variable but I've seen business reject it as an option more often than not and there are multiple posts on the community to query the dom directly for the attachment list e.g:

this.document.getElementsByClassName('get-attachment').length

The attachment variable does not allow multiple attachments and the attachment is not attached to the RITM. The dom query makes for bad ux as it alone will not display the asterisk similar to the Mandatory Attachment checkbox.

lauri457_0-1765930036799.png

I wanted to achieve above oob functionality but control it without making changes to any of the related widgets so I had a look at how the widget works and turns out it can be achieved quite easily.

 

Looking at the SC Catalog Item widget

In the catalog item widget clicking the order button calls a function from the scope called triggerOnSubmit which does the submit with GlideForm, but the function itself does not handle much else. From the asterisk elements we can see that they are controlled by the mandatory_attachment boolean returned from $sp.getCatalogItem() which is controlled by the checkbox in the portal settings

<span class="fa fa-asterisk mandatory" ng-if="data.sc_cat_item.mandatory_attachment" ...>

There is no relevant reference to the mandatory_attachment property in the widget itself so looking at where the data.sc_cat_item object is passed as an argument we can see that it is bound into the item attribute of the spCatItem directive.

<sp-cat-item item="::data.sc_cat_item"></sp-cat-item>

Inside the link function of the spCatItem directive the item is stored in the scope and how the functionality works can be seen in the event handler that is registered to the GlideForm submit event. If there are no attachments an errormessage is displayed and the submission is canceled by returning false from the handler.

g_form.$private.events.on('onSubmit', function () {
// ... removed script
	if ($scope.item.mandatory_attachment) {
		if (!$scope.item.attachment_submitted && !($scope.attachments && $scope.attachments.length > 0)) {
			var _response = spUtil.addErrorMessage(scMandatoryAttachMessage);
			var handledMessage = _response !== false;
			if (!handledMessage)
				spModal.alert(scMandatoryAttachMessage);
			return false;
		}
	}
	return true;
});

 

Solution

This all means that to trigger mandatory attachments we should only need to set 

$scope.data.sc_cat_item.mandatory_attachment to true/false in the catalog item widget scope. I did not want to edit the widget itself so to achieve this from a catalog client script we can use angular.element() which returns a jquery wrapper and angularjs adds a method to the returned object that allows us to get the scope and manipulate it, we just need to allow access to the window object from the script by unticking isolate script. We can use the first element with id sc_cat_item inside the directive to access scope
this.angular.element("#sc_cat_item").scope().data.sc_cat_item.mandatory_attachment
 
To use this in a client script we just need to change the value in the scope and then make sure the view is updated afterwards to show the mandatory asterisk. Angularjs won't automatically trigger a digest cycle if you update a scope value from the outside so we can safely cause a digest to happen by scheduling the function with $scope.$evalAsync. You could also call $apply for the same effect. 
//isolate script should be false
function onChange(control, oldValue, newValue, isLoading) {
	if (isLoading) {
		return;
	}
	const attachMandatoryGa = new GlideAjax("CatalogSI");
	attachMandatoryGa.addParam("sysparm_name", "mandatoryAttachment");
	attachMandatoryGa.addParam("sysparm_value", newValue);
	attachMandatoryGa.getXMLAnswer((answer) => {
		const catitemScope = this.angular.element("#sc_cat_item").scope();
		catitemScope.$evalAsync(() => {
			catitemScope.data.sc_cat_item.mandatory_attachment = answer === "true";
		});
	})
}
 

Conclusion

The solution should be fairly robust as the $scope.data.sc_cat_item does not get updated at all once it is set so changes to the object will persist. This allows us to achieve better ux matching a common business requirement.

5 REPLIES 5

Matthew Green2
Mega Guru

Very interesting, Thanks for the perspective!

Ankur Bawiskar
Tera Patron
Tera Patron

@lauri457 

thanks for sharing

when I tried this it gave me browser console error for catalog item but it did give attachment not added message

could you please check this from your side? Isolate script = False

AnkurBawiskar_0-1765941377443.png

 

Regards,
Ankur
Certified Technical Architect  ||  9x ServiceNow MVP  ||  ServiceNow Community Leader

Perhaps something in the client script/glideajax call, could you paste the error from your browser console?

OlaN
Tera Sage
Tera Sage

Hi,

Interesting solution.

But if the use case is only to make an attachment mandatory based on some other conditions on the form, I would rather add an attachment variable, and make it mandatory using Catalog UI policy or Catalog Client Script, just like any other variable.