You are probably well aware of the concept of service catalog variables that you can use in the service portal. There is already plenty to choose from, but even that rich library is sometimes not enough. Every project is unique and there is no repeating scenario. That’s why not every possible case could have been covered. ServiceNow team understood that and left something special for those rare occasions – Custom and Custom with label variable types.
Special variables
In addition to the common single-line texts or select boxes, we also have something called Custom (Custom with label is pretty much the same thing, but it renders with additional label – I will skip refereing to both of them for the rest of the article). Once you add it, you will notice that it allows you to add 4 different types of objects:
- Macro
- Summary macro
- Widget
- Macroponent
The first two options seem like they only exist to support old instances. The fourth one, on the other hand supports the new UI Builder components. I will be using the widget option, as it is the most popular choice right now.
What is the use case?
Imagine you are working on a catalog item that should allow the user to pick a location. Seems pretty simple, even if you don’t have it hierarchical (which I strongly recommend to have). The caveat is that users may be using this catalog item for locations that don’t exist in your foundation data. Now, what are your options?
- Ignore the requirement to add a new location, because you will manage it in your foundation data imports
Risky approach, as you may still not get everything. But the fastest. - Build hierarchical references to allow user to add leaf location (the office or the room)
But what if one of the parents is missing? - Redirect users to another catalog item designed for adding locations
I doubt users will raise another request just to get what they want. - Add free form text field for user to enter location
All users know the coding? The hierarchy? Do they know it also includes data at country/city/building/… level ? - Combine some ideas and add a widget allowing users to enter new location with proper hierarchy.
This was my approach
The widget
The widget is not that complex when you think of it.
- You need a dropdown for every hierarchy level you want to include
- You need to place them near each other, because the catalog item form is narrow
- You need an input to allow free form text
- And finally, you need to combine it with the catalog item form and make sure that the new location is saved as a variable within the record
Everytime I have to create a widget, I visit the UI Bootstrap library page to find some inspirations. All of the components found there can be used in service portal widgets and they bring fresh, modern experience to the end users. For that particular use case, I decided to use the Dropdown on the button.
The setup is super easy:
<div class="btn-group" uib-dropdown>
<button id="levelRef" type="button" class="btn btn-info" uib-dropdown-toggle>
{{locations.level.name}} <span class="caret"></span>
</button>
<ul class="dropdown-menu" uib-dropdown-menu role="menu" aria-labelledby="cityRef">
<li ng-repeat="locationRecord in c.locations.level" role="menuitem" ng-click="c.changeLevel(locationRecord.sys_id)"><a href="#">{{locationRecord.name}}</a></li>
<li class="divider"></li>
<li role="menuitem" ng-click="c.notListedModal('level')"><a href="#">Level not listed</a></li>
</ul>
</div>
- Line 1:
uib-dropdown
attribute is the dropdown component renderer - Line 2:
uib-dropdown-toggle
attribute is the dropdown trigger - Line 5:
uib-dropdown-menu
attribute defines the list of choices for the dropdown - Line 7:
divider
adds a horizontal line between dynamic and static choices
I have also anonymized my hierarchy, that’s why I have level
everywhere.
It’s up to you how you want to retrieve the already existing data for hierarchy. I used the GlideQuery
, it shows a really nice advantage to classical GlideRecord
solution:
function getData(locationType, parent) {
let ret = [];
let gq = new global.GlideQuery('cmn_location')
.where('cmn_location_type', locationType);
if (!gs.nil(parent))
gq = gq.where('parent', parent);
gq
.orderBy('name')
.select('name')
.forEach(function(childLocation){
ret.push({
sys_id: childLocation.sys_id,
name: childLocation.name
});
});
return ret;
}
This covers all possible hierarchies and is pretty self-explanatory. Notice the use of let
– I created the widget in ES2012 scoped application.
In the client controller, all you need is
- to handle the hierarchy reset on any dropdown change
- disabling/enabling other dropdowns on hierarchy change
- allowing the user to add new data at any hierarchy level
- saving the newly added location to another variable
Yes, you need another variable to save the data from the widget. But once you master that simple client script snippet:
if ($scope.page.g_form) {
$scope.page.g_form.setValue('some_variable', newLocationString);
}
You will quickly understand that nothing really stops you!
Final decisions
You still have to make one decision – don’t forget about the new data. For some reason, user had to add it. You made it as easy as possible, but you can’t just ignore it. You probably want to have it reviewed by another team responsible for managing the facility. Whether it’s just an email notification, another catalog item created in the background or a simple SCTASK – do something and don’t ignore the user.
Making the platform even more accessible makes my job very enjoyable. And I hope you will it too, dear reader. The fact that you want to share that joy with me by reading the last paragraph gives me the reason to write more. Thank you!
BTW, I’ll add a video of how it works once I get my PDI up and running. For now, just believe me that it works like that: