How to dynamically update content block based on selected option using Jelly?

zhen
Kilo Explorer

Hi there!

I am trying to write a script that will update the content in a dynamic content block based on the option selected.  The option should query back to a table and look for results related to the table.  I am getting stuck on the onchange function...

 

<g:evaluate var="jvar_tasktypes" object="true" jelly="true">
var tables = 'u_billing_information';
var obj=[];
var gr= new GlideAggregate('u_billing_information');
  gr.groupBy('u_business_unit');
  gr.query();
  while(gr.next()){
    obj.push([gr.getValue('u_business_unit'),gr.getDisplayValue('u_business_unit')]);
  }
  obj;
</g:evaluate>

 <select id='filter_task_type' class='select2-search' onchange='filterTaskType()'>
        <option value="">All</option>
        <j:forEach var="jvar_tasktype" items="${jvar_tasktypes}">
            <option value="${jvar_tasktype[0]}">${jvar_tasktype[1]}</option>       
        </j:forEach>
    </select>  
<script>
function filterTaskType(){
 var taskType = $j('#filter_task_type').val();
<g:evaluate var="jvar_gr" object="true">
   var gr = new GlideRecord("u_billing_information");
   gr.addQuery('u_business_unit', taskType);
   gr.query();
   gr;
</g:evaluate>
<j:if test="${jvar_gr.next()}">
   hello I see you
</j:if>

}

</script>

 

 

1 ACCEPTED SOLUTION

ChrisBurks
Mega Sage

Hi Zhen,

 

There two issues here:

  1. Jelly processes server side before the page is rendered. Therefore making a change to the select drop down won't trigger anything unless there is some sort of page refresh and "caching" of the data to pass (many times done via url params in ServiceNow). 
  2. Even if it were able to pass the data directly, the filterTaskType function still wouldn't display because there is no return of the data because the j:if statement is still inside the function. 

There are a few ways to resolve this. One popular way is to use a combination of a Client Callable Script Include and a GlideAjax call to that script include. Important Note: Script Include must be Client Callable. That's accomplished by checking that field on the Script Include form when creating it.

 

Example:

Note: This is based on your posted script. However, some things are modified because I don't have the same environment, such as tables, as you. The concepts are still the same.

Client Callable Script Include:

var BillingInfoUtil = Class.create();
BillingInfoUtil.prototype = Object.extendsObject(AbstractAjaxProcessor, {
	
	getBillingInfo: function(){
		var taskType = this.getParameter('sysparm_task_type');
		gs.log("Task Type: " + taskType, "Select EX")
		var gr = new GlideRecord("incident");
		//this gliderecord setup is assuming you're expecting one record exists that meets the
		//criteria. Adjust if necessary
		gr.get(taskType);
		var fields = ['number','short_description','state']
        var bu = {};
		if (gr.getUniqueValue()){
			//this is an assumption. You'll need to determine what your needs are;
			fields.forEach(function(field){
				bu[field] = gr.getValue(field)
			})
			
		}
		
		return JSON.stringify([bu]);
		
	},

    type: 'BillingInfoUtil'
});

 

Dynamic Content Block using GlideAjax:

<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
	<g:evaluate var="jvar_tasktypes" object="true" jelly="true">
		// Changed to GlideRecord for demo purpose as I don't have the same tables or setup as you
		var objs = [];
		var gr= new GlideRecord('incident');
		gr.groupBy('number');
		gr.setLimit(5);
		gr.query();
		while(gr.next()){
		objs.push({'sys_id': gr.getValue('sys_id'),'number':gr.getDisplayValue('number')});
		}
		objs;
	</g:evaluate>
	<div class="container">
		<div class="col-md-3">
			<div class="panel-body">
				<h4>Select an Incident</h4>
				<select id='filter_task_type' class='select2-search form-control'  onchange='filterTaskType()'>
					<option value="">All</option>
					<j:forEach var="jvar_tasktype" items="${jvar_tasktypes}">
						<option value="${jvar_tasktype.sys_id}">${jvar_tasktype.number}</option>       
					</j:forEach>
				</select>  
			</div>
		</div>
		<div class="col-md-9">
			<div class="panel-body">
				<h3>Incident Details</h3>
				<div id="incDetails">
					<div class="list-group"></div>
				</div>
			</div>
		</div>
	</div>
	<script>
		function filterTaskType(){
			var container = document.querySelector('.container');
			var taskType = container.querySelector('#filter_task_type').value;
		    var incDetails = container.querySelector('.list-group');
			
		  //Here is where the GlideAjax comes in instead of g:evaluate
		    var ga = new GlideAjax('BillingInfoUtil');
			ga.addParam('sysparm_name', 'getBillingInfo');
			ga.addParam('sysparm_task_type', taskType);
			ga.getXML(callback);
			
			function callback(response) {
			  var result = response.responseXML.documentElement.getAttribute("answer");
		      var details = JSON.parse(result);
		      var keys = Object.keys(details[0]);
		      var html = [];
		      keys.forEach(function(key){
				 var label = key.toUpperCase().replace("_", " ");
		         var text = details[0][key];
		         html.push('<div class="list-group-item"><h4 class="list-group-item-heading">'+ label + '</h4>');
				 html.push('<p class="list-group-item-text">' + text + '</p></div>');
			  });
		       incDetails.innerHTML = html.join("");
			  
			}

		}

	</script>

</j:jelly>

 

Screencast of result:

View solution in original post

11 REPLIES 11

ChrisBurks
Mega Sage

Hi Zhen,

 

There two issues here:

  1. Jelly processes server side before the page is rendered. Therefore making a change to the select drop down won't trigger anything unless there is some sort of page refresh and "caching" of the data to pass (many times done via url params in ServiceNow). 
  2. Even if it were able to pass the data directly, the filterTaskType function still wouldn't display because there is no return of the data because the j:if statement is still inside the function. 

There are a few ways to resolve this. One popular way is to use a combination of a Client Callable Script Include and a GlideAjax call to that script include. Important Note: Script Include must be Client Callable. That's accomplished by checking that field on the Script Include form when creating it.

 

Example:

Note: This is based on your posted script. However, some things are modified because I don't have the same environment, such as tables, as you. The concepts are still the same.

Client Callable Script Include:

var BillingInfoUtil = Class.create();
BillingInfoUtil.prototype = Object.extendsObject(AbstractAjaxProcessor, {
	
	getBillingInfo: function(){
		var taskType = this.getParameter('sysparm_task_type');
		gs.log("Task Type: " + taskType, "Select EX")
		var gr = new GlideRecord("incident");
		//this gliderecord setup is assuming you're expecting one record exists that meets the
		//criteria. Adjust if necessary
		gr.get(taskType);
		var fields = ['number','short_description','state']
        var bu = {};
		if (gr.getUniqueValue()){
			//this is an assumption. You'll need to determine what your needs are;
			fields.forEach(function(field){
				bu[field] = gr.getValue(field)
			})
			
		}
		
		return JSON.stringify([bu]);
		
	},

    type: 'BillingInfoUtil'
});

 

Dynamic Content Block using GlideAjax:

<?xml version="1.0" encoding="utf-8" ?>
<j:jelly trim="false" xmlns:j="jelly:core" xmlns:g="glide" xmlns:j2="null" xmlns:g2="null">
	<g:evaluate var="jvar_tasktypes" object="true" jelly="true">
		// Changed to GlideRecord for demo purpose as I don't have the same tables or setup as you
		var objs = [];
		var gr= new GlideRecord('incident');
		gr.groupBy('number');
		gr.setLimit(5);
		gr.query();
		while(gr.next()){
		objs.push({'sys_id': gr.getValue('sys_id'),'number':gr.getDisplayValue('number')});
		}
		objs;
	</g:evaluate>
	<div class="container">
		<div class="col-md-3">
			<div class="panel-body">
				<h4>Select an Incident</h4>
				<select id='filter_task_type' class='select2-search form-control'  onchange='filterTaskType()'>
					<option value="">All</option>
					<j:forEach var="jvar_tasktype" items="${jvar_tasktypes}">
						<option value="${jvar_tasktype.sys_id}">${jvar_tasktype.number}</option>       
					</j:forEach>
				</select>  
			</div>
		</div>
		<div class="col-md-9">
			<div class="panel-body">
				<h3>Incident Details</h3>
				<div id="incDetails">
					<div class="list-group"></div>
				</div>
			</div>
		</div>
	</div>
	<script>
		function filterTaskType(){
			var container = document.querySelector('.container');
			var taskType = container.querySelector('#filter_task_type').value;
		    var incDetails = container.querySelector('.list-group');
			
		  //Here is where the GlideAjax comes in instead of g:evaluate
		    var ga = new GlideAjax('BillingInfoUtil');
			ga.addParam('sysparm_name', 'getBillingInfo');
			ga.addParam('sysparm_task_type', taskType);
			ga.getXML(callback);
			
			function callback(response) {
			  var result = response.responseXML.documentElement.getAttribute("answer");
		      var details = JSON.parse(result);
		      var keys = Object.keys(details[0]);
		      var html = [];
		      keys.forEach(function(key){
				 var label = key.toUpperCase().replace("_", " ");
		         var text = details[0][key];
		         html.push('<div class="list-group-item"><h4 class="list-group-item-heading">'+ label + '</h4>');
				 html.push('<p class="list-group-item-text">' + text + '</p></div>');
			  });
		       incDetails.innerHTML = html.join("");
			  
			}

		}

	</script>

</j:jelly>

 

Screencast of result:

Thank you! You have saved my life.

find_real_file.png

This is amazing. Any idea how I could get this to work based off of user's preference using an interactive filter? I would like to use this on a dashboard with other reports, but I cannot seem to get this to respond to the other filter, nor can I make any of my other reports follow this filter.

Thanks!