mitchellstutler
Tera Contributor

This post is going to build on my previous post. If you are wanting to follow along in your own instance you will probably have an easier time if you work through the first post before beginning this one.

In this post we will learn how to set up widget options and retrieve data from the server script. By the end of this we will have the tools we need to build configurable and dynamic widgets.

Widget options

Widget options allow you to set up configurable properties for individual instances of your widget. This gives you the ability to use the same widget across multiple pages or even portals while still giving you the flexibility to customize those instances of the widget.

To set our widget options up there will be 3 main steps: create the option schema, set default values for these options in our server script, and using these options in our client script.

From the widget editor, click the hamburger menu near the top right and select 'Edit option schema'. This gives us a modal that allows to create, modify, or delete options for our widget. In this example we will set up 3 options: Width, Bar Height, and Left Margin. Click the "+" button to add a new option and then give each of these options a type of "integer". Here is a screenshot of my option schema:

Post 2 Option Schema.png

Now that we have these options, we will use our server script to set default values in case the user doesn't need to customize their widget instance. Below is the portion of our server script that accomplishes the default values. Later in this post I will post the complete server script, but for now we will just enter this code at the beginning of our server script function.

// Set default options

options.width = options.width || 600;

options.bar_height = options.bar_height || 20;

options.left_margin = options.left_margin || 100;

These 3 lines check if there is a value for these options and sets to our default values if not.

Now that we have an option schema configured and are protected with default values, we can reference these options in our client script. The data and options objects are available to us in the client script by prefixing them with c like this "c.options.width". I will paste the full client script in a later section. For now, here are the lines that set our variables to our options so that we can easily reference them later.

// 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;

Server script

Now that we have properties set up let's work on dynamically pulling data from ServiceNow. In the previous post, we used hard-coded data to generate our bar chart. This time we will use a GlideAggregate call to grab the number of incidents in each category. If you aren't familiar with GlideAggregate check out its page on the Product Documentation Site.

The first thing we'll do is declare an array of objects within the data object:

data.categories = [];

Next, we'll make our GlideAggregate call and create an object within the array for each of the incident categories that are returned.

var count = new GlideAggregate('incident');

count.addQuery('active', 'true');

count.addAggregate('COUNT', 'category');

count.query();

while (count.next()) {

                      var category = count.category.getDisplayValue();

                      var categoryCount = count.getAggregate('COUNT', 'category') * 1.0;

                      data.categories.push({"category": category, "value": categoryCount});

}

We now have an array of objects that we can reference in our client script. Here is a screenshot of our server script along with the complete script pasted.

Post 2 Server Script.png

(function() {

                      /* populate the 'data' object */

               

                      // Set default options

                      options.width = options.width || 600;

                      options.bar_height = options.bar_height || 20;

                      options.left_margin = options.left_margin || 100;

               

                      // Create an array of objects containing the categories along with

                      // the number of objects in each

                      data.categories = [];

               

                      var count = new GlideAggregate('incident');

                      count.addQuery('active', 'true');

                      count.addAggregate('COUNT', 'category');

                      count.query();

                      while (count.next()) {

                                              var category = count.category.getDisplayValue();

                                              var categoryCount = count.getAggregate('COUNT', 'category') * 1.0;

                                              data.categories.push({"category": category, "value": categoryCount});

                      }

})();

Client script

In the last post we had a variable named "data" that we had set equal to a hard-coded array of objects in our client script. This time we will set that same variable equal to our array of objects that we created in our server script. Here is the line of code that accomplishes this:

// Define our hard-coded data

var data = c.data.categories;

Other than that and the options that we set up earlier, the main new piece introduced is the bottom axis. We add that in with these lines:

// 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);

Below is a screenshot of our client script as well as the pasted script.

Post 2 Client Script.png

function() {

                      /* widget controller */

                      var c = this;

               

                      // Define our hard-coded data

                      var data = c.data.categories;

               

                      // 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;

               

                      var chart = d3.select(".chart")

                      .attr("width", width)

                      .attr("height", barHeight * data.length + 50);

               

                      // 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; })]);

               

                      // Add a g container for each row from our data

                      var bar = chart.selectAll("g")

                      .data(data)

                      .enter().append("g")

                      .attr("transform", function(d, i) { return "translate(0," + i * barHeight + ")"; });

               

                      // Add a rectangle element with the width based off of the value from that row of data

                      bar.append("rect")

                      .attr("width", function(d) { return x(d.value) - leftMargin; })

                      .attr("height", barHeight - 1)

                      .attr("x", leftMargin);

               

                      // Add text elements to serve as labels of our categories

                      bar.append("text")

                      .attr("x", leftMargin - 5)

                      .attr("y", barHeight / 2)

                      .attr("width", leftMargin)

                      .attr("dy", ".35em")

                      .style("fill", "black")

                      .style("text-anchor", "end")

                      .text(function(d) { return d.category; });

               

                      // 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);

               

}

CSS

We also have a couple of updates to our CSS this time. Here is what I have in my widget's CSS:

.chart rect {

  fill: steelblue;

}

.chart text {

  font: 10px sans-serif;

}

.centered-chart {

  text-align: center;

}

.axis text {

  font: 10px sans-serif;

}

.axis path,

.axis line {

  fill: none;

  stroke: #000;

  shape-rendering: crispEdges;

}

Trying it out

Now that we have everything in place we will go ahead and test our updated widget out. The first thing we'll do is view our page in the page designer and hover over our widget. You should see a pencil icon in the top right corner of your widget. If you click that pencil you will get a modal that has your options in it.

Here is a screenshot of how I configured my widget instance:

Post 2 Option Configuration.png

Here is what those options give me in action:

Post 2 Bar Chart.png

Next time

In the next post, we will explore how to use multiple datasets with D3 and how to make our chart interactive.

Mitch Stutler

VividCharts Founder

vividcharts.com

linkedin.com/in/mitchellstutler

twitter.com/mitchstutler

Sources

- ServiceNow GlideAggregate

- Service Portal Documentation

- https://d3js.org/

3 Comments