The Zurich release has arrived! Interested in new features and functionalities? Click here for more

Mike Patel
Tera Sage

I saw few questions about on how to show end user their tickets that are INC and REQ on same widget so I decided to write this article.

1. Create new widget called Name: My Ticket and ID: my-tickets

HTML:

<sp-panel>
  <uib-tabset>
    <uib-tab index="0" heading="Active Requests" uib-tooltip="View all active requests that you opened or that were opened on your behalf."><sp-widget widget="data.all_active"></sp-widget></uib-tab>
    <uib-tab index="1" heading="Closed Requests" uib-tooltip="View all closed requests that you opened or that were opened on your behalf."><sp-widget widget="data.all_closed"></sp-widget></uib-tab>
  </uib-tabset>
</sp-panel>

Server: (Change filter as you like)

(function(){
	
	// Setup data tables
	var activeFilter = 'sys_class_name=incident^state=1^ORstate=2^ORstate=4^ref_incident.caller_idDYNAMIC90d1921e5f510100a9ad2572f2b477fe^ORopened_byDYNAMIC90d1921e5f510100a9ad2572f2b477fe^NQactive=true^sys_class_name=sc_request^ref_sc_request.requested_forDYNAMIC90d1921e5f510100a9ad2572f2b477fe^ORopened_byDYNAMIC90d1921e5f510100a9ad2572f2b477fe';
	var taskQueryFilter = 'short_descriptionLIKE{q}^ORnumberLIKE{q}^ORrequested_for.nameLIKE{q}^ORu_user.nameLIKE{q}^ORref_incident.caller_id.nameLIKE{q}^ORdescriptionLIKE{q}';
	var all_active = {
		"table": "task",
		"view": "mobile",
		"fields": "number,short_description,sys_created_on,sys_updated_on",
		"show_keywords": true,
		"show_breadcrumbs": false,
		"o": "sys_created_on", // order_by
		"filter": activeFilter,
		"storef": activeFilter,
		"query_filter": taskQueryFilter
	};
	
	var closedFilter = 'active=false^sys_class_name=incident^ORsys_class_name=sc_request';
	var all_closed = {
		"table": "task",
		"view": "mobile",
		"fields": "number,short_description,sys_created_on,sys_updated_on",
		"show_keywords": true,
		"show_breadcrumbs": false,
		"o": "sys_created_on", // order_by
		"filter": closedFilter,
		"storef": closedFilter,
		"query_filter": taskQueryFilter
	};
	
	data.all_active = $sp.getWidget("my_widget_data_table", all_active);
	data.all_closed = $sp.getWidget("my_widget_data_table", all_closed);
	
	// Look for any parameters for setting the active tab
	data.tabName = $sp.getParameter('tab_focus') || 'all_active';
	
})();

Client:

function ($scope, $location, $rootScope, $uibModal, spUtil, $http) {
	var c = this;
	this.filterText = "";
	this.showFilter = false;

	$scope.$on('data_table.click', function(event, mass) {
		event.stopPropagation();
		event.preventDefault();		
		$location.search('id', 'ticket');
		$location.search('sys_id', mass.sys_id);
		$location.search('table', mass.table);	
	});
}

2. Create new widget called Name: My Data Table and ID: my_widget_data_table 

HTML:

