As already explained and shown here, we can pretty easily, based on the ServiceNow and AngularJS documentation, override client facing actions of one widget from another widget. This time, the task is slightly different – we want to change the data returned by the server script.
Use case
I was asked to modify the ticket page. I was supposed to update the text value displayed in the stage renderer. Why and to what extent – it does not matter now. Initially I thought it would be pretty easy task, just change some variables in the widget. Unfortunately, it wasn’t that easy.
Investigation
Just in case you don’t know how to check what’s behind the value displayed on the service portal, I’ll guide you step-by-step:
- Open the page where you want to do the modification
I usually do that in the front-end, to make sure I can pinpoint the responsible widget. Because, eventually, on service portal it’s all about widgets. - Do the ctrl+alt+right mouse button on the part you want to modify
There is a hidden menu there with some handy options for portal development, remember that key combination!
- Use the Widget in editor option
- When searching for the stage display part you will discover two things:
- The Standard Ticket Header widget is read only
- There is another widget responsible for rendering the stages (line 99 in HTML template; line 243 in server script)
<sp-widget widget="field.stageWidget"></sp-widget></div>
f.stageWidget = $sp.getWidget('request_item_workflow_stages', {req_item_id: sysId});
- With this knowledge, even cloning the Standard Ticket Header widget would not help. So let’s open the
request_item_workflow_stages
widget.
Another read only widget! Who would have thought… - Anyway, you will find two templates included. Based on your stage renderer (in my case it was
Workflow-driven
), one of them will be used.
If you really want to find out which one you are using, it’s pretty easy – just copy the important parts of the server script in the background script window and do some logging. It’s not important from this article’s perspective. - Both templates are doing
ng-repeat
fordata.choiceList
array. After another close look at thediv
holding the stages, it looks likedata.choiceList
is an array of objects.
But they display different values – check both to make sure you will overwrite correct property - If you take a look at the server script,
data.choiceList
comes from multiple different sources, based on your renderer. I haven’t checked all of them, but I guess that they are not visible, not to mention editable, on the instance. In my case it wasSNC.RendererAPI().getAllWorkflowChoices()
- Now we know everything, let’s start doing the real work!
Really? I’m still confused…
We know that we have two options – we can either clone and modify the Request Item Workflow Stages, clone and modify the Standard Ticket Header and update the ticket page… Or read my previous article one more time, create a new widget to overwrite the data.choiceList
and add it to the ticket page. Well, to be honest, when I initially considered the second option, it sound crazy – I would have to modify the object coming from the server side. But eventually, it lands on the client side and based on its values something happens. When you add to it that it’s AngularJS
and when you know that client modifications to the $scope
are not only possible, but also immediately visible…
You see where I’m going now?
New widget
The new widget is pretty simple – all we need is a client function that will be triggered by MutationObserver
defined in the link
function.
Here’s the code for the link
function:
function link(scope, element, attrs, controller) {
var observer = new MutationObserver(function (mutations) {
for (var i = 0; i < mutations.length; i++) {
var mutation = mutations[i];
if (mutation.addedNodes) {
for (var j = 0; j < mutation.addedNodes.length; j++) {
var node = mutation.addedNodes[j];
if (!node.tagName) {
continue;
}
if (node.classList.contains('v[sys_id of the Request Item Workflow Stages widget')) {
controller.init(angular.element(node));
observer.disconnect();
return;
}
}
}
}
});
observer.observe(element.parent().closest('div')[0], {
childList: true,
subtree: true
});
}
It’s pretty basic, but there is one caveat – it highly depends on the widget placement. Take a closer look at highlighted line 21 – it starts the MutationObserver
instance at given node. You have to be extra careful to initialize it at the correct one, based on your widget’s placement on the page. I added my widget just before the Standard Ticket Header, in the same rom and column.
On line 11 and 12 you can see how to find the correct node and run the client script with Request Item Workflow Stages‘ node as an argument.
Honestly, this was the hardest part. When we have this, we can create a client script, as simple as
api.controller=function() {
var c = this;
c.init = function (targetElement) {
targetScope = targetElement.scope();
var list = targetScope.data.choiceList;
for (var i = 0; i < list.length; i++){
if (list[i].selected){
targetScope.data.choiceList[i].displayValue = 'My new displayValue';
}
}
};
};
Simple, isn’t it? Line 5 assigns the target scope to the global variable. Line 9 is to make sure that we update only the currently in progress stage, that’s what we want to do.
Conclusion
It’s really simple to work with widgets in service portal. Once you practice enough to understand more and more technicalities of the AngularJS
, you can easily overcome obstacles at little cost.
Remember that and keep discovering!