-
Notifications
You must be signed in to change notification settings - Fork 74
Lab 4
So far the incident workspace page only has one widget but this is the perfect time to think about sharing data and interactions between widgets. As soon as you add another widget to the page you will probably ask yourself one of these questions:
- How do I keep my widgets in sync when changing records or filters?
- How can my widgets share context?
- How do I maintain and persist state?
You can easily drop widgets onto a page and have them all do their own thing. A portal homepage is a good example of that. In this case, all the widgets on the page are ultimately going to react to a single table&filter or a selected record.
To share data between widgets you can use a custom javascript dependency or an angular provider called a service. In this lab you will create a toolbar widget, data service, and then use that data service across your widgets.
-
Using STUDIO click Create Application File.
-
Select Service Portal -> Widget. Click Create.
-
On the widget editor click Create a new widget. Use the following values:
Widget name: Workspace header
Widget ID: workspace_headerClick Submit.
-
Edit the option schema with the following values:
Label: Title
Name: title
Type: stringLabel: Table
Name: table
Default Value: incident
Type: stringLabel: Count expressions
Name: count_expressions
Hint: Label,query;Label,query;...
Default Value: Unassigned Incidents,assigned_toISEMPTY;New Incidents,state=1
Type: string -
Click Save
-
Enter this for the HTML Template:
<div class="workspace-header clearfix">
<div class="pull-right">
<div class="count" ng-repeat="counter in c.counters">
{{counter.count}}
<span class="count-label">{{::counter.label}}</span>
</div>
</div>
<div class="h2">
{{::c.options.title}}
</div>
</div>
- Enter this for CSS:
$workspace-header-background: $gray-light !default;
$workspace-header-padding: 5px !default;
.workspace-header {
background-color: $workspace-header-background;
margin: 0px;
margin-bottom: 5px;
padding: $workspace-header-padding;
@include border-top-radius(3px);
@include border-bottom-radius(3px);
.count {
font-size: 3rem;
text-align: center;
display: inline-block;
margin-right: 20px;
.count-label {
font-size: 1.8rem;
display: block;
}
}
}
- Enter this for Client Script:
function($http) {
/* widget controller */
var c = this;
var countExpressions = c.options.count_expressions.split(";");
c.counters = [];
for(var i=0;i<=countExpressions.length-1;i++) {
var parts = countExpressions[i].split(",");
var counter = makeCounter(parts[0], parts[1]);
runCounter(counter);
c.counters.push(counter);
}
function runCounter(counter) {
var url = "/api/now/stats/"+ c.options.table +"?sysparm_query="+ counter.filter +"&sysparm_count=true";
$http.get(url).then(function(response) {
counter.count = response.data.result.stats.count;
});
}
function makeCounter(label, filter) {
return {label: label, filter: filter, count: 0};
}
}
- Save the widget.
In summary, this widget can show a heading and counts.
When you configure the instance options for this widget you can provide count expressions in the following format:
Label,filter
Label: A string to show below the count
Filter: An encoded query string
Separate multiple count expressions with a semi-colon.
For each count expression, it will display the label and call the stats rest api to get a count of records matching the filter. This isn’t very intuitive to configure or efficient to execute but it can be easily modified later to use a shared data source. The takeaway here is that we aren’t using Server Script at all. You will soon see how to provide data to your widgets via rest endpoints.
-
Open the Service Portal designer in a new tab by going to [instacnce]/$spd.do
-
Open the Incident Workspace page.
-
Drag a new 12 column container to the very top of the page above the existing [3 | 9] layout like this:
-
Drag the Workspace header widget into that new container:
Now that you have 2 widgets on a page using nearly the same data source, it’s time to create a data service that both widgets can use. It’s important to remember that since widgets are just angular directives, all the widgets on a page will use the same instance of an angular service. It’s just an instance of an object that is accessible by any widget that chooses to use it.
Angular Providers can’t be created from Studio so you need to create it using the platform.
-
Open your instance in a new tab:
[instance]/ -
Type Service Portal in the navigator and select Angular Providers
-
At the top of the list click New
-
Add a new Widget Angular Provider with the following values:
Type: Service
Name: workspaceData
Client Script:
function(amb) {
var watcher;
var dataUpdatedHandlers = [];
function init(table, filter) {
if (watcher) {
watcher.unsubscribe();
}
if (table && filter) {
var watcherChannel = amb.getChannelRW(table, filter);
amb.connect();
watcher = watcherChannel.subscribe(function(message) {
if (!message.data) {
return;
}
dataUpdatedHandlers.forEach(function(fn) { fn.call(fn); });
});
}
}
return {
onDataUpdated: function(callbackFn) {
dataUpdatedHandlers.push(callbackFn);
},
initRecordWatcher: function(table, filter) {
init(table, filter);
}
};
}
This service has two methods:
-
onDataUpdated(callbackFn)
Use this method to subscribe to data updates. For the filter defined in initRecor dWatcher, when a record is added, updated or removed, the callback functions will be executed. -
initRecordWatcher(table, filter)
Use this method to start watching a table & filter for changes.
-
Using the navigator, open the Widgets list
-
Edit the Workspace Header widget
-
Scroll to the bottom of the form, open the Angular Providers related list, and click Edit
-
Find workspaceData in the collection of angular providers. Double-click to move it to the right-side slush bucket.
-
Click Save
Now repeat the same steps for the Card List widget so both widgets will import the workspaceData service.
- Using the widget editor, open the Card List widget
- Inject the workspaceData service into the Client Script by adding it as a parameter to the
controller function:
- Add the following code just below var c = this;
workspaceData.initRecordWatcher(c.options.table, c.options.filter);
workspaceData.onDataUpdated(function() {
c.data.rows = [];
c.server.update().then(function(data){
c.data.rows = data.rows;
});
});
Your Client Script should look like this:
- Save the widget
Now when the record watcher fires, the widget will run c.server.update() and refresh the card list. This isn’t optimal though and in the next section you will learn how to get the card list data from a webservice which utilizes a data stream and is much more performant than re-running the server script and serializing 25 records every time a record change is observed.
- Using the widget editor, open the Workspace Header widget
- Just like you did before, inject the workspaceData service into the Client Script controller function.
- Add the following code just below var c = this;
workspaceData.onDataUpdated(function(){
c.counters.forEach(function(counter){ runCounter(counter); });
});