<div>
  <div class="panel panel-{{options.color}} b">
    <div class="panel-heading form-inline" ng-hide="options.hide_header">
      <span class="dropdown m-r-xs">
        <span class="dropdown-toggle glyphicon glyphicon-menu-hamburger" style="line-height: 1.4em" id="optionsMenu" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true"></span>
        <ul class="dropdown-menu" aria-labelledby="optionsMenu">
          <li ng-repeat="t in ::exportTypes">
            <a ng-href="/{{data.table}}_list.do?{{::t.value}}&sysparm_query={{data.filter}}&sysparm_view={{data.view}}" target="_new">${Export as} {{::t.label}}</a>
          </li>
        </ul>
      </span>
      <span class="panel-title"><i ng-if="options.glyph" class="fa fa-{{options.glyph}} m-r"></i>{{data.title || data.table_plural}}</span>

      <button name="new" type="button" class="btn btn-primary btn-sm m-l-xs" ng-click="newRecord()" ng-if="options.show_new && data.canCreate && !data.newButtonUnsupported">${New}</button>
      <div class="pull-right" ng-if="options.show_keywords">
        <form ng-submit="setSearch(true)">
          <div class="input-group">
            <input type="text" name="datatable-search" ng-model="data.keywords" class="form-control" placeholder="${Search}">
            <span class="input-group-btn">
              <button name="search" class="btn btn-default" type="submit"><span class="glyphicon glyphicon-search"></span></button>
            </span>
          </div>
          <button name="new" type="button" class="btn btn-primary" ng-click="clearSearch()">${Clear Search}</button>
        </form>
      </div>
      <div class="clearfix"></div>
    </div>
    <!-- body -->
    <div class="panel-body">
      <div ng-if="options.show_breadcrumbs && data.filter" class="filter-breadcrumbs">
        <sp-widget widget="data.filterBreadcrumbs"></sp-widget>
      </div>
      <div class="alert alert-info" ng-if="!data.list.length && !data.num_pages && !data.invalid_table && !loadingData">
        ${No records in {{data.table_label}} <span ng-if="data.filter">using that filter</span>}
      </div>
      <div class="alert alert-info" ng-if="loadingData">
        <fa name="spinner" spin="true"></fa> ${Loading data}...
      </div>
      <table class="table table-striped table-responsive" ng-if="data.list.length">
        <thead>
          <tr>
            <th ng-repeat="field in data.fields_array track by $index" ng-click="setOrderBy(field)">
              <div class="th-title">{{data.column_labels[field]}}</div>
              <i class="fa" ng-if="field == data.o" ng-class="{'asc': 'fa-chevron-up', 'desc': 'fa-chevron-down'}[data.d]"></i>
            </th>
          </tr>
        </thead>
        <tbody>
          <tr ng-repeat="item in data.list track by item.sys_id">
            <td class="pointer" ng-class="{selected: item.selected}" ng-click="go(data.table, item)" ng-repeat="field in data.fields_array" data-field="{{field}}" data-th="{{data.column_labels[field]}}">{{item[field].display_value}}</td>
          </tr>
        </tbody>
      </table>
      <div ng-class="{'pruned-msg-filter-pad': (!options.show_breadcrumbs || !data.filter) && !data.list.length}" class="pruned-msg" ng-if="rowsWerePruned()">
        <span ng-if="rowsPruned == 1">${{{rowsPruned}} row removed by security constraints}</span>
        <span ng-if="rowsPruned > 1">${{{rowsPruned}} rows removed by security constraints}</span>
      </div>
    </div>
    <!-- footer -->
    <div class="panel-footer" ng-hide="options.hide_footer" ng-if="data.row_count">
      <div class="btn-toolbar m-r pull-left">
        <div class="btn-group">
          <a ng-disabled="data.p == 1" href="javascript:void(0)" ng-click="setPageNum(data.p - 1)" class="btn btn-default"><i class="fa fa-chevron-left"></i></a>
        </div>
        <div ng-if="data.num_pages > 1 && data.num_pages < 20" class="btn-group">
          <a ng-repeat="i in getNumber(data.num_pages) track by $index" ng-click="setPageNum($index + 1)" href="javascript:void(0)" ng-class="{active: ($index + 1) == data.p}" type="button" class="btn btn-default">{{$index + 1}}</a>
        </div>
        <div class="btn-group">
          <a ng-disabled="data.p == data.num_pages" href="javascript:void(0)" ng-click="setPageNum(data.p + 1)" class="btn btn-default"><i class="fa fa-chevron-right"></i></a>
        </div>
      </div>
      <div class="m-t-xs panel-title">${Rows {{data.window_start + 1}} - {{ mathMin(data.window_end,data.row_count) }} of {{data.row_count}}}</div>

      <span class="clearfix"></span>
    </div>
  </div>
</div>

CSS:

.panel-heading {
  padding-left: 8px;
}

thead {
  border-bottom: 1px solid #ddd;
}

table {
  margin-bottom: 0;
}

.table > thead > tr > th {
  border: 1px solid #ddd;
  cursor: pointer;
  vertical-align: middle;
  
  &:first-child {
    border-left: none;
  }
  
  &:last-child {
    border-right: none;
  }
}

th i {
  display: inline-block;
  margin-left: 5px;
  color: #A0A0A0;
}

th .disabled{
  color:#ddd;
}

.th-title {
  display: inline-block;
  color: #428bca;
}

.panel-body {
  overflow: auto;
  padding: 0px;
}


.selected {
  color: #fff;
  background-color: #909090;
  border-color: 1px solid #fff;
}

tbody tr:last-child {
  border-bottom: none;
}

.pruned-msg {
  padding-bottom: 10px; 
  padding-left: 4px; 
  text-align: center;
}

.pruned-msg-filter-pad {
  padding-top:8px; 
}

.filter-breadcrumbs {
  border-bottom: 1px solid #ddd;
  padding-top: 3px;
}

 

Server:

(function() {
	if (!input) // asynch load list
		return;

	data.title = options.title || input.title;

	/*
	 * data.table = the table
	 * data.p = the current page starting at 1
	 * data.o = the order by column
	 * data.d = the order by direction
	 * data.keywords = the keyword search term
	 * data.list = the table data as an array
	 * data.invalid_table = true if table is invalid or if data was not succesfully fetched
	 * data.table_label = the table's display name. e.g. Incident
	 * data.table_plural = the table's plural display name. e.g. Incidents
	 * data.fields = a comma delimited list of field names to show in the data table
	 * data.column_labels = a map of field name -> display name
	 * data.window_size = the number of rows to show
	 * data.filter = the encoded query
	 * data.query_filter = encoded query template to use when searching this table
	 */
	// copy to data[name] from input[name] || option[name]
	optCopy(['table', 'p', 'o', 'd', 'filter', 'filterACLs', 'fields', 'keywords', 'view']);
	optCopy(['relationship_id', 'apply_to', 'apply_to_sys_id']);
	optCopy(['query_filter']); // NYT Custom: Allow custom query
	if (!data.table) {
		data.invalid_table = true;
		data.table_label = "";
		return;
	}

	if (!data.fields) {
		if (data.view)
			data.fields = $sp.getListColumns(data.table, data.view);
		else
			data.fields = $sp.getListColumns(data.table);
	}

	data.view = data.view || 'mobile';
	data.table = data.table || $sp.getValue('table');
	data.filter = data.filter || $sp.getValue('filter');
	data.keywords = data.keywords || $sp.getValue('keywords');
	data.textSearch = data.textSearch || ($sp.getValue('text_search') == 'true');
	data.query_filter = data.query_filter || $sp.getValue('keywords') || '';
	data.p = data.p || $sp.getValue('p') || 1;
	data.p = parseInt(data.p);
	data.o = data.o || $sp.getValue('o') || $sp.getValue('order_by');
	data.d = data.d || $sp.getValue('d') || $sp.getValue('order_direction');
	data.window_size = data.window_size || $sp.getValue('maximum_entries') || 20;
	data.page_index = data.p - 1;
	data.show_new = data.show_new || options.show_new;

	/*
	| Custom data property to store if a session is encrypted
	| Used in navigation decisions to redirect or not
	|
	*/
	data.isEncryptedSession = gs.isEdgeEncryptedSession();

	var gr;
	if (gs.getProperty("glide.security.ui.filter") == "true" || GlideTableDescriptor.get(data.table).getED().hasAttribute("glide.security.ui.filter")) {
		gr = new FilteredGlideRecord(data.table);
		gr.applyRowSecurity();
	} else
		gr = new GlideRecordSecure(data.table);
	if (!gr.isValid()) {
		data.invalid_table = true;
		data.table_label = data.table;
		return;
	}

	data.canCreate = gr.canCreate();
	data.newButtonUnsupported = data.table == "sys_attachment";
	data.table_label = gr.getLabel();
	data.table_plural = gr.getPlural();
	if (data.filter) {
		if (data.filterACLs)
			gr = $sp.addQueryString(gr, data.filter);
		else
			gr.addEncodedQuery(data.filter);
	}

	if (data.keywords){
		data.queryString = data.query_filter.replace(/\{q\}/g, data.keywords);
		gr.addEncodedQuery(data.queryString);
	}

	//data.filter = gr.getEncodedQuery();

	if (data.relationship_id) {
		var rel = GlideRelationship.get(data.relationship_id);
		var target = new GlideRecord(data.table);
		var applyTo = new GlideRecord(data.apply_to);
		applyTo.get("sys_id", data.apply_to_sys_id);
		rel.queryWith(applyTo, target); // put the relationship query into target
		gr.addEncodedQuery(target.getEncodedQuery()); // get the query the relationship made for us
	}

	if (data.o){
		if (data.d == "asc")
			gr.orderBy(data.o);
		else
			gr.orderByDesc(data.o);
	}

	data.window_start = data.page_index * data.window_size;
	data.window_end = (data.page_index + 1) * data.window_size;
	gr.chooseWindow(data.window_start, data.window_end);
	gr._query();

	data.row_count = gr.getRowCount();
	data.num_pages = Math.ceil(data.row_count / data.window_size);
	data.column_labels = {};
	data.fields_array = data.fields.split(',');

	// use GlideRecord to get field labels vs. GlideRecordSecure
	var grForLabels = new GlideRecord(data.table);
	for (var i in data.fields_array) {
		var field = data.fields_array[i];
		var ge = grForLabels.getElement(field);
		if (ge == null)
			continue;

		data.column_labels[field] = ge.getLabel();
	}

	data.list = [];
	while (gr._next()) {
		var record = {};
		$sp.getRecordElements(record, gr, data.fields);
		if (gr instanceof FilteredGlideRecord) {
			// FilteredGlideRecord doesn't do field-level
			// security, so take care of that here
			for (var f in data.fields_array) { 
				var fld = data.fields_array[f];
				if (!gr.isValidField(fld))
					continue;

				if (!gr[fld].canRead()) {
					record[fld].value = null;
					record[fld].display_value = null;
				}
			}
		}
		record.sys_id = gr.getValue('sys_id');
		record.table = gr.getValue('sys_class_name');
		data.list.push(record);
	}

	var breadcrumbWidgetParams = { table: data.table, query: data.filter };
	data.filterBreadcrumbs = $sp.getWidget('widget-filter-breadcrumbs', breadcrumbWidgetParams);

	// copy to data from input or options
	function optCopy(names) {
		names.forEach(function(name) {
			data[name] = input[name] || options[name];
		})
	}

})();

