Shravani Yadava
Mega Explorer

In this article I want to share one of the frequent and complex requirement we usually get in Service Catalog forms(Service Portal).

which is Dynamic HTML tables on Catalog forms with Add and Remove rows.

 

Requirement: 

We need to display a table (ex: multi row variable set ) on a catalog form and when user fills a variable (select item) and provides its quantity we need to calculate the price and update the total value as show in below screenshot.

find_real_file.png

 

For the above scenario we can 

Possibility 1 (Using Custom widget):

1.For Service Portal we can, Develop custom widget and embed it on the catalog form with all the functionalities that are available with Multi row variable set.

2. For ServiceNow Native UI we need to create a macro to re render all the information provided by the user

3. It will take certain time to develop and we need to handle lot of things. But we will have flexibility in doing multiple things as it is custom built.

 

Possibility 2 (Using Multi row variable set):

For mentioned use case, Using multi row variable set might not be that fruitful due to below issues

1. It wont support capability of accessing other form variables and we cant display separate row with Total as shown in above screenshot

2. When user delete rows by either clicking Remove All or individual remove row buttons,we cannot track those deletes and update our calculations accordingly.

Refer: ServiceNow Docs

 

My Approach

I tried to figure out an approach which uses the combination of Multi row variable set and a custom widget to achieve mentioned functionality without complex logic or any DOM manipulations.

 

Solution:

1. Create a multi row variable set with all the necessary variables, In our case Select Item, quantity and price.

2. Create widget and add it to the catalog item as a variable of type Macro

3. In the client script of the widget, try to watch for the changes in the multi row variable set using angular js component ($scope.$watch) as shown below

$scope.$watch(function(){
/* item_prices - Internal Name of multi row variable set */
return $scope.page.g_form.getValue('item_prices');
},function(val){
// Business Logic	
}); 
	

4. When there are changes , We can calculate the total price by retrieving the value of entire multi row variable set.

example:

[
   {
      "quantity":"10",
      "select_item":"laptop",
      "price":"10000"
   },
   {
      "quantity":"30",
      "select_item":"keyboard",
      "price":"600"
   },
   {
      "quantity":"2",
      "select_item":"mouse",
      "price":"20"
   }
]

5. We can populate the calculated price in any variable out side multi row variable set 

                                             OR 

We can bind the value to any html element on the widget.

 

Refer to end result in below screenshot.

find_real_file.png 

Widget Reference:

Body HTML:

<div>

  <div ng-if="c.totalPrice" class="total_class pull-right"> 
    <div class="total_text"> Total: </div>
    <div> <span class="badge badge-secondary total_value">{{c.totalPrice }} </span> </div>
  
  </div> 
  
</div>

 

Client Script:

api.controller=function($scope) {
  /* widget controller */
  var c = this;
	
	
	c.totalPrice ='';
	
	$scope.$watch(function(){
		return $scope.page.g_form.getValue('item_prices');
	},function(val){
		
		c.totalPrice ='';
		$scope.page.g_form.setValue('total_price','');
		
		if(val !=''){
			
			var obj = JSON.parse(val);
			var tPrice=0;
			
			for(var i in obj)
				tPrice =parseInt(tPrice)+parseInt(obj[i].price);
			
			
			
			if(tPrice!='' && tPrice>0){
				c.totalPrice =tPrice;
				$scope.page.g_form.setValue('total_price',tPrice);
			}
			
			
			
		}
		
		
	}); 
	
	
};

 

Attached working demo for reference.

find_real_file.png

 

Comments
Ramiro Rincon B
Tera Contributor

Hi Shravani,

Thank you very much for this post!

But for some reason, I can't make it work for my needs (I'm in New York version). When I pasted the code you shared for the client script side on the Widget, SN throws me an error message:

find_real_file.png

Any idea how can I fix it?

Thanks in advance!

Shravani Yadava
Mega Explorer

hi @Ramiro Rincon Barraza,

 

Sorry, I missed you comment. It didn't work because of the client controller function call format. "api.controller" pattern is used in Paris. Please try to change the code as below.

 

function() {
  /* widget controller */
  var c = this;
 
	
	
	c.totalPrice ='';
	
	$scope.$watch(function(){
		return $scope.page.g_form.getValue('item_prices');
	},function(val){
		
		c.totalPrice ='';
		$scope.page.g_form.setValue('total_price','');
		
		if(val !=''){
			
			var obj = JSON.parse(val);
			var tPrice=0;
			
			for(var i in obj)
				tPrice =parseInt(tPrice)+parseInt(obj[i].price);
			
			
			
			if(tPrice!='' && tPrice>0){
				c.totalPrice =tPrice;
				$scope.page.g_form.setValue('total_price',tPrice);
			}
			
			
			
		}
		
		
	});
  
  
}

 

Let me know if you are still facing any issues.

raarndt
Tera Contributor

This is awesome, how would you make it work, if you need it to work the other way too. Not only the portal side?

Version history
Last update:
‎10-02-2020 04:11 AM
Updated by: