Create a Living, Listening Widget
The Service Portal has an extremely useful feature called Record Watch. Record Watch allows you to configure a listener function that notifies your widget when certain database actions take place. When you have a Record Watch function configured, your widget can automatically adjust itself accordingly.
In this example, I am going to explain how I added a Record Watch listener function that automatically increases the size of a bar in a bar chart when a matching record is added. This will build on a previous post of mine which can be found here, so this post will strictly focus on the Record Watch portion.
Record Watch Function
First, you'll want to inject spUtil into your client script function parameters. I'll post my full client script at the end in case you aren't sure where to put this.
Here is my Record Watch function which I will walk through:
spUtil.recordWatch($scope, "incident", "active=true", function(name, d) {
if (d.action == 'entry') {
for (i=0; i < $scope.activeData.length; i++) {
if (d.record.category.display_value == $scope.activeData[i].category) {
$scope.activeData[i].value++;
break;
}
}
$scope.updateBars($scope.activeData);
}
});
In the first line, we call the Record Watch function from spUtil. The second parameter we pass is the table that we want to listen to and the third parameter is the filter so we only get notifications for the specific types of records we want. Lastly, we create an anonymous function that will allow us to make sense of the notification we receive from our Record Watch function.
We are passing the parameters of name and d to our anonymous function. The name will provide information about the update. The d parameter contains information about the action type as well as the information from the record that was updated. I encourage you to log these 2 objects to your console so you can explore them to get a better feel for what we get back from Record Watch.
You can see that inside of my anonymous function I am only looking for inserted records by using if (d.action == 'entry'). When I get a matching notification, I check the newly created incident's category and increment the bar that has a matching category.
This is just one example out of infinite possibilities of how you can use the Record Watch functionality. My specific thought behind this example is that you could create a dashboard that doesn't need to be refreshed because the widgets automatically adjust according to the Record Watch notifications.
Client Script
function(spUtil, $scope) {
/* widget controller */
var c = this;
// Grab our category counts from our Server Script
$scope.activeData = c.data.active;
$scope.inactiveData = c.data.inactive;
$scope.allData = c.data.all;
// Set the width of the chart along with the height of each bar
var width = c.options.width,
barHeight = c.options.bar_height,
leftMargin = c.options.left_margin;
$scope.updateBars = function(data) {
// Set the dimensions of our chart
var chart = d3.select(".chart").attr("width", width)
.attr("height", barHeight * data.length + 50);
// Remove existing axis and tooltip
d3.select(".x.axis").remove();
chart.select(".counter").remove();
// Add a placeholder text element for our tooltip
var counter = chart.append("text").attr("class", "counter")
.attr("y", 10)
.attr("x", width-20);
// Set the domain and range of the chart
var x = d3.scaleLinear()
.range([leftMargin, width])
.domain([0, d3.max(data, function(d) { return d.value * 1; }) + 10]);
// Bind our new data to our g elements
var bar = chart.selectAll("g").data(data, function(d) { return d.category;});
// Remove existing bars that aren't in the new data
bar.exit().remove();
// Create new g elements for new categories in our new data
var barEnter = bar.enter().append("g")
.attr("transform", function(d, i) { return "translate(0," + i * barHeight + ")"; });
// Enter new rect elements
barEnter.append("rect")
.on("mouseover", highlightBar)
.on("mouseout", unhighlightBar)
.attr("class", "chart-bar")
.attr("height", barHeight - 1)
.attr("x", leftMargin)
.transition().duration(750)
.attr("width", function(d) { return x(d.value) - leftMargin; });
// Enter new text labels
barEnter.append("text")
.attr("x", leftMargin - 5)
.attr("y", barHeight / 2)
.attr("width", leftMargin)
.attr("dy", ".35em")
.style("fill", "black")
.style("text-anchor", "end")
.transition()
.delay(750)
.text(function(d) { return d.category; });
// Update existing bars
bar.transition().duration(750)
.attr("transform", function(d, i) { return "translate(0," + i * barHeight + ")"; });
bar.selectAll('rect')
.on("mouseover", highlightBar)
.on("mouseout", unhighlightBar)
.data(data, function(d) { return d.category;})
.transition().duration(750)
.attr("width", function(d) { return x(d.value) - leftMargin; });
// Create the x-axis and append it to the bottom of the chart
var xAxis = d3.axisBottom().scale(x);
chart.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (barHeight * data.length) + ")")
.attr("x", leftMargin)
.call(xAxis);
// Define functions for our hover functionality
function highlightBar(d,i) {
d3.select(this).style("fill", "#b0c4de");
counter.text(d.category + ' ' + d.value);
}
function unhighlightBar(d,i) {
d3.select(this).style("fill", "#4682b4");
counter.text("");
}
}
spUtil.recordWatch($scope, "incident", "active=true", function(name, d) {
if (d.action == 'entry') {
for (i=0; i < $scope.activeData.length; i++) {
if (d.record.category.display_value == $scope.activeData[i].category) {
$scope.activeData[i].value++;
break;
}
}
$scope.updateBars($scope.activeData);
}
});
$scope.updateBars($scope.activeData);
}
Mitch Stutler
VividCharts Founder
linkedin.com/in/mitchellstutler
Sources
- d3js.org
https://www.servicenow.com/community/developer-blog/create-a-living-listening-widget/ba-p/2288190