Client:

function ($scope, $location, spUtil, amb, $http) {
	var c = this;
	
	/*
	* options:
	* hide_footer (bool) = true to remove the data table footer contents
	* hide_header (bool) = true to remove the data table header contents
	* show_new (bool) = true to show the "New" record button
	* show_keywords (bool) = true to show the keyword search field
	* table (string) = the table name to query
	* filter (string) = the encoded query
	* o (string) = the order by column
	* d (string) = The order by direction: asc or desc
	* p (int) = the page to jump to
	* fields (string) = comma seperated list of fields that become the list columns
	* view (string) = the default view to load for columns, overrides fields
	*/
	
	$scope.clearSearch = function(){
		$scope.data.filter = $scope.data.storef;
		$scope.data.keywords = null;
		$scope.setSearch(true);
	};
	
	$scope.exportTypes = [{label:'PDF', value: 'PDF'}, {label:'Excel', value:'EXCEL'}, {label:'CSV', value:'CSV'}];
	var keys = ['table', 'filter', 'p', 'o', 'd'];

	var eventNames = {
		click: 'data_table.click',
		setFilter: 'data_table.setFilter',
		setKeywords: 'data_table.setKeywords'
	};

	$scope.go = function(table, item) {
		var parms = {};
		parms.table = item.table || table;
		parms.sys_id = item.sys_id;
		parms.record = item;
		parms.isEncryptedSession = c.data.isEncryptedSession;
		$scope.ignoreLocationChange = true;
		for (var x in c.data.list) {
			c.data.list[x].selected = false;
		}
		item.selected = true;
		$scope.$emit(eventNames.click, parms);
	};

	$scope.newRecord = function(){
		var parms = {
			id: 'form',
			table: $scope.data.table,
			sys_id: '-1'
		};
		if ($scope.data.filter != '')
			parms.query = $scope.data.filter;

		$location.search(parms);
	};

	function recoverStateFromUrl() {
		$scope.data.fields = [];
		var s = $location.search();
		for (var x in keys) {
			if (s[keys[x]]) {
				$scope.data[keys[x]] = s[keys[x]];
			}
		}
		$scope.server.update().then(function(data) {
			if (s.sys_id) {
				for (var x in data.list) {
					if (data.list[x].sys_id == s.sys_id) {
						$scope.go(s.table, data.list[x]);
					}
				}
			}
		});
	}

	if ($scope.options.fromUrl) {
		$scope.$on('$locationChangeSuccess', function(e) {
			if ($scope.ignoreLocationChange){
				$scope.ignoreLocationChange = false;
				return;
			}

			// Helps to recover state when using the browser's back button
			recoverStateFromUrl();
		});
	}


	$scope.getNumber = function(num) {
		return new Array(num);
	}

	$scope.mathMin = function(v1,v2) {
		return Math.min(v1,v2);
	}

	function getData(updateUrl) {
		var f = $scope.data;
		spUtil.update($scope).then(function(data) {
			f.view = data.view;
			if ($scope.options.fromUrl && updateUrl)
				setPermalink(f.table, f.filter, f.o, f.d, f.p);

			if ($scope.options.show_breadcrumbs && data.filterBreadcrumbs)
				$scope.$broadcast('widget-filter-breadcrumbs.setBreadcrumbs', data.filterBreadcrumbs.data);

			initRecordWatcher(f.table, f.filter);
		});
	}

	function setPermalink(table, filter, orderBy, orderDirection, page){
		$scope.ignoreLocationChange = true;
		var search = $location.search();
		angular.extend(search, {
			spa: 1,
			table: table,
			filter: filter,
			p: page,
			o: orderBy,
			d: orderDirection
		});
		$location.search(search);
	}

	var watcher;
	function initRecordWatcher(table, filter){
		if (watcher)
			watcher.unsubscribe();

		if (table && filter) {
			var watcherChannel = amb.getChannelRW(table, filter);
			amb.connect();
			watcher = watcherChannel.subscribe(function() {
				spUtil.update($scope)
			});
		}
	}

	$scope.setPageNum = function(num) {
		$scope.data.p = num;
		getData(true);
	}

	$scope.setOrderBy = function(field) {
		var d = "asc";
		if ($scope.data.o == field) {
			if ($scope.data.d == "asc")
				d = "desc";
			else
				d = "asc";
		}
		$scope.data.o = field;
		$scope.data.d = d;
		$scope.setSearch(true);
	}

	$scope.setSearch = function(updateUrl) {
		$scope.data.p = 1;
		getData(updateUrl);
	}

	$scope.$on(eventNames.setFilter, function(e, newFilter){
		$scope.data.filter = newFilter;
		$scope.setSearch(false);
	});

	$scope.$on(eventNames.setKeywords, function(e, keywords){
		$scope.data.keywords = keywords;
		$scope.setSearch(false);
	});

	$scope.$on('widget-filter-breadcrumbs.queryModified', function(e, newFilter){
		$scope.data.filter = newFilter;
		$scope.setSearch(true);
	});

	$scope.rowsWerePruned = function() {
		if (!$scope.data.list)
			return;

		$scope.rowsPruned = $scope.mathMin($scope.data.window_end,$scope.data.row_count) - $scope.data.window_start - $scope.data.list.length;
		return $scope.rowsPruned > 0;
	}

	$scope.showFilter = function() {
		return !$scope.data.list.length && !$scope.data.num_pages && !$scope.data.invalid_table && !$scope.loadingData;
	}

	c.appendQuery = function(query){
		if ($scope.data.filter.length > 1)
			$scope.data.filter += '^';
		$scope.data.filter += query;
		$scope.setSearch();

	}

	// Makes Widget Async
	var title = $scope.data.title;
	if ($scope.options.use_instance_title == 'true')
		title = $scope.options.title;
	$scope.data = $scope.options;
	$scope.loadingData = true;
	$scope.server.update().then(function() {
		if ($scope.data.newButtonUnsupported)
			console.log("Service Portal: New button not supported for sys_attachment list");
		$scope.loadingData = false;
		$scope.data.title = title;
		initRecordWatcher($scope.data.table, $scope.data.filter);
	});

	function parseQuery(table, queryString){
		return $http.post('/api/now/sp/parsequery/' + table, queryString).then(function(response){
			return response.data.result;
		});
	}

	c.createQueryTerm = function(table, field, sys_id, operator){
		return $http.get('/api/now/sp/getInOutQueryTerm', {
			params: {
				table: table,
				sys_id: sys_id,
				field: field,
				operator: operator
			}
		}).then(function(response){
			if (response && response.data && response.data.result)
				return response.data.result.parts;
		});
	}

	c.showMatching = function(field, newTerm) {
		var queryString = $scope.data.filter;
		var eq = "";
		parseQuery($scope.data.table, queryString).then(function(oldTerms) {
			for(var i=0; i<oldTerms.length; i++){
				var term = oldTerms[i];
				if (isSameField(newTerm, term))
					continue;

				if (eq.length)
					eq += '^';

				eq += getEncodedTerm(term);
			}
			if (eq.length)
				eq += '^';
			eq += getEncodedTerm(newTerm);

			$scope.data.filter = eq;
			$scope.setSearch();
		});
	};

	c.filterOut = function(field, newTerm) {
		var eq = $scope.data.filter;
		if (eq.length)
			eq += '^';

		eq += getEncodedTerm(newTerm);
		$scope.data.filter = eq;
		$scope.setSearch();
	};

	function isSameField(t1, t2) {
		if ('left' in t1 && 'left' in t2)
			return t1.left.field === t2.left.field;
		else if ('left' in t1)
			return t1.left.field === t2.field;
		else if ('left' in t2)
			return t1.field === t2.left.field;
		return t1.field === t2.field;
	}

	function getEncodedTerm(term) {
		var eq;
		if (term.left) {
			eq = getEncodedTerm(term.left);
			eq += '^OR';
			eq += getEncodedTerm(term.right);
		} else {
			eq = term.field;
			eq += term.operator;
			eq += term.value;
		}
		return eq;
	}

}

 

3. Create new page called Name: My Tickets and ID: my_tickets

 find_real_file.png

 Final Result:

find_real_file.png

 

find_real_file.png

Bonus: (Add link in header)

got to sp_rectangle_menu_item.list and click on New and make it same as below

find_real_file.png

 

Comments
Allen Andreas
Administrator
Administrator

Nicely done. I know people tend to walk a fine line between Incidents and Requests so if your (the person who wants this) organization is mature enough to not get confused by this, then by all means, use it.

One small discrepancy is the "Active Requests" and "Closed Requests" tabs...not sure if I would call those tabs that since it deals with more than just Requests, but again, I'm sure that's very easy to change. Just my 2 cents on the matter.

This is awesome though Mike. Thank you for doing the hard work for us!

Mike Patel
Tera Sage

Thanks & Yes, It's pretty easy to change in HTML code.

<uib-tab index="0" heading="Active Tickets"

Brian Lancaster
Tera Sage

What if we did not want to use the work ticket or request.  Instead have a 2 separate pages one to show open and closed incidents and one to show open and closed requests.  Would I need to create a widget for incidents and a widget for requests?

Mike Patel
Tera Sage

If you want 2 pages then you will need 2 widgets one for each type

Keerti2
Mega Expert


kindly let me know to add short description for request , as of now i can isee "item name , request number" when i click on "My Requests"

