Skip to content
melissamoghaddam edited this page May 6, 2017 · 15 revisions

Using data & events across widgets

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.

Add a toolbar widget

  1. Using STUDIO click Create Application File.

  2. Select Service Portal -> Widget. Click Create.

  3. On the widget editor click Create a new widget. Use the following values:

    Widget name: Workspace header
    Widget ID: workspace_header

    Click Submit.

  4. Edit the option schema with the following values:
    Label: Title
    Name: title
    Type: string

    Label: Table
    Name: table
    Default Value: incident
    Type: string

    Label: Count expressions
    Name: count_expressions
    Hint: Label,query;Label,query;...
    Default Value: Unassigned Incidents,assigned_toISEMPTY;New Incidents,state=1
    Type: string

  5. Click Save

  6. 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>
  1. 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;
    }
  }
}
  1. 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};
	}
}
  1. Save the widget.

In summary, this widget can show a heading and counts.
widget summary

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.

Add the toolbar to the Incident Workspace page

  1. Open the Service Portal designer in a new tab by going to [instacnce]/$spd.do

  2. Open the Incident Workspace page.

  3. Drag a new 12 column container to the very top of the page above the existing [3 | 9] layout like this:
    widget designer

  4. Drag the Workspace header widget into that new container:
    workspace

Create an angular provider

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.

  1. Open your instance in a new tab:
    [instance]/

  2. Type Service Portal in the navigator and select Angular Providers
    provider

  3. At the top of the list click New

  4. 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.

Connect widgets together using data service

  1. Using the navigator, open the Widgets list

  2. Edit the Workspace Header widget

  3. Scroll to the bottom of the form, open the Angular Providers related list, and click Edit
    Edit providers

  4. Find workspaceData in the collection of angular providers. Double-click to move it to the right-side slush bucket.
    slush bucket

  5. Click Save

Now repeat the same steps for the Card List widget so both widgets will import the workspaceData service.

Use the workspaceData service inside the Card List widget

  1. Using the widget editor, open the Card List widget
  2. Inject the workspaceData service into the Client Script by adding it as a parameter to the controller function:
  3. 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:
Client controller

  1. 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.

Use the workspaceData service inside the Workspace Header widget

  1. Using the widget editor, open the Workspace Header widget
  2. Just like you did before, inject the workspaceData service into the Client Script controller function.
  3. Add the following code just below var c = this;
workspaceData.onDataUpdated(function(){
    c.counters.forEach(function(counter){ runCounter(counter); });
});
Clone this wiki locally