Data Collections: Demystifying AngularJS and Service Portal

featured

Data Collections: Demystifying AngularJS and Service Portal

This series of blog shorts is intended for those who have little to no experience with AngularJS and custom widgets. We will take little bites into the world of Service Portal and AngularJS, demystifying it piece by piece.

In this post, we’ll look at how a list of catalog items is populated from a server side data collection. To illustrate, we’ll examine the SC Category Page widget, which is what you see when you click into a category in the Service Catalog.

If you’re following along in your own instance, go open the “SC Category Page” widget in widget editor mode. Clone it to create an editable copy for you to play with.

We want to understand how this list of items is created. So, to get our first clue we look in the HTML template. We talked about ng-repeat in a previous post, so you may recognize what’s happening in line 9. That ng-repeat is looping through the items in the array data.items.

<div class="col-sm-6 col-md-4" ng-repeat="item in data.items">

Great, so we found where each item gets repeated in the template, but what is populating data.items with content?

Looking through the client script and server script, see if you can find anything defining data.items. Spoiler alert, it’s in line 17 of the server script.

var items = data.items = [];

Exactly what are data.items and items?

data is an object used to store information and properties about each widget instance. There are many useful properties, options, etc. stored in the data object of each widget. In this widget, we add items to data. Thus data.items is now an object we can use to hold our collection of catalog items.

items is a local array that was created to hold the collection of catalog items and their properties. Both items and data.items start out as blank arrays. As we push content to items, both items and data.items will stay in sync. Why? Because data.items is a reference to items as a result of the way line 17 was written.

How do we push GlideRecord results into items?

When the GlideRecord queries, it returns the entire record object for each catalog item. That’s a lot of useless info. We don’t need to know everything, just what’s necessary. Therefore, we use a separate item object to collect only the properties/values we are interested in. This also gives us a chance to manipulate the data into a format most friendly to our purposes.

On line 32 of the server script an empty item object is defined inside the GlideRecord while loop.

while (sc.next()) {

     // Does user have permission to see this item?
     if (!$sp.canReadRecord("sc_cat_item", sc.sc_cat_item.sys_id.getDisplayValue()))
          continue;
     var item = {}; 
     …
}

The item object is used to collect the properties we want for each catalog item.

For example, on line 37 a property is set for sys_class_name. It’s given the same value as the catalog item’s sys_class_name.

item.sys_class_name = sc.sc_cat_item.sys_class_name + "";

 

Line 37 uses the + “” method to stringify the field value. This is important, because this value (sys_class_name) may get interpreted as an object, which would cause problems. Another way to stringify is: sc.sc_cat_item.sys_class_name.toString();

Or, the ServiceNow best practice way would be: sc.sc_cat_item.sys_class_name.getValue(‘sys_class_name’);

On line 38 a page property is hardcoded:

item.page = 'sc_cat_item';

Then depending on conditions, such as if this is a Content Item or Order Guide, we might change the item.page property, or create new properties. Lines 39 – 50:

if (item.sys_class_name == 'sc_cat_item_guide')
     item.page = 'sc_cat_item_guide';
else if (item.sys_class_name == 'sc_cat_item_content') {
     $sp.getRecordValues(item, gr, 'url,content_type,kb_article');
     if (item.content_type == 'kb') {
          item.page = 'kb_article';
          item.sys_id = item.kb_article;
     } else if (item.content_type == 'literal') {
          item.page = 'sc_cat_item';
     } else if (item.content_type == 'external')
          item.target = '_blank';
     }

See, you can create as many properties as you want, however you want, for each item. When done, push the item object to the items array. Line 52:

items.push(item);

Remember, these changes also affect data.items since it is a reference to items.

data.items should be full of catalog items now

Since we stored the collection items in the data object, we can access it in the HTML template and client controller. To see it in action, go back to Line 9 in the HTML template where ng-repeat loops on data.items:

<div class="col-sm-6 col-md-4" ng-repeat="item in data.items">
     …
     <div class="text-muted item-short-desc">{{item.short_description}}</div>
     …
</div>

Remember, if you want to use something in the template, first you’ll have to add it to the item object in the server script. For example, if there was a catalog item field named u_font_icon, I can add it as a property to item. At around line 51 in the server script I would write:

item.fontIcon = sc.sc_cat_item.getValue(‘u_font_icon’);
  • fontIcon is the name I give to the property.
  • sc_cat_item.u_font_icon is the reference to the custom field u_font_icon.

Then, in the HTML template I can use the property (here we assume the value I get from that field is a font awesome class, ie. ‘fa-laptop’):

<div class="col-sm-6 col-md-4" ng-repeat="item in data.items">
     …
     <i class=”fa {{ item.fontIcon }}”></i>
     <div class="text-muted item-short-desc">{{item.short_description}}</div>
     …
</div>

There’s a lot to explore on this topic, so in the spirit of brevity I will call curtains on this post. I hope this helps your understanding the widget’s data object, and using it to store content.

We didn’t even touch the client controller… maybe next time.

Aloha, Jeff