now i want to see " item name , short description and request number" . kindly let me know code and where can we update that code.

 

Herin D
Kilo Contributor

Thanks Mike. This information is very helpful. How can we add breadcrumbs to this so that we can filter the query. 

Mike Patel
Tera Sage

On my tickets widget server script (2nd script on this article) you will see 

"show_breadcrumbs": false,

change that to true so it shows breadcrumbs.

Only issue will be any filter you might have applied will be visible to user.

Herin D
Kilo Contributor

Thanks for the information Mike it worked. Much Appreciated.  I can see the breadcrumbs now. What i have noticed is that if change the filter to All it removes the filter and I cannot filter anything. How should i be changing this which allows use of filter on the table.

 

Mike Patel
Tera Sage

Since breadcrumbs is set to display true then it will show all filters and user will be able to remove your stored filter.

basantsoni
Kilo Guru

Hi Mike, I have create first widhet. but no data is getting show when i preview wighet. 

just buttons are coming. 

Mike Patel
Tera Sage

Hi, You need to complete whole thing. There is no preview.

basantsoni
Kilo Guru

HI Mike, Thanks for your responce. If we'll completed all steps as mention on PDI Instance then information will display. Do we need to change anything.

Mike Patel
Tera Sage

I saw your other post since you are trying to get catalog item and asset info so there are some changes you need to do. I added xml file to your post.

Herin D
Kilo Contributor

Hi Mike,

thanks for the information. breadcrumbs set to true works but we cannot change the filter as there is no option available. if we click on All it removes the filter and we cannot filter anything as no option available.

How can the behavior be changed so that we can change or edit the filter.

Thanks

Keerti2
Mega Expert

I tried to modify Font, background , lenth of backcolour for "My Watched Items" same like as "My Approvals" showing in below screenshot , i try to modify in "my-tickets" widget and "my_widget_data_table" widget CSS code , but no luck 😞

Kindly help me the code for the same

find_real_file.png

Mike Patel
Tera Sage

Are you trying to change the My watched Items label color from white to some another color?

Like below

find_real_file.png

Css

.nav-tabs > li.active > a, .nav-tabs > li.active > a:hover, .nav-tabs > li.active > a:focus {
    color: red;
    background-color: limegreen;
    border: 1px solid #ddd;
    border-bottom-color: transparent;
    cursor: default;
}
Keerti2
Mega Expert

Hi Mike,

Thanks alot for your help , this widget output looks like "Box under box" but the image should like "my approval" single box. Request you please help on this.

 

Mike Patel
Tera Sage

If you want to add Title like below then you have to do it on widget instance.

find_real_file.png

Add Title

find_real_file.png

Keerti2
Mega Expert
i want to see same like "My Approval"  , but my watched list appears like Box under one more box . Kindly help me to appear as single box same like my approval.
 
i dont want table for Active request or closed requests , i want  see records directly  , i can see another table under page , 
 
 
example like below screenshot
 
 
image.png
 
Mike Patel
Tera Sage

I can't see the image but My Approval is different widget type so it will not be the same.

Keerti2
Mega Expert

Thank you Mike , when i see number , short description ...etc fields   but table borders not appearing , kindly help me to add borders on table , am using below css code

 

.nav-tabs > li.active > a, .nav-tabs > li.active > a:hover, .nav-tabs > li.active > a:focus {
    color: black;
    background-color: #a7b6d1;
    border: 1px solid #ddd;
    border-bottom-color: transparent;
    width: 338px;
    cursor: default;
}

Keerti2
Mega Expert

Kindly alter code can see like below only

 

 

find_real_file.png

Kim Sullivan
Tera Guru

Great widget!  I love how you made it so easy to scale up with more tabs!

Daniel Voutt
Tera Contributor

Hi, 

Thank you so much for this, it is awesome and a great addition to the service portal!

I am trying to add a new tab for change requests and have added the additional code required in the body HTML, the new query in the server script but when I load the page, I'm getting the new tab but it's empty - has anyone else added more tabs and I have overlooked something else that needs to be added to make the data appear for the additional tab.

I have tried the same filtering on the "closed requests" code and it works as required, so I know the filter etc is correct, so I must have missed something that helps render the data.

Are there any changes needed in the my_widget_data_table widget as I've currently only updated the "my-tickets" widget.

Any help would be appreciated as adding the extra tabs will allow me to put everything all in one place.

Daniel Voutt
Tera Contributor

Hi, have you managed to add more tabs?  I have tried by not able to get the data to appear - thinking I've missed something somewhere.

Mike Patel
Tera Sage

Can you share your code so I can look through it.

Daniel Voutt
Tera Contributor

Hi, thanks for coming back to me.

I have added the following:

Body HTML

    <uib-tab index="2" heading="Change Requests" uib-tooltip="View all Change Requests that you opened or that were opened on your behalf."><sp-widget widget="data.all_changes"></sp-widget></uib-tab>

Server script

var changeFilter = 'sys_class_name=change_request';
var all_changes = {
"table": "task",
"view": "mobile",
"fields": "number,sys_created_on,sys_updated_on",
"show_keywords": true,
"show_breadcrumbs": true,
"o": "sys_created_on", // order_by
"filter": changeFilter,
"storef": changeFilter,
"query_filter": taskQueryFilter
};

 

data.all_change = $sp.getwidget("my_widget_data_table", all_changes);

Does anything need to be added to the other file?

Daniel Voutt
Tera Contributor

ignore the typo in the last statement - that has been corrected and no data still happens

Anmol Azhar1
Kilo Explorer

Hi Mike -

Great article, it was very helpful!

How would we get rid of the text 'Tasks' next to the hamburger icon for the export types?

Also, how can we edit the text when there are no records found?

find_real_file.png

find_real_file.png

Mike Patel
Tera Sage

On my_widget_data_table widget removed bolded part.

 

<span class="panel-title"><i ng-if="options.glyph" class="fa fa-{{options.glyph}} m-r"></i>{{data.title || data.table_plural}}</span>

 

same widget has HTML below, That's where it comes from

<div class="alert alert-info" ng-if="!data.list.length && !data.num_pages && !data.invalid_table && !loadingData"> ${No records in {{data.table_label}} <span ng-if="data.filter">using that filter</span>} </div>

Daniel Voutt
Tera Contributor

Hi, did you get a chance to review my code?  sorry to pester!  Dan

Anmol Azhar1
Kilo Explorer
Thank you so much Mike, really appreciate it!
Abc34
Kilo Explorer

Thanks for this article.I have implemented this functionality but when i load the page for the first time it taking more time to load the data(data is very less). Do you have any suggestion to improve this?

Sumanth Now
Tera Expert

@Mike Patel  Hello Mike, Thanks for the article, I have implemented on my personal instance and its working fine.

However, I am struggling to add/enable filters for End users to filter accordingly.

I have followed the below article and related community links, but no luck.

https://support.servicenow.com/kb?id=kb_article_view&sysparm_article=KB0687054

I believe you have used similar to 'data table from instance definition' widget?

Mike, can you please add filters/condition builder in your script and update here OR advise how to achieve this?

@Herin D Have you had any luck with the filters?

 

Many thanks in advance,

Sumanth

 

Siva74
Tera Contributor

Thanks very much for this solution @Mike Patel , works like a charm and I could also add more tabs.

Regards,

Sivaranjani S

davilu
Mega Sage

@Mike Patel thanks! this is awesome, one question though, what does that last line in the server script do?

 

data.tabName = $sp.getParameter('tab_focus') || 'all_active';

Mike Patel
Tera Sage

That's for the dynamic url if you want to pass the parameter that focuses onload.

if no parameter is passed then all_active tab is loaded.

In url if you pass tab_focus=all_closed then when it is loaded it will focuses on all closed tab.

Simon19
Tera Explorer

Hi @Mike Patel Thank you so much for this, it is a brilliant piece of work.  I would like to use this in the context of the cmdb_ci table to list devices by multiple different sys_class_name. How do I get the record to open with the relevant form instead of the activity form it currently opens with?

Many Thanks,

Simon. 

namrata23
Tera Contributor

Hello @Mike Patel 

Can you please tell me, where should I make changes in the code to show reqs from my custom table that does not extend from task table, it is custom table of scoped application. Kindly let me know. 

 

Thankyou

Mike Patel
Tera Sage

@namrata23 Please share your code and I can review it.

Sean Silveira
Tera Contributor

Hi @Mike Patel,

 

Thanks for this.  Is it possible to have it show Incidents and Tasks?  Rather than incident and Requests?

 

- Sean

jbasa
Tera Contributor

@Mike Patel This is exactly what I am looking for.

I have 5 different tables extended from Incident for 5 different departments.  I want callers to have a view like you show here where they can search across those. Any other suggestions for me on how or what else I need to take into account for this? I am not a dev but have been the sole admin of our instance for 6 years ( small org).

My hope is I can mirror what you have and figure it out.

jbasa
Tera Contributor

@Mike Patel 

 

Can multiple tables be added here?

 

jbasa_0-1708186162424.png

 

HarishVardineni
Tera Contributor

Search results showing same as filter results but we want to check keyword search text in list view fields only

Vedavalli
Tera Expert

Hi @Mike Patel 

I have created widgets as you mentioned but for me getting like mentioned in the screen shot could you please let me know why I am not getting list view
Screenshot (464).png

abhi56
Tera Contributor
  • @Mike Patel @Can I call two diff page from those two tabs instead of calling two diff widget
  • Actually I want to apply a page specific css to make filter read only but it is only working for the first tab ,for the second tab it is not working properly ,so I want to call two diff page from those tabs instead of widget like we call using href tag 
kmolson73
Tera Expert

Do you think there is a way to adjust the server script in the my tickets widget to allow to check multiple custom tables that are not related to the Task table? 

Krecker
Tera Expert

@Mike Patel Thanks for this awesome code.
@Sumanth Now @Herin D 
I managed to activate the filter widget. As Mike Patel mentioned in his posts, the filter is getting visible for end users by that. But that's another story.
I mainly used parts of the OOB Widget "Data Table" to enhance the code.

Here is the solution:

In the "My Data Table" widget

1. HTML
replace this part

<div ng-if="options.show_breadcrumbs && data.filter" class="filter-breadcrumbs">
    <sp-widget widget="data.filterBreadcrumbs"></sp-widget>
</div>​

with this

<div ng-if="options.show_breadcrumbs && (data.filter || data.enable_filter)" class="filter-breadcrumbs">
    <sp-widget widget="data.filterBreadcrumbs"></sp-widget>
</div>


2. Server Script
replace lines 151 & 152

var breadcrumbWidgetParams = { table: data.table, query: data.filter,enable_filter: data.enable_filter};
data.filterBreadcrumbs = $sp.getWidget('widget-filter-breadcrumbs', breadcrumbWidgetParams);​

with this

	data.enable_filter = (input.enable_filter == true || input.enable_filter == "true" ||
	options.enable_filter == true || options.enable_filter == "true");
var breadcrumbWidgetParams = {
	table: data.table,
	query: data.filter,
	enable_filter: data.enable_filter
	};
data.filterBreadcrumbs = $sp.getWidget('widget-filter-breadcrumbs', breadcrumbWidgetParams);​


In the "My Tickets" widget

1. Server Script
In the options for the tabs - change "show_breadcrumbs" to true and add

"enable_filter": true,​

so it looks like this:

var all_active = {
		"table": "task",
		"view": "mobile",
		"fields": "number,short_description,sys_created_on,sys_updated_on",
		"show_keywords": true,
		"show_breadcrumbs": true,
		"enable_filter": true,
		"o": "sys_created_on", // order_by
		"filter": activeFilter,
		"storef": activeFilter,
		"query_filter": taskQueryFilter
	};​


Result: (showing the default "activeFilter" of the widget - better change it when using this)

Krecker_0-1744924770845.png

 

Version history
Last update:
‎05-11-2019 11:34 AM
Updated by: