Skip to content

Commit a81a30b

Browse files
Dynamic table record selector widget (#2015)
* Create README.md This README provides instructions on how to set up, configure, and use the widget in ServiceNow Service Portal. * Create HTML.html This HTML file defines the structure and layout for the widget’s interface. * Create Client Side.js This client-side script manages the widget’s functionality using AngularJS. It listens to field changes, makes HTTP requests to ServiceNow REST APIs to fetch table and record data, determines the correct display field (including parent table lookups and fallbacks), and updates the widget dynamically.
1 parent 42c690c commit a81a30b

File tree

3 files changed

+198
-0
lines changed

3 files changed

+198
-0
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
api.controller=function($scope, $http, spUtil, $rootScope, $timeout, spModal) {
2+
/* widget controller */
3+
var c = this;
4+
5+
// Initialize scope variables
6+
$scope.tablename = {
7+
displayValue: '',
8+
value: '',
9+
name: 'tablename'
10+
};
11+
$scope.record = {
12+
displayValue: '',
13+
value: '',
14+
name: 'record'
15+
};
16+
$scope.selectedTable = '';
17+
$scope.TableSysId = '';
18+
19+
// Handle field changes (table/record)
20+
$scope.$on("field.change", function(evt, parms) {
21+
if (parms.field.name === 'tablename') {
22+
// Get sys_id of selected table → fetch actual table name & label
23+
var sysId = parms.newValue;
24+
var url = '/api/now/table/sys_db_object/' + sysId + '?sysparm_fields=name,label';
25+
$http.get(url).then(function(res) {
26+
if (res.data.result) {
27+
$scope.selectedTable = res.data.result.name;
28+
$scope.selectedTableLabel = res.data.result.label;
29+
c.getDisplayField($scope.selectedTable, sysId); // fetch display field
30+
}
31+
});
32+
} else if (parms.field.name === 'record') {
33+
// Save selected record sys_id
34+
$scope.TableSysId = parms.newValue;
35+
}
36+
});
37+
38+
// Get display field for a table (recursive if needed)
39+
c.getDisplayField = function(tableName, tablesysId) {
40+
var url = '/api/now/table/sys_dictionary' +
41+
'?sysparm_query=name=' + tableName + '^display=true' +
42+
'&sysparm_fields=element' +
43+
'&sysparm_limit=1';
44+
45+
$http.get(url).then(function(response) {
46+
if (response.data.result && response.data.result.length > 0) {
47+
// Found display field
48+
$scope.recorddisplayValue = response.data.result[0].element;
49+
} else {
50+
// Check parent table
51+
var parentsysIdUrl = '/api/now/table/sys_db_object/' + tablesysId + '?sysparm_fields=super_class';
52+
$http.get(parentsysIdUrl).then(function(parentRes) {
53+
var parentTable = parentRes.data.result.super_class.value;
54+
55+
if (!parentTable) {
56+
// No parent - fallback checks
57+
var nameCheckUrl = '/api/now/table/sys_dictionary' +
58+
'?sysparm_query=name=' + tableName + '^element=name' +
59+
'&sysparm_fields=element&sysparm_limit=1';
60+
61+
$http.get(nameCheckUrl).then(function(nameRes) {
62+
if (nameRes.status == 200) {
63+
$scope.recorddisplayValue = 'name';
64+
} else {
65+
var numberCheckUrl = '/api/now/table/sys_dictionary' +
66+
'?sysparm_query=name=' + tableName + '^element=number' +
67+
'&sysparm_fields=element&sysparm_limit=1';
68+
69+
$http.get(numberCheckUrl).then(function(numberRes) {
70+
if (numberRes.status == 200) {
71+
$scope.recorddisplayValue = 'number';
72+
} else {
73+
$scope.recorddisplayValue = 'sys_id'; // Final fallback
74+
}
75+
});
76+
}
77+
});
78+
79+
} else {
80+
// Parent exists - recurse
81+
var parentNameUrl = '/api/now/table/sys_db_object/' + parentTable + '?sysparm_fields=name';
82+
$http.get(parentNameUrl).then(function(parentResname) {
83+
var parentTableName = parentResname.data.result.name;
84+
c.getDisplayField(parentTableName, parentTable); // recursive lookup
85+
});
86+
}
87+
});
88+
}
89+
}, function(error) {
90+
spModal.alert("Error fetching display field: " + error);
91+
});
92+
};
93+
94+
};
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<div class="row">
2+
<div class="col-md-6">
3+
<label class="form-label">Select Table</label>
4+
<sn-record-picker
5+
field="tablename"
6+
table="'sys_db_object'"
7+
display-field="'label'"
8+
value-field="'sys_id'"
9+
search-fields="'label'"
10+
page-size="100">
11+
</sn-record-picker>
12+
</div>
13+
14+
<!-- After a table is chosen, show record selector -->
15+
<div class="col-md-6" ng-show="selectedTable && recorddisplayValue">
16+
<label class="form-label">Select Record</label>
17+
<sn-record-picker
18+
field="record"
19+
table="selectedTable"
20+
display-field="recorddisplayValue"
21+
value-field="'sys_id'"
22+
search-fields="recorddisplayValue"
23+
page-size="100">
24+
</sn-record-picker>
25+
</div>
26+
</div>
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
Widget Name: Dynamic Table and Record Selector
2+
3+
Overview:
4+
This ServiceNow Service Portal widget allows users to dynamically select any table and then choose a record from that table. The widget automatically determines which field should be shown as the display field for the selected table. It also handles parent table inheritance and provides fallback options for display fields.
5+
6+
Main Features:
7+
8+
Lists all tables from the sys_db_object table.
9+
10+
Automatically finds the correct display field (field with display=true).
11+
12+
Supports parent table lookup if the child table does not have a display field.
13+
14+
Provides fallback checks for fields named "name", "number", or defaults to "sys_id".
15+
16+
Uses ServiceNow REST APIs to fetch metadata and record data dynamically.
17+
18+
Works with the standard sn-record-picker directive in Service Portal.
19+
20+
How It Works:
21+
22+
The first record picker displays all tables from sys_db_object using the label field.
23+
24+
When the user selects a table, the widget fetches the actual table name and label using the sys_id.
25+
26+
The controller calls the getDisplayField function to determine which field should be displayed in the record picker.
27+
28+
It checks sys_dictionary for a field with display=true.
29+
30+
If found, that field is used as the display field.
31+
32+
If not found, it checks if the table has a parent (super_class).
33+
34+
If a parent exists, it recursively checks the parent table.
35+
36+
If there is no parent, it uses fallback checks for "name", then "number", and finally "sys_id".
37+
38+
The second record picker then displays the records from the selected table using the determined display field.
39+
40+
When the user selects a record, its sys_id is stored in the variable TableSysId.
41+
42+
Example Flow:
43+
44+
Select “Incident” from the table picker.
45+
46+
The widget detects that the display field is “number”.
47+
48+
The record picker lists incident numbers.
49+
50+
When a record is selected, its sys_id is saved for further use.
51+
52+
Technologies Used:
53+
54+
ServiceNow Service Portal
55+
56+
AngularJS (spUtil, spModal)
57+
58+
ServiceNow REST API:
59+
60+
/api/now/table/sys_db_object
61+
62+
/api/now/table/sys_dictionary
63+
64+
Use Cases:
65+
66+
Creating dynamic reference selectors for any table.
67+
68+
Building tools that link or map data between tables.
69+
70+
CMDB record selection where tables may have inheritance.
71+
72+
Generic admin utilities or catalog forms needing flexible input.
73+
74+
File Components:
75+
76+
HTML Template: Contains two sn-record-picker elements for selecting table and record.
77+
78+
Client Controller (JS): Handles field change events, fetches table metadata, determines display fields, and manages recursion logic.

0 commit comments

Comments
 